blob: 7751c2930ad3475b84779b1d7c510396f4884582 [file] [log] [blame]
/*
* 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;
}
}