Package org.restlet.engine.local

Source Code of org.restlet.engine.local.FileClientHelper

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 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.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty 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 org.restlet.engine.local;

import java.io.File;
import java.io.FileFilter;
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.Request;
import org.restlet.Response;
import org.restlet.data.CharacterSet;
import org.restlet.data.Encoding;
import org.restlet.data.Language;
import org.restlet.data.LocalReference;
import org.restlet.data.MediaType;
import org.restlet.data.Metadata;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Range;
import org.restlet.data.Status;
import org.restlet.engine.io.BioUtils;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;

/**
* Connector to the file resources accessible. Here is the list of parameters
* that are supported. They should be set in the Client's context before it is
* started:
* <table>
* <tr>
* <th>Parameter name</th>
* <th>Value type</th>
* <th>Default value</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>temporaryExtension</td>
* <td>String</td>
* <td>tmp</td>
* <td>The name of the extension to use to store the temporary content while
* uploading content via the PUT method.</td>
* </tr>
* <tr>
* <td>resumeUpload</td>
* <td>boolean</td>
* <td>false</td>
* <td>Indicates if a failed upload can be resumed. This will prevent the
* deletion of the temporary file created.</td>
* </tr>
* </table>
*
* @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.
     * @return True if all extensions of the file are known by the metadata
     *         service.
     */
    protected boolean checkExtensionsConsistency(File file) {
        boolean knownExtension = true;

        Collection<String> set = Entity.getExtensions(file.getName(),
                getMetadataService());
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext() && knownExtension) {
            knownExtension = getMetadataService().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 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,
            Representation representation) {
        boolean result = true;

        if (representation != null) {
            Variant var = new Variant();
            Entity.updateMetadata(fileName, var, false, getMetadataService());

            // "var" contains the theoretical 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)),
                getMetadataService());
    }

    /**
     * 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");
    }

    @Override
    protected void handleLocal(Request request, Response response,
            String decodedPath) {
        String scheme = request.getResourceRef().getScheme();

        if (Protocol.FILE.getSchemeName().equalsIgnoreCase(scheme)) {
            handleFile(request, response, decodedPath);
        } else {
            throw new IllegalArgumentException(
                    "Protocol \""
                            + scheme
                            + "\" not supported by the connector. Only FILE is supported.");
        }
    }

    protected void handleFile(Request request, Response response,
            String decodedPath) {
        if (Method.GET.equals(request.getMethod())
                || Method.HEAD.equals(request.getMethod())) {
            handleEntityGet(request, response, getEntity(decodedPath));
        } else if (Method.PUT.equals(request.getMethod())) {
            handleFilePut(request, response, decodedPath, new File(decodedPath));
        } 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 (BioUtils.delete(file)) {
                    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 (BioUtils.delete(file)) {
                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.
     */
    protected void handleFilePut(Request request, Response response,
            String path, File file) {
        // 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(), request.getEntity())) {
                // Ask the client to reiterate properly its request
                response.setStatus(new Status(Status.CLIENT_ERROR_BAD_REQUEST,
                        "The metadata are not consistent with the URI"));
                return;
            }

            // We look for the possible variants
            // 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(),
                    getMetadataService());

            // Look for resources with the same base name
            FileFilter filter = new FileFilter() {
                public boolean accept(File file) {
                    return file.isFile()
                            && baseName.equals(Entity.getBaseName(
                                    file.getName(), getMetadataService()));
                }
            };

            File[] files = file.getParentFile().listFiles(filter);
            File uniqueVariant = null;
            List<File> variantsList = new ArrayList<File>();

            if (files != null && files.length > 0) {
                // Set the list of extensions, due to the file name and the
                // default metadata.
                // TODO It seems we could handle more clearly the equivalence
                // between the file name space and the target resource (URI
                // completed by default metadata)
                Variant variant = new Variant();
                Entity.updateMetadata(file.getName(), variant, false,
                        getMetadataService());
                Collection<String> extensions = Entity.getExtensions(variant,
                        getMetadataService());

                for (File entry : files) {
                    Collection<String> entryExtensions = Entity.getExtensions(
                            entry.getName(), getMetadataService());

                    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()) {
                    // Negotiated 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;
                }

                // This resource does not exist, yet. Complete it with the
                // default metadata
                Entity.updateMetadata(file.getName(), request.getEntity(),
                        true, getMetadataService());

                // Update the URI
                StringBuilder fileName = new StringBuilder(baseName);

                for (Language language : request.getEntity().getLanguages()) {
                    updateFileExtension(fileName, language);
                }

                for (Encoding encoding : request.getEntity().getEncodings()) {
                    updateFileExtension(fileName, encoding);
                }

                // It is important to finish with the media type as it is
                // often leveraged by operating systems to detect file type
                updateFileExtension(fileName, request.getEntity()
                        .getMediaType());

                file = new File(file.getParentFile(), fileName.toString());
            }

            // Before putting the file representation, we check that all the
            // extensions are known
            if (!checkExtensionsConsistency(file)) {
                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;
            }

            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()) {
                            BioUtils.delete(tmp);
                        }

                        if (!tmp.exists()) {
                            // Copy the target file.
                            InputStream in = new FileInputStream(file);
                            OutputStream out = new FileOutputStream(tmp);
                            BioUtils.copy(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()) {
                            BioUtils.copy(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();
                            }
                        } 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);
                            BioUtils.copy(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()) {
                        BioUtils.delete(tmp);
                    }

                    return;
                }

                // Then delete the existing file
                if (tmp.exists() && BioUtils.delete(file)) {
                    // Finally move the temporary file to the existing file
                    // location
                    boolean renameSuccessful = false;
                    if (tmp.renameTo(file)) {
                        if (request.getEntity() == null) {
                            response.setStatus(Status.SUCCESS_NO_CONTENT);
                        } else {
                            response.setStatus(Status.SUCCESS_OK);
                        }

                        renameSuccessful = 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
                        // file system to another.
                        if (tmp.exists()) {
                            try {
                                InputStream in = new FileInputStream(tmp);
                                OutputStream out = new FileOutputStream(file);
                                BioUtils.copy(in, out);
                                out.close();
                                renameSuccessful = true;
                                BioUtils.delete(tmp);
                            } catch (Exception e) {
                                renameSuccessful = false;
                            }
                        }
                        if (!renameSuccessful) {
                            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()) {
                        BioUtils.delete(tmp);
                    }
                }
            } else {
                // The file does not exist yet.
                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()) {
                            BioUtils.copy(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();
                            }
                        } 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);
                                BioUtils.copy(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"));
    }

    /**
     * Complete the given file name with the extension corresponding to the
     * given metadata.
     *
     * @param fileName
     *            The file name to complete.
     * @param metadata
     *            The metadata.
     */
    private void updateFileExtension(StringBuilder fileName, Metadata metadata) {
        boolean defaultMetadata = true;

        if (getMetadataService() != null) {
            if (metadata instanceof Language) {
                Language language = (Language) metadata;
                defaultMetadata = language.equals(getMetadataService()
                        .getDefaultLanguage());
            } else if (metadata instanceof MediaType) {
                MediaType mediaType = (MediaType) metadata;
                defaultMetadata = mediaType.equals(getMetadataService()
                        .getDefaultMediaType());
            } else if (metadata instanceof CharacterSet) {
                CharacterSet characterSet = (CharacterSet) metadata;
                defaultMetadata = characterSet.equals(getMetadataService()
                        .getDefaultCharacterSet());
            } else if (metadata instanceof Encoding) {
                Encoding encoding = (Encoding) metadata;
                defaultMetadata = encoding.equals(getMetadataService()
                        .getDefaultEncoding());
            }
        }

        // We only add extension for metadata that differs from default ones
        if (!defaultMetadata) {
            String extension = getMetadataService().getExtension(metadata);

            if (extension != null) {
                fileName.append("." + extension);
            } else {
                if (metadata.getParent() != null) {
                    updateFileExtension(fileName, metadata.getParent());
                }
            }
        }
    }
}
TOP

Related Classes of org.restlet.engine.local.FileClientHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.