| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.fsprovider.internal.mapper; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Calendar; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.jcr.Node; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.builder.ToStringBuilder; |
| import org.apache.commons.lang3.builder.ToStringStyle; |
| import org.apache.sling.adapter.annotations.Adaptable; |
| import org.apache.sling.adapter.annotations.Adapter; |
| import org.apache.sling.api.resource.AbstractResource; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceMetadata; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.resource.ValueMap; |
| import org.apache.sling.api.wrappers.DeepReadValueMapDecorator; |
| import org.apache.sling.contentparser.api.ParserOptions; |
| import org.apache.sling.fsprovider.internal.ContentFileExtensions; |
| import org.apache.sling.fsprovider.internal.FileStatCache; |
| import org.apache.sling.fsprovider.internal.FsResourceProvider; |
| import org.apache.sling.fsprovider.internal.mapper.jcr.FsNode; |
| import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapDecorator; |
| import org.apache.sling.fsprovider.internal.parser.ContentFileCache; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The <code>FsResource</code> represents a file system file or folder as |
| * a Sling Resource. |
| */ |
| @Adaptable(adaptableClass=Resource.class, adapters={ |
| @Adapter({File.class, URL.class}), |
| @Adapter(condition="If the resource is an FsResource and is a readable file.", value=InputStream.class) |
| }) |
| public final class FileResource extends AbstractResource { |
| |
| /** |
| * The resource type for file system files mapped into the resource tree by |
| * the {@link FsResourceProvider} (value is "nt:file"). |
| */ |
| static final String RESOURCE_TYPE_FILE = "nt:file"; |
| |
| /** |
| * The resource type for file system folders mapped into the resource tree |
| * by the {@link FsResourceProvider} (value is "nt:folder"). |
| */ |
| static final String RESOURCE_TYPE_FOLDER = "nt:folder"; |
| |
| // the owning resource resolver |
| private final ResourceResolver resolver; |
| |
| // the path of this resource in the resource tree |
| private final String resourcePath; |
| |
| // the file wrapped by this instance |
| private final File file; |
| |
| // the resource metadata, assigned on demand |
| private ResourceMetadata metaData; |
| |
| // the resource type, assigned on demand |
| private String resourceType; |
| private String resourceSuperType; |
| |
| // valuemap is build on demand |
| private ValueMap valueMap; |
| |
| private final ContentFileExtensions contentFileExtensions; |
| private final ContentFileCache contentFileCache; |
| private final FileStatCache fileStatCache; |
| |
| private static final Logger log = LoggerFactory.getLogger(FileResource.class); |
| |
| /** |
| * Creates an instance of this File system resource. |
| * |
| * @param resolver The owning resource resolver |
| * @param resourcePath The resource path in the resource tree |
| * @param file The wrapped file |
| */ |
| FileResource(ResourceResolver resolver, String resourcePath, File file, FileStatCache fileStatCache) { |
| this(resolver, resourcePath, file, null, null, fileStatCache); |
| } |
| |
| FileResource(ResourceResolver resolver, String resourcePath, File file, |
| ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache, |
| FileStatCache fileStatCache) { |
| this.resolver = resolver; |
| this.resourcePath = resourcePath; |
| this.file = file; |
| this.contentFileExtensions = contentFileExtensions; |
| this.contentFileCache = contentFileCache; |
| this.fileStatCache = fileStatCache; |
| } |
| |
| /** |
| * Returns the path of this resource |
| */ |
| public @NotNull String getPath() { |
| return resourcePath; |
| } |
| |
| /** |
| * Returns the resource meta data for this resource containing the file |
| * length, last modification time and the resource path (same as |
| * {@link #getPath()}). |
| */ |
| public @NotNull ResourceMetadata getResourceMetadata() { |
| if (metaData == null) { |
| metaData = new LazyModifiedDateResourceMetadata(file); |
| metaData.setContentLength(file.length()); |
| metaData.setResolutionPath(resourcePath); |
| if (fileStatCache.isDirectory(file)) { |
| metaData.put(FsResourceProvider.RESOURCE_METADATA_FILE_DIRECTORY, Boolean.TRUE); |
| } |
| } |
| return metaData; |
| } |
| |
| /** |
| * Returns the resource resolver which cause this resource object to be |
| * created. |
| */ |
| public @NotNull ResourceResolver getResourceResolver() { |
| return resolver; |
| } |
| |
| public String getResourceSuperType() { |
| if (resourceSuperType == null) { |
| resourceSuperType = getValueMap().get("sling:resourceSuperType", String.class); |
| } |
| return resourceSuperType; |
| } |
| |
| public @NotNull String getResourceType() { |
| if (resourceType == null) { |
| ValueMap props = getValueMap(); |
| resourceType = props.get("sling:resourceType", String.class); |
| if (resourceType == null) { |
| // fallback to jcr:primaryType when resource type not set |
| resourceType = props.get("jcr:primaryType", String.class); |
| } |
| } |
| return resourceType; |
| } |
| |
| /** |
| * Returns an adapter for this resource. This implementation supports |
| * <code>File</code>, <code>InputStream</code> and <code>URL</code> |
| * plus those supported by the adapter manager. |
| */ |
| @Override |
| @SuppressWarnings({ "unchecked", "null" }) |
| public @Nullable <AdapterType> AdapterType adaptTo(@NotNull Class<AdapterType> type) { |
| if (type == File.class) { |
| return (AdapterType) file; |
| } |
| else if (type == InputStream.class) { |
| if (fileStatCache.isFile(file) && file.canRead()) { |
| try { |
| return (AdapterType) new FileInputStream(file); |
| } |
| catch (IOException ioe) { |
| log.info("adaptTo: Cannot open a stream on the file " + file, ioe); |
| } |
| } |
| else { |
| log.debug("adaptTo: File {} is not a readable file", file); |
| } |
| } |
| else if (type == URL.class) { |
| try { |
| return (AdapterType) file.toURI().toURL(); |
| } |
| catch (MalformedURLException mue) { |
| log.info("adaptTo: Cannot convert the file path " + file + " to an URL", mue); |
| } |
| } |
| else if (type == ValueMap.class) { |
| return (AdapterType) getValueMap(); |
| } |
| else if (type == Node.class) { |
| ContentFile contentFile = getNodeDescriptorContentFile(); |
| if (contentFile != null) { |
| // support a subset of JCR API for content file resources |
| return (AdapterType)new FsNode(contentFile, getResourceResolver()); |
| } |
| } |
| return super.adaptTo(type); |
| } |
| |
| @Override |
| public String toString() { |
| return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) |
| .append("path", resourcePath) |
| .append("file", file.getPath()) |
| .append("resourceType", getResourceType()) |
| .build(); |
| } |
| |
| @Override |
| public ValueMap getValueMap() { |
| if (valueMap == null) { |
| // this resource simulates nt:file/nt:folder behavior by returning it as resource type |
| // we should simulate the corresponding JCR properties in a value map as well |
| if (fileStatCache.exists(file) && file.canRead()) { |
| Map<String,Object> props = new HashMap<String, Object>(); |
| props.put("jcr:primaryType", fileStatCache.isFile(file) ? RESOURCE_TYPE_FILE : RESOURCE_TYPE_FOLDER); |
| props.put("jcr:createdBy", "system"); |
| |
| Calendar lastModifed = Calendar.getInstance(); |
| lastModifed.setTimeInMillis(file.lastModified()); |
| props.put("jcr:created", lastModifed); |
| |
| // overlay properties with those from node descriptor content file, if it exists |
| ContentFile contentFile = getNodeDescriptorContentFile(); |
| if (contentFile != null) { |
| for (Map.Entry<String, Object> entry : contentFile.getValueMap().entrySet()) { |
| // skip primary type if it is the default type assigned by contentparser when none is defined |
| if (StringUtils.equals(entry.getKey(), "jcr:primaryType") |
| && StringUtils.equals((String)entry.getValue(), ParserOptions.DEFAULT_PRIMARY_TYPE)) { |
| continue; |
| } |
| props.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| valueMap = new DeepReadValueMapDecorator(this, new ValueMapDecorator(props)); |
| } |
| } |
| return valueMap; |
| } |
| |
| private ContentFile getNodeDescriptorContentFile() { |
| if (contentFileExtensions == null || contentFileCache == null) { |
| return null; |
| } |
| for (String fileNameSuffix : contentFileExtensions.getSuffixes()) { |
| File fileWithSuffix = new File(file.getPath() + fileNameSuffix); |
| if (fileStatCache.exists(fileWithSuffix) && fileWithSuffix.canRead()) { |
| return new ContentFile(fileWithSuffix, resourcePath, null, contentFileCache); |
| } |
| } |
| return null; |
| } |
| |
| } |