All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.noelios.restlet.local.FileClientHelper Maven / Gradle / Ivy

Go to download

This OSGi bundle wraps org.restlet, and com.noelios.restlet ${pkgVersion} jar files.

The newest version!
/**
 * Copyright 2005-2008 Noelios Technologies.
 * 
 * The contents of this file are subject to the terms of the following open
 * source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 (the "Licenses"). You can
 * select the license that you prefer but you may not use this file except in
 * compliance with one of these Licenses.
 * 
 * You can obtain a copy of the LGPL 3.0 license at
 * http://www.gnu.org/licenses/lgpl-3.0.html
 * 
 * You can obtain a copy of the LGPL 2.1 license at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 * 
 * You can obtain a copy of the CDDL 1.0 license at
 * http://www.sun.com/cddl/cddl.html
 * 
 * See the Licenses for the specific language governing permissions and
 * limitations under the Licenses.
 * 
 * Alternatively, you can obtain a royaltee free commercial license with less
 * limitations, transferable or non-transferable, directly at
 * http://www.noelios.com/products/restlet-engine
 * 
 * Restlet is a registered trademark of Noelios Technologies.
 */

package com.noelios.restlet.local;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import org.restlet.Client;
import org.restlet.data.Encoding;
import org.restlet.data.Language;
import org.restlet.data.LocalReference;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Range;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.Variant;
import org.restlet.service.MetadataService;
import org.restlet.util.ByteUtils;

/**
 * Connector to the file resources accessible. Here is the list of parameters
 * that are supported:
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
Parameter nameValue typeDefault valueDescription
temporaryExtensionStringtmpThe name of the extension to use to store the temporary content while * uploading content via the PUT method.
resumeUploadbooleanfalseIndicates if a failed upload can be resumed. This will prevent the * deletion of the temporary file created.
* * @author Jerome Louvel * @author Thierry Boileau */ public class FileClientHelper extends EntityClientHelper { /** * Constructor. * * @param client * The client to help. */ public FileClientHelper(Client client) { super(client); getProtocols().add(Protocol.FILE); } /** * Check that all extensions of the file correspond to a known metadata. * * @param file * The file whose extensions are checked. * @param metadataService * The metadata service. * @return True if all extensions of the file are known by the metadata * service. */ protected boolean checkExtensionsConsistency(File file, MetadataService metadataService) { boolean knownExtension = true; final Collection set = Entity.getExtensions(file.getName(), metadataService); final Iterator iterator = set.iterator(); while (iterator.hasNext() && knownExtension) { knownExtension = metadataService.getMetadata(iterator.next()) != null; } return knownExtension; } /** * Checks that the URI and the representation are compatible. The whole set * of metadata of the representation must be included in the set of those of * the URI * * @param fileName * The name of the resource * @param metadataService * metadata helper * @param representation * the provided representation * @return true if the metadata of the representation are compatible with * the metadata extracted from the filename */ private boolean checkMetadataConsistency(String fileName, MetadataService metadataService, Representation representation) { boolean result = true; if (representation != null) { final Variant var = new Variant(); updateMetadata(metadataService, fileName, var); // "var" contains the theorical correct metadata if (!var.getLanguages().isEmpty() && !representation.getLanguages().isEmpty() && !var.getLanguages().containsAll( representation.getLanguages())) { result = false; } if ((var.getMediaType() != null) && (representation.getMediaType() != null) && !(var.getMediaType().includes(representation .getMediaType()))) { result = false; } if (!var.getEncodings().isEmpty() && !representation.getEncodings().isEmpty() && !var.getEncodings().containsAll( representation.getEncodings())) { result = false; } } return result; } @Override public Entity getEntity(String decodedPath) { // Take care of the file separator. return new FileEntity( new File(LocalReference.localizePath(decodedPath))); } /** * Returns the name of the extension to use to store the temporary content * while uploading content via the PUT method. Defaults to "tmp". * * @return The name of the extension to use to store the temporary content. */ public String getTemporaryExtension() { return getHelpedParameters().getFirstValue("temporaryExtension", "tmp"); } /** * Handles a call. * * @param request * The request to handle. * @param response * The response to update. */ @Override public void handle(Request request, Response response) { final String scheme = request.getResourceRef().getScheme(); if (Protocol.FILE.getSchemeName().equalsIgnoreCase(scheme)) { super.handle(request, response); } else { throw new IllegalArgumentException( "Protocol \"" + scheme + "\" not supported by the connector. Only FILE is supported."); } } @Override protected void handleEntity(Request request, Response response, String path, String decodedPath, MetadataService metadataService) { if (Method.GET.equals(request.getMethod()) || Method.HEAD.equals(request.getMethod())) { handleEntityGet(request, response, path, getEntity(decodedPath), metadataService); } else if (Method.PUT.equals(request.getMethod())) { handleFilePut(request, response, decodedPath, new File(decodedPath), metadataService); } else if (Method.DELETE.equals(request.getMethod())) { handleFileDelete(response, new File(decodedPath)); } else { response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); response.getAllowedMethods().add(Method.GET); response.getAllowedMethods().add(Method.HEAD); response.getAllowedMethods().add(Method.PUT); response.getAllowedMethods().add(Method.DELETE); } } /** * Handles a DELETE call for the FILE protocol. * * @param response * The response to update. * @param file * The file or directory to delete. */ protected void handleFileDelete(Response response, File file) { if (file.isDirectory()) { if (file.listFiles().length == 0) { if (file.delete()) { response.setStatus(Status.SUCCESS_NO_CONTENT); } else { response.setStatus(Status.SERVER_ERROR_INTERNAL, "Couldn't delete the directory"); } } else { response.setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Couldn't delete the non-empty directory"); } } else { if (file.delete()) { response.setStatus(Status.SUCCESS_NO_CONTENT); } else { response.setStatus(Status.SERVER_ERROR_INTERNAL, "Couldn't delete the file"); } } } /** * Handles a PUT call for the FILE protocol. * * @param request * The request to update. * @param response * The response to update. * @param path * The encoded path of the requested file or directory. * @param file * The requested file or directory. * @param metadataService * The metadata service. */ protected void handleFilePut(Request request, Response response, String path, File file, final MetadataService metadataService) { // Deals with directory boolean isDirectory = false; if (file.exists()) { if (file.isDirectory()) { isDirectory = true; response.setStatus(new Status(Status.CLIENT_ERROR_FORBIDDEN, "Can't put a new representation of a directory")); return; } } else { // No existing file or directory found if (path.endsWith("/")) { isDirectory = true; // Create a new directory and its parents if necessary if (file.mkdirs()) { response.setStatus(Status.SUCCESS_NO_CONTENT); } else { getLogger().log(Level.WARNING, "Unable to create the new directory"); response.setStatus(new Status(Status.SERVER_ERROR_INTERNAL, "Unable to create the new directory")); } return; } } if (!isDirectory) { // Several checks : first the consistency of the metadata and // the filename boolean partialPut = !request.getRanges().isEmpty(); if (!checkMetadataConsistency(file.getName(), metadataService, request.getEntity())) { // ask the client to reiterate properly its request response.setStatus(new Status(Status.REDIRECTION_SEE_OTHER, "The metadata are not consistent with the URI")); return; } else { // We look for the possible variants // 1- set up base name as the longest part of the name // without known extensions (beginning from the left) final String baseName = Entity.getBaseName(file.getName(), metadataService); final Collection extensions = Entity.getExtensions(file .getName(), metadataService); // 2- loooking for resources with the same base name final File[] files = file.getParentFile().listFiles(); File uniqueVariant = null; final List variantsList = new ArrayList(); if (files != null) { for (final File entry : files) { if (baseName.equals(Entity.getBaseName(entry.getName(), metadataService))) { final Collection entryExtensions = Entity .getExtensions(entry.getName(), metadataService); if (entryExtensions.containsAll(extensions)) { variantsList.add(entry); if (extensions.containsAll(entryExtensions)) { // The right representation has been found. uniqueVariant = entry; } } } } } if (uniqueVariant != null) { file = uniqueVariant; } else { if (!variantsList.isEmpty()) { // Negociated resource (several variants, but not the // right one). // Check if the request could be completed or not. // The request could be more precise response .setStatus(new Status( Status.CLIENT_ERROR_NOT_ACCEPTABLE, "Unable to process properly the request. Several variants exist but none of them suits precisely.")); return; } else { // This resource does not exist, yet. // Complete it with the default metadata updateMetadata(metadataService, file.getName(), request .getEntity()); if (request.getEntity().getLanguages().isEmpty()) { if (metadataService.getDefaultLanguage() != null) { request.getEntity().getLanguages().add( metadataService.getDefaultLanguage()); } } if (request.getEntity().getMediaType() == null) { request.getEntity().setMediaType( metadataService.getDefaultMediaType()); } if (request.getEntity().getEncodings().isEmpty()) { if ((metadataService.getDefaultEncoding() != null) && !metadataService.getDefaultEncoding() .equals(Encoding.IDENTITY)) { request.getEntity().getEncodings().add( metadataService.getDefaultEncoding()); } } // Update the URI final StringBuilder fileName = new StringBuilder( baseName); if (metadataService.getExtension(request.getEntity() .getMediaType()) != null) { fileName.append("." + metadataService.getExtension(request .getEntity().getMediaType())); } for (final Language language : request.getEntity() .getLanguages()) { if (metadataService.getExtension(language) != null) { fileName.append("." + metadataService .getExtension(language)); } } for (final Encoding encoding : request.getEntity() .getEncodings()) { if (metadataService.getExtension(encoding) != null) { fileName.append("." + metadataService .getExtension(encoding)); } } file = new File(file.getParentFile(), fileName .toString()); } } // Before putting the file representation, we check that all // the extensions are known if (!checkExtensionsConsistency(file, metadataService)) { response .setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to process properly the URI. At least one extension is not known by the server.")); return; } else { File tmp = null; boolean error = false; if (file.exists()) { // The PUT call is handled in two phases: // 1- write a temporary file // 2- rename the target file if (partialPut) { RandomAccessFile raf = null; // Replace the content of the file // First, create a temporary file try { // The temporary file used for partial PUT. tmp = new File(file.getCanonicalPath() + "." + getTemporaryExtension()); // Support only one range. Range range = request.getRanges().get(0); if (tmp.exists() && !isResumeUpload()) { tmp.delete(); } if (!tmp.exists()) { // Copy the target file. InputStream in = new FileInputStream(file); OutputStream out = new FileOutputStream(tmp); ByteUtils.write(in, out); out.flush(); out.close(); } raf = new RandomAccessFile(tmp, "rwd"); // Go to the desired offset. if (range.getIndex() == Range.INDEX_LAST) { if (raf.length() <= range.getSize()) { raf.seek(range.getSize()); } else { raf .seek(raf.length() - range.getSize()); } } else { raf.seek(range.getIndex()); } // Write the entity to the temporary file. if (request.isEntityAvailable()) { ByteUtils.write(request.getEntity() .getStream(), raf); } } catch (IOException ioe) { getLogger().log(Level.WARNING, "Unable to create the temporary file", ioe); response.setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to create a temporary file")); error = true; } finally { try { if (raf != null) { raf.close(); // Calling the garbage collector helps // to workaround lock issues on Windows System.gc(); } } catch (IOException ioe) { getLogger() .log( Level.WARNING, "Unable to close the temporary file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); error = true; } } } else { FileOutputStream fos = null; try { tmp = File.createTempFile("restlet-upload", "bin"); if (request.isEntityAvailable()) { fos = new FileOutputStream(tmp); ByteUtils.write(request.getEntity() .getStream(), fos); } } catch (IOException ioe) { getLogger().log(Level.WARNING, "Unable to create the temporary file", ioe); response.setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to create a temporary file")); error = true; } finally { try { if (fos != null) { fos.close(); } } catch (IOException ioe) { getLogger() .log( Level.WARNING, "Unable to close the temporary file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); error = true; } } } if (error) { if (tmp.exists() && !isResumeUpload()) { tmp.delete(); } return; } // Then delete the existing file if (tmp.exists() && file.delete()) { // Finally move the temporary file to the // existing file location boolean renameSuccessfull = false; if (tmp.renameTo(file)) { if (request.getEntity() == null) { response .setStatus(Status.SUCCESS_NO_CONTENT); } else { response.setStatus(Status.SUCCESS_OK); } renameSuccessfull = true; } else { // Many aspects of the behavior of the method // "renameTo" are inherently platform-dependent: // the rename operation might not be able to // move a file from one filesystem to another. if (tmp.exists()) { try { InputStream in = new FileInputStream( tmp); OutputStream out = new FileOutputStream( file); ByteUtils.write(in, out); out.flush(); out.close(); renameSuccessfull = true; tmp.delete(); } catch (Exception e) { renameSuccessfull = false; } } if (!renameSuccessfull) { getLogger() .log(Level.WARNING, "Unable to move the temporary file to replace the existing file"); response .setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to move the temporary file to replace the existing file")); } } } else { getLogger().log(Level.WARNING, "Unable to delete the existing file"); response.setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to delete the existing file")); if (tmp.exists() && !isResumeUpload()) { tmp.delete(); } } } else { // The file does not exist yet. final File parent = file.getParentFile(); if ((parent != null) && !parent.exists()) { // Create the parent directories then the new file if (!parent.mkdirs()) { getLogger() .log(Level.WARNING, "Unable to create the parent directory"); response .setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to create the parent directory")); } } // Create the new file if (partialPut) { // This is a partial PUT RandomAccessFile raf = null; try { raf = new RandomAccessFile(file, "rwd"); // Support only one range. Range range = request.getRanges().get(0); // Go to the desired offset. if (range.getIndex() == Range.INDEX_LAST) { if (raf.length() <= range.getSize()) { raf.seek(range.getSize()); } else { raf .seek(raf.length() - range.getSize()); } } else { raf.seek(range.getIndex()); } // Write the entity to the file. if (request.isEntityAvailable()) { ByteUtils.write(request.getEntity() .getStream(), raf); } } catch (FileNotFoundException fnfe) { getLogger().log(Level.WARNING, "Unable to create the new file", fnfe); response.setStatus( Status.SERVER_ERROR_INTERNAL, fnfe); } catch (IOException ioe) { getLogger().log(Level.WARNING, "Unable to create the new file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); } finally { try { if (raf != null) { raf.close(); // Calling the garbage collector helps // to workaround lock issues on Windows System.gc(); } } catch (IOException ioe) { getLogger() .log( Level.WARNING, "Unable to close the new file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); } } } else { // This is simple PUT of the full entity FileOutputStream fos = null; try { if (file.createNewFile()) { if (request.getEntity() == null) { response .setStatus(Status.SUCCESS_NO_CONTENT); } else { fos = new FileOutputStream(file); ByteUtils.write(request.getEntity() .getStream(), fos); response .setStatus(Status.SUCCESS_CREATED); } } else { getLogger().log(Level.WARNING, "Unable to create the new file"); response.setStatus(new Status( Status.SERVER_ERROR_INTERNAL, "Unable to create the new file")); } } catch (FileNotFoundException fnfe) { getLogger().log(Level.WARNING, "Unable to create the new file", fnfe); response.setStatus( Status.SERVER_ERROR_INTERNAL, fnfe); } catch (IOException ioe) { getLogger().log(Level.WARNING, "Unable to create the new file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); } finally { try { if (fos != null) { fos.close(); } } catch (IOException ioe) { getLogger() .log( Level.WARNING, "Unable to close the new file", ioe); response.setStatus( Status.SERVER_ERROR_INTERNAL, ioe); } } } } } } } } /** * Indicates if a failed upload can be resumed. This will prevent the * deletion of the temporary file created. Defaults to "false". * * @return True if a failed upload can be resumed, false otherwise. */ public boolean isResumeUpload() { return Boolean.parseBoolean(getHelpedParameters().getFirstValue( "resumeUpload", "false")); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy