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

com.noelios.restlet.local.DirectoryResource 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.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;

import org.restlet.Directory;
import org.restlet.Uniform;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Preference;
import org.restlet.data.Reference;
import org.restlet.data.ReferenceList;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;

/**
 * Resource supported by a set of context representations (from file system,
 * class loaders and webapp context). A content negotiation mechanism (similar
 * to Apache HTTP server) is available. It is based on path extensions to detect
 * variants (languages, media types or character sets).
 * 
 * @see Apache
 *      mod_negotiation module
 * @author Jerome Louvel
 * @author Thierry Boileau
 */
public class DirectoryResource extends Resource {

    /**
     * Returns the set of extensions contained in a given directory entry name.
     * 
     * @param entryName
     *            The directory entry name.
     * @return The set of extensions.
     */
    public static Set getExtensions(String entryName) {
        final Set result = new TreeSet();
        final String[] tokens = entryName.split("\\.");
        for (int i = 1; i < tokens.length; i++) {
            result.add(tokens[i].toLowerCase());
        }
        return result;
    }

    /** The base set of extensions. */
    private Set baseExtensions;

    /**
     * The local base name of the resource. For example, "foo.en" and
     * "foo.en-GB.html" return "foo".
     */
    private String baseName;

    /** The parent directory handler. */
    private final Directory directory;

    /** If the resource is a directory, this contains its content. */
    private ReferenceList directoryContent;

    /**
     * If the resource is a directory, the non-trailing slash caracter leads to
     * redirection.
     */
    private boolean directoryRedirection;

    /** Indicates if the target resource is a directory. */
    private boolean directoryTarget;

    /** The context's directory URI (file, clap URI). */
    private String directoryUri;

    /** If the resource is a file, this contains its content. */
    private Representation fileContent;

    /** Indicates if the target resource is a file. */
    private boolean fileTarget;

    /** Indicates if the target resource is a directory with an index. */
    private boolean indexTarget;

    /** The original target URI, in cas of extensions tunneling. */
    private Reference originalRef;

    /** The resource path relative to the directory URI. */
    private String relativePart;

    /** The context's target URI (file, clap URI). */
    private String targetUri;

    /** The unique representation of the target URI, if it exists. */
    private Reference uniqueReference;

    /**
     * This constructor aims at answering the following questions:
*
    *
  • does this request target a directory?
  • *
  • does this request target a directory, with an index file?
  • *
  • should this request be redirected (target is a directory with no * trailing "/")?
  • *
  • does this request target a file?
  • *
*
* The following constraints must be taken into account:
*
    *
  • the underlying helper may not support content negotiation and be * able to return the list of possible variants of the target file (e.g. the * CLAP helper).
  • *
  • the underlying helper may not support directory listing
  • *
  • the extensions tunneling cannot apply on a directory
  • *
  • underlying helpers that do not support content negotiation cannot * support extensions tunneling
  • *
* * @param directory * The parent directory handler. * @param request * The handled call. * @throws IOException */ public DirectoryResource(Directory directory, Request request, Response response) throws IOException { super(directory.getContext(), request, response); // Update the member variables this.directory = directory; this.relativePart = request.getResourceRef().getRemainingPart(false, false); setModifiable(this.directory.isModifiable()); setNegotiateContent(this.directory.isNegotiateContent()); // Restore the original URI in case the call has been tunnelled. if ((getApplication() != null) && getApplication().getTunnelService().isExtensionsTunnel()) { this.originalRef = request.getOriginalRef(); if (this.originalRef != null) { this.originalRef.setBaseRef(request.getResourceRef() .getBaseRef()); this.relativePart = this.originalRef.getRemainingPart(); } } if (this.relativePart.startsWith("/")) { // We enforce the leading slash on the root URI this.relativePart = this.relativePart.substring(1); } // The target uri does not take into account the query and fragment // parts of the resource. this.targetUri = new Reference(directory.getRootRef().toString() + this.relativePart).normalize().toString(false, false); if (!this.targetUri.startsWith(directory.getRootRef().toString())) { // Prevent the client from accessing resources in upper directories this.targetUri = directory.getRootRef().toString(); } if (getClientDispatcher() == null) { getLogger().warning( "No client dispatcher is available on the context. Can't get the target URI: " + this.targetUri); } else { // Try to detect the presence of a directory Response contextResponse = getClientDispatcher() .get(this.targetUri); if (contextResponse.getEntity() != null) { // As a convention, underlying client connectors return the // directory listing with the media-type // "MediaType.TEXT_URI_LIST" when handling directories if (MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity() .getMediaType())) { this.directoryTarget = true; this.fileTarget = false; this.directoryContent = new ReferenceList(contextResponse .getEntity()); if (!request.getResourceRef().getIdentifier().endsWith("/")) { // All requests will be automatically redirected this.directoryRedirection = true; } if (!this.targetUri.endsWith("/")) { this.targetUri += "/"; this.relativePart += "/"; } // Append the index name if ((getDirectory().getIndexName() != null) && (getDirectory().getIndexName().length() > 0)) { this.directoryUri = this.targetUri; this.baseName = getDirectory().getIndexName(); this.targetUri = this.directoryUri + this.baseName; this.indexTarget = true; } else { this.directoryUri = this.targetUri; this.baseName = null; } } else { // Allows underlying helpers that do not support "content // negotiation" to return the targetted file. this.directoryTarget = false; this.fileTarget = true; this.fileContent = contextResponse.getEntity(); } } else { this.directoryTarget = false; this.fileTarget = false; // Let's try with the facultative index, in case the underlying // client connector does not handle directory listing. if (this.targetUri.endsWith("/")) { // In this case, the trailing "/" shows that the URI must // point to a directory if ((getDirectory().getIndexName() != null) && (getDirectory().getIndexName().length() > 0)) { this.directoryUri = this.targetUri; this.directoryTarget = true; contextResponse = getClientDispatcher().get( this.directoryUri + getDirectory().getIndexName()); if (contextResponse.getEntity() != null) { this.baseName = getDirectory().getIndexName(); this.targetUri = this.directoryUri + this.baseName; this.directoryContent = new ReferenceList(); this.directoryContent.add(new Reference( this.targetUri)); this.indexTarget = true; } } } else { // Try to determine if this target URI with no trailing "/" // is a directory, in order to force the redirection. if ((getDirectory().getIndexName() != null) && (getDirectory().getIndexName().length() > 0)) { // Append the index name contextResponse = getClientDispatcher().get( this.targetUri + "/" + getDirectory().getIndexName()); if (contextResponse.getEntity() != null) { this.directoryUri = this.targetUri + "/"; this.baseName = getDirectory().getIndexName(); this.targetUri = this.directoryUri + this.baseName; this.directoryTarget = true; this.directoryRedirection = true; this.directoryContent = new ReferenceList(); this.directoryContent.add(new Reference( this.targetUri)); this.indexTarget = true; } } } } // In case the request does not target a directory and the file has // not been found, try with the tunnelled URI. if (isNegotiateContent() && !this.directoryTarget && !this.fileTarget && (this.originalRef != null)) { this.relativePart = request.getResourceRef().getRemainingPart(); // The target uri does not take into account the query and // fragment parts of the resource. this.targetUri = new Reference(directory.getRootRef() .toString() + this.relativePart).normalize().toString(false, false); if (!this.targetUri.startsWith(directory.getRootRef() .toString())) { // Prevent the client from accessing resources in upper // directories this.targetUri = directory.getRootRef().toString(); } } // Try to get the directory content, in case the request does not // target a directory if (!this.directoryTarget) { final int lastSlashIndex = this.targetUri.lastIndexOf('/'); if (lastSlashIndex == -1) { this.directoryUri = ""; this.baseName = this.targetUri; } else { this.directoryUri = this.targetUri.substring(0, lastSlashIndex + 1); this.baseName = this.targetUri .substring(lastSlashIndex + 1); } contextResponse = getClientDispatcher().get(this.directoryUri); if ((contextResponse.getEntity() != null) && MediaType.TEXT_URI_LIST.equals(contextResponse .getEntity().getMediaType())) { this.directoryContent = new ReferenceList(contextResponse .getEntity()); } } if (this.baseName != null) { // Remove the extensions from the base name final int firstDotIndex = this.baseName.indexOf('.'); if (firstDotIndex != -1) { // Store the set of extensions this.baseExtensions = getExtensions(this.baseName); // Remove stored extensions from the base name this.baseName = this.baseName.substring(0, firstDotIndex); } } } // Check if the resource exists or not. final List variants = getVariants(); if ((variants == null) || (variants.isEmpty())) { setAvailable(false); } // Check if the resource is located in a sub directory. if (isAvailable() && !this.directory.isDeeplyAccessible()) { // Count the number of "/" character. int index = this.relativePart.indexOf("/"); if (index != -1) { index = this.relativePart.indexOf("/", index); setAvailable((index == -1)); } } // Log results getLogger().info("Converted target URI: " + this.targetUri); getLogger().fine("Converted base name : " + this.baseName); } /** * Returns the local base name of the file. For example, "foo.en" and * "foo.en-GB.html" return "foo". * * @return The local name of the file. */ public String getBaseName() { return this.baseName; } /** * Returns a client dispatcher. * * @return A client dispatcher. */ private Uniform getClientDispatcher() { return getDirectory().getContext().getClientDispatcher(); } /** * Returns the parent directory handler. * * @return The parent directory handler. */ public Directory getDirectory() { return this.directory; } /** * Returns the context's directory URI (file, clap URI). * * @return The context's directory URI (file, clap URI). */ public String getDirectoryUri() { return this.directoryUri; } /** * Allows to sort the list of representations set by the resource. * * @return A Comparator instance imposing a sort order of representations or * null if no special order is wanted. */ private Comparator getRepresentationsComparator() { // Sort the list of representations by their identifier. final Comparator identifiersComparator = new Comparator() { public int compare(Representation rep0, Representation rep1) { final boolean bRep0Null = (rep0.getIdentifier() == null); final boolean bRep1Null = (rep1.getIdentifier() == null); if (bRep0Null && bRep1Null) { return 0; } if (bRep0Null) { return -1; } if (bRep1Null) { return 1; } return rep0.getIdentifier().getLastSegment().compareTo( rep1.getIdentifier().getLastSegment()); } }; return identifiersComparator; } /** * Returns the context's target URI (file, clap URI). * * @return The context's target URI (file, clap URI). */ public String getTargetUri() { return this.targetUri; } /** * Returns the representation variants. * * @return The representation variants. */ @Override public List getVariants() { final List results = super.getVariants(); if (!results.isEmpty()) { return results; } getLogger().info("Getting variants for : " + getTargetUri()); if ((this.directoryContent != null) && (getRequest().getResourceRef() != null) && (getRequest().getResourceRef().getBaseRef() != null)) { // Allows to sort the list of representations final SortedSet resultSet = new TreeSet( getRepresentationsComparator()); // Compute the base reference (from a call's client point of view) String baseRef = getRequest().getResourceRef().getBaseRef() .toString(false, false); if (!baseRef.endsWith("/")) { baseRef += "/"; } final int lastIndex = this.relativePart.lastIndexOf("/"); if (lastIndex != -1) { baseRef += this.relativePart.substring(0, lastIndex); } final int rootLength = getDirectoryUri().length(); if (this.baseName != null) { String filePath; for (final Reference ref : getVariantsReferences()) { // Add the new variant to the result list final Response contextResponse = getClientDispatcher().get( ref.toString()); if (contextResponse.getStatus().isSuccess() && (contextResponse.getEntity() != null)) { filePath = ref.toString(false, false).substring( rootLength); final Representation rep = contextResponse.getEntity(); if (filePath.startsWith("/")) { rep.setIdentifier(baseRef + filePath); } else { rep.setIdentifier(baseRef + "/" + filePath); } resultSet.add(rep); } } } results.addAll(resultSet); if (resultSet.isEmpty()) { if (this.directoryTarget && getDirectory().isListingAllowed()) { final ReferenceList userList = new ReferenceList( this.directoryContent.size()); // Set the list identifier userList.setIdentifier(baseRef); final SortedSet sortedSet = new TreeSet( getDirectory().getComparator()); sortedSet.addAll(this.directoryContent); for (final Reference ref : sortedSet) { final String filePart = ref.toString(false, false) .substring(rootLength); final StringBuilder filePath = new StringBuilder(); if ((!baseRef.endsWith("/")) && (!filePart.startsWith("/"))) { filePath.append('/'); } filePath.append(filePart); userList.add(baseRef + filePath); } final List list = getDirectory().getIndexVariants( userList); for (final Variant variant : list) { results.add(getDirectory().getIndexRepresentation( variant, userList)); } } } } else if (this.fileTarget && (this.fileContent != null)) { // Sets the identifier of the target representation. if (getRequest().getOriginalRef() != null) { this.fileContent.setIdentifier(getRequest().getOriginalRef()); } else { this.fileContent.setIdentifier(getRequest().getResourceRef()); } results.add(this.fileContent); } return results; } /** * Returns the references of the representations of the target resource * according to the directory handler property * * @return The list of variants references */ private ReferenceList getVariantsReferences() { this.uniqueReference = null; final ReferenceList result = new ReferenceList(0); try { final Request contextCall = new Request(Method.GET, this.targetUri); // Ask for the list of all variants of this resource contextCall.getClientInfo().getAcceptedMediaTypes().add( new Preference(MediaType.TEXT_URI_LIST)); final Response contextResponse = getClientDispatcher().handle( contextCall); if (contextResponse.getEntity() != null) { // Test if the given response is the list of all variants for // this resource if (MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity() .getMediaType())) { final ReferenceList listVariants = new ReferenceList( contextResponse.getEntity()); Set extensions = null; String entryUri; String fullEntryName; String baseEntryName; int lastSlashIndex; int firstDotIndex; for (final Reference ref : listVariants) { entryUri = ref.toString(); lastSlashIndex = entryUri.lastIndexOf('/'); fullEntryName = (lastSlashIndex == -1) ? entryUri : entryUri.substring(lastSlashIndex + 1); baseEntryName = fullEntryName; // Remove the extensions from the base name firstDotIndex = fullEntryName.indexOf('.'); if (firstDotIndex != -1) { baseEntryName = fullEntryName.substring(0, firstDotIndex); } // Check if the current file is a valid variant if (baseEntryName.equals(this.baseName)) { boolean validVariant = true; // Verify that the extensions are compatible extensions = getExtensions(fullEntryName); validVariant = (((extensions == null) && (this.baseExtensions == null)) || (this.baseExtensions == null) || extensions .containsAll(this.baseExtensions)); if (validVariant && (this.baseExtensions != null) && this.baseExtensions .containsAll(extensions)) { // The unique reference has been found. this.uniqueReference = ref; } if (validVariant) { result.add(ref); } } } } else { result.add(contextResponse.getEntity().getIdentifier()); } } } catch (IOException ioe) { getLogger().log(Level.WARNING, "Unable to get resource variants", ioe); } return result; } @Override public void handleGet() { if (this.directoryRedirection) { // If this request targets a directory and if the target URI does // not end with a trailing "/", the client is told to redirect to a // correct URI. // Restore the cut extensions in case the call has been tunnelled. if (this.originalRef != null) { getResponse().redirectPermanent( this.originalRef.getIdentifier() + "/"); } else { getResponse().redirectPermanent( getRequest().getResourceRef().getIdentifier() + "/"); } } else { super.handleGet(); } } /** * Indicates if the target resource is a directory. * * @return True if the target resource is a directory. */ public boolean isDirectoryTarget() { return this.directoryTarget; } /** * Indicates if the target resource is a file. * * @return True if the target resource is a file. */ public boolean isFileTarget() { return this.fileTarget; } @Override public void removeRepresentations() throws ResourceException { if (this.directoryRedirection) { if (this.originalRef != null) { getResponse().redirectSeeOther( this.originalRef.getIdentifier() + "/"); } else { getResponse().redirectSeeOther( getRequest().getResourceRef().getIdentifier() + "/"); } } else { final Request contextRequest = new Request(Method.DELETE, this.targetUri); final Response contextResponse = new Response(contextRequest); if (this.directoryTarget && !this.indexTarget) { contextRequest.setResourceRef(this.targetUri); getClientDispatcher().handle(contextRequest, contextResponse); } else { // Check if there is only one representation // Try to get the unique representation of the resource final ReferenceList references = getVariantsReferences(); if (!references.isEmpty()) { if (this.uniqueReference != null) { contextRequest.setResourceRef(this.uniqueReference); getClientDispatcher().handle(contextRequest, contextResponse); } else { // We found variants, but not the right one contextResponse .setStatus(new Status( Status.CLIENT_ERROR_NOT_ACCEPTABLE, "Unable to process properly the request. Several variants exist but none of them suits precisely. ")); } } else { contextResponse.setStatus(Status.CLIENT_ERROR_NOT_FOUND); } } getResponse().setStatus(contextResponse.getStatus()); } } /** * Sets the context's target URI (file, clap URI). * * @param targetUri * The context's target URI. */ public void setTargetUri(String targetUri) { this.targetUri = targetUri; } @Override public void storeRepresentation(Representation entity) throws ResourceException { if (this.directoryRedirection) { if (this.originalRef != null) { getResponse().redirectSeeOther( this.originalRef.getIdentifier() + "/"); } else { getResponse().redirectSeeOther( getRequest().getResourceRef().getIdentifier() + "/"); } } else { // Transfer of PUT calls is only allowed if the readOnly flag is not // set. final Request contextRequest = new Request(Method.PUT, this.targetUri); // Add support of partial PUT calls. contextRequest.getRanges().addAll(getRequest().getRanges()); contextRequest.setEntity(entity); final Response contextResponse = new Response(contextRequest); contextRequest.setResourceRef(this.targetUri); getClientDispatcher().handle(contextRequest, contextResponse); getResponse().setStatus(contextResponse.getStatus()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy