| /* |
| * 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.superimposing.impl; |
| |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.resource.ResourceWrapper; |
| import org.apache.sling.superimposing.SuperimposingResourceProvider; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Superimposing resource provider. |
| * Maps a single source path to the target root path, with or without overlay depending on configuration. |
| */ |
| public class SuperimposingResourceProviderImpl implements SuperimposingResourceProvider { |
| |
| private static final Logger log = LoggerFactory.getLogger(SuperimposingResourceProviderImpl.class); |
| |
| private final String rootPath; |
| private final String rootPrefix; |
| private final String sourcePath; |
| private final String sourcePathPrefix; |
| private final boolean overlayable; |
| private final String toString; |
| private ServiceRegistration registration; |
| |
| SuperimposingResourceProviderImpl(String rootPath, String sourcePath, boolean overlayable) { |
| this.rootPath = rootPath; |
| this.rootPrefix = rootPath.concat("/"); |
| this.sourcePath = sourcePath; |
| this.sourcePathPrefix = sourcePath.concat("/"); |
| this.overlayable = overlayable; |
| StringBuilder sb = new StringBuilder(getClass().getSimpleName()); |
| sb.append(" [path=").append(rootPath).append(", "); |
| sb.append("sourcePath=").append(sourcePath).append(", "); |
| sb.append("overlayable=").append(overlayable).append("]"); |
| this.toString = sb.toString(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Resource getResource(ResourceResolver resolver, HttpServletRequest httpServletRequest, String path) { |
| return getResource(resolver, path); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Resource getResource(ResourceResolver resolver, String path) { |
| final String mappedPath = mapPath(this, resolver, path); |
| if (null != mappedPath) { |
| // the existing resource where the superimposed content is retrieved from |
| final Resource mappedResource = resolver.getResource(mappedPath); |
| if (null != mappedResource) { |
| return new SuperimposingResource(mappedResource, path); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Iterator<Resource> listChildren(Resource resource) { |
| |
| // unwrap resource if it is a wrapped resource |
| final Resource currentResource; |
| if (resource instanceof ResourceWrapper) { |
| currentResource = ((ResourceWrapper)resource).getResource(); |
| } |
| else { |
| currentResource = resource; |
| } |
| |
| // delegate resource listing to resource resolver |
| if (currentResource instanceof SuperimposingResource) { |
| final SuperimposingResource res = (SuperimposingResource) currentResource; |
| final ResourceResolver resolver = res.getResource().getResourceResolver(); |
| final Iterator<Resource> children = resolver.listChildren(res.getResource()); |
| return new SuperimposingResourceIterator(this, children); |
| } |
| return null; |
| } |
| |
| /** |
| * Maps a path below the superimposing root to the target resource's path. |
| * @param provider Superimposing resource provider |
| * @param resolver Resource resolver |
| * @param path Path to map |
| * @return Mapped path or null if no mapping available |
| */ |
| static String mapPath(SuperimposingResourceProviderImpl provider, ResourceResolver resolver, String path) { |
| if (provider.overlayable) { |
| return mapPathWithOverlay(provider, resolver, path); |
| } |
| else { |
| return mapPathWithoutOverlay(provider, resolver, path); |
| } |
| } |
| |
| /** |
| * Maps a path below the superimposing root to the target resource's path with check for overlaying. |
| * @param provider Superimposing resource provider |
| * @param resolver Resource resolver |
| * @param path Path to map |
| * @return Mapped path or null if no mapping available |
| */ |
| static String mapPathWithOverlay(SuperimposingResourceProviderImpl provider, ResourceResolver resolver, String path) { |
| if (StringUtils.equals(path, provider.rootPath)) { |
| // Superimposing root path cannot be overlayed |
| return mapPathWithoutOverlay(provider, resolver, path); |
| } |
| else if (StringUtils.startsWith(path, provider.rootPrefix)) { |
| if (hasOverlayResource(resolver, path)) { |
| // overlay item exists, allow underlying resource provider to step in |
| return null; |
| } |
| else { |
| // overlay item does not exist, overlay cannot be applied, fallback to mapped path without overlay |
| return mapPathWithoutOverlay(provider, resolver, path); |
| } |
| } |
| return null; |
| } |
| |
| static boolean hasOverlayResource(ResourceResolver resolver, String path) { |
| // check for overlay resource by checking directly in underlying JCR |
| final Session session = resolver.adaptTo(Session.class); |
| try { |
| return (null != session && session.itemExists(path)); |
| } catch (RepositoryException e) { |
| log.error("Error accessing the repository. ", e); |
| } |
| return false; |
| } |
| |
| /** |
| * Maps a path below the superimposing root to the target resource's path without check for overlaying. |
| * @param provider Superimposing resource provider |
| * @param resolver Resource resolver |
| * @param path Path to map |
| * @return Mapped path or null if no mapping available |
| */ |
| static String mapPathWithoutOverlay(SuperimposingResourceProviderImpl provider, ResourceResolver resolver, String path) { |
| final String mappedPath; |
| if (StringUtils.equals(path, provider.rootPath)) { |
| mappedPath = provider.sourcePath; |
| } else if (StringUtils.startsWith(path, provider.rootPrefix)) { |
| mappedPath = StringUtils.replaceOnce(path, provider.rootPrefix, provider.sourcePathPrefix); |
| } else { |
| mappedPath = null; |
| } |
| return mappedPath; |
| } |
| |
| /** |
| * Maps a path below the target resource to the superimposed resource's path. |
| * |
| * @param provider |
| * @param path |
| * @return |
| */ |
| static String reverseMapPath(SuperimposingResourceProviderImpl provider, String path) { |
| final String mappedPath; |
| if (path.startsWith(provider.sourcePathPrefix)) { |
| mappedPath = StringUtils.replaceOnce(path, provider.sourcePathPrefix, provider.rootPrefix); |
| } else if (path.equals(provider.sourcePath)) { |
| mappedPath = provider.rootPath; |
| } else { |
| mappedPath = null; |
| } |
| return mappedPath; |
| } |
| |
| //---------- Service Registration |
| |
| void registerService(BundleContext context) { |
| final Dictionary<String, Object> props = new Hashtable<String, Object>(); |
| props.put(Constants.SERVICE_DESCRIPTION, "Provider of superimposed resources"); |
| props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation"); |
| props.put(ROOTS, new String[]{rootPath}); |
| |
| registration = context.registerService(SERVICE_NAME, this, props); |
| |
| log.info("Registered {}", this); |
| } |
| |
| void unregisterService() { |
| if (registration != null) { |
| registration.unregister(); |
| registration = null; |
| log.info("Unregistered {}", this); |
| } |
| } |
| |
| /** |
| * @return Root path (source path) |
| */ |
| public String getRootPath() { |
| return rootPath; |
| } |
| |
| /** |
| * @return Target path (destination path) |
| */ |
| public String getSourcePath() { |
| return sourcePath; |
| } |
| |
| /** |
| * @return Overlayable yes/no |
| */ |
| public boolean isOverlayable() { |
| return overlayable; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof SuperimposingResourceProviderImpl) { |
| final SuperimposingResourceProviderImpl srp = (SuperimposingResourceProviderImpl)o; |
| return this.rootPath.equals(srp.rootPath) && this.sourcePath.equals(srp.sourcePath) && this.overlayable == srp.overlayable; |
| |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return toString; |
| } |
| } |