com.noelios.restlet.local.DirectoryResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.servicemix.bundles.restlet
Show all versions of org.apache.servicemix.bundles.restlet
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