Java tutorial
/* * 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.jcr.resource.internal; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Dictionary; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; import javax.jcr.Session; import org.apache.commons.collections.BidiMap; import org.apache.commons.collections.bidimap.TreeBidiMap; import org.apache.sling.api.resource.ResourceProvider; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.commons.osgi.OsgiUtil; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.jcr.resource.JcrResourceResolverFactory; import org.apache.sling.jcr.resource.JcrResourceTypeProvider; import org.apache.sling.jcr.resource.internal.helper.MapEntries; import org.apache.sling.jcr.resource.internal.helper.Mapping; import org.apache.sling.jcr.resource.internal.helper.ResourceProviderEntry; import org.apache.sling.jcr.resource.internal.helper.ResourceProviderEntryException; import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderEntry; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The <code>JcrResourceResolverFactoryImpl</code> is the * {@link JcrResourceResolverFactory} service providing the following * functionality: * <ul> * <li><code>JcrResourceResolverFactory</code> service * <li>Bundle listener to load initial content and manage OCM mapping * descriptors provided by bundles. * <li>Fires OSGi EventAdmin events on behalf of internal helper objects * </ul> * * @scr.component immediate="true" label="%resource.resolver.name" * description="%resource.resolver.description" * @scr.property name="service.description" * value="Sling JcrResourceResolverFactory Implementation" * @scr.property name="service.vendor" value="The Apache Software Foundation" * @scr.service interface="org.apache.sling.jcr.resource.JcrResourceResolverFactory" * @scr.reference name="ResourceProvider" * interface="org.apache.sling.api.resource.ResourceProvider" * cardinality="0..n" policy="dynamic" * @scr.reference name="JcrResourceTypeProvider" * interface="org.apache.sling.jcr.resource.JcrResourceTypeProvider" * cardinality="0..n" policy="dynamic" */ public class JcrResourceResolverFactoryImpl implements JcrResourceResolverFactory { public final static class ResourcePattern { public final Pattern pattern; public final String replacement; public ResourcePattern(final Pattern p, final String r) { this.pattern = p; this.replacement = r; } } /** * @scr.property values.1="/apps" values.2="/libs" */ public static final String PROP_PATH = "resource.resolver.searchpath"; /** * Defines whether namespace prefixes of resource names inside the path * (e.g. <code>jcr:</code> in <code>/home/path/jcr:content</code>) are * mangled or not. * <p> * Mangling means that any namespace prefix contained in the path is replaced * as per the generic substitution pattern <code>/([^:]+):/_$1_/</code> * when calling the <code>map</code> method of the resource resolver. * Likewise the <code>resolve</code> methods will unmangle such namespace * prefixes according to the substituation pattern * <code>/_([^_]+)_/$1:/</code>. * <p> * This feature is provided since there may be systems out there in the wild * which cannot cope with URLs containing colons, even though they are * perfectly valid characters in the path part of URI references with a * scheme. * <p> * The default value of this property if no configuration is provided is * <code>true</code>. * * @scr.property value="true" type="Boolean" */ private static final String PROP_MANGLE_NAMESPACES = "resource.resolver.manglenamespaces"; /** * @scr.property value="true" type="Boolean" */ private static final String PROP_ALLOW_DIRECT = "resource.resolver.allowDirect"; /** * The resolver.virtual property has no default configuration. But the sling * maven plugin and the sling management console cannot handle empty * multivalue properties at the moment. So we just add a dummy direct * mapping. * * @scr.property values.1="/-/" */ private static final String PROP_VIRTUAL = "resource.resolver.virtual"; /** * @scr.property values.1="/-/" values.2="/content/-/" * Cvalues.3="/apps/×/docroot/-/" * Cvalues.4="/libs/×/docroot/-/" * values.5="/system/docroot/-/" */ private static final String PROP_MAPPING = "resource.resolver.mapping"; /** default log */ private final Logger log = LoggerFactory.getLogger(getClass()); /** * The JCR Repository we access to resolve resources * * @scr.reference */ private SlingRepository repository; /** * The (optional) resource type providers. */ protected final List<JcrResourceTypeProviderEntry> jcrResourceTypeProviders = new ArrayList<JcrResourceTypeProviderEntry>(); /** * List of ResourceProvider services bound before activation of the * component. */ private final List<ServiceReference> delayedResourceProviders = new LinkedList<ServiceReference>(); /** * List of JcrResourceTypeProvider services bound before activation of the * component. */ protected List<ServiceReference> delayedJcrResourceTypeProviders = new LinkedList<ServiceReference>(); protected ComponentContext componentContext; // helper for the new JcrResourceResolver2 private MapEntries mapEntries = MapEntries.EMPTY; /** all mappings */ private Mapping[] mappings; /** The fake urls */ private BidiMap virtualURLMap; /** <code>true</code>, if direct mappings from URI to handle are allowed */ private boolean allowDirect = false; // the search path for ResourceResolver.getResource(String) private String[] searchPath; private ResourceProviderEntry rootProviderEntry; // whether to mangle paths with namespaces or not private boolean mangleNamespacePrefixes; public JcrResourceResolverFactoryImpl() { this.rootProviderEntry = new ResourceProviderEntry("/", null, null); } // ---------- JcrResourceResolverFactory ----------------------------------- /** * Returns a new <code>ResourceResolve</code> for the given session. Note * that each call to this method returns a new resource manager instance. */ public ResourceResolver getResourceResolver(Session session) { JcrResourceProviderEntry sessionRoot = new JcrResourceProviderEntry(session, rootProviderEntry, getJcrResourceTypeProviders()); return new JcrResourceResolver2(sessionRoot, this, mapEntries); } protected JcrResourceTypeProvider[] getJcrResourceTypeProviders() { JcrResourceTypeProvider[] providers = null; synchronized (this.jcrResourceTypeProviders) { if (this.jcrResourceTypeProviders.size() > 0) { providers = new JcrResourceTypeProvider[this.jcrResourceTypeProviders.size()]; int index = 0; final Iterator<JcrResourceTypeProviderEntry> i = this.jcrResourceTypeProviders.iterator(); while (i.hasNext()) { providers[index] = i.next().provider; } } } return providers; } // ---------- Implementation helpers -------------------------------------- /** If uri is a virtual URI returns the real URI, otherwise returns null */ String virtualToRealUri(String virtualUri) { return (virtualURLMap != null) ? (String) virtualURLMap.get(virtualUri) : null; } /** * If uri is a real URI for any virtual URI, the virtual URI is returned, * otherwise returns null */ String realToVirtualUri(String realUri) { return (virtualURLMap != null) ? (String) virtualURLMap.getKey(realUri) : null; } public BidiMap getVirtualURLMap() { return virtualURLMap; } public Mapping[] getMappings() { return mappings; } String[] getSearchPath() { return searchPath; } boolean isMangleNamespacePrefixes() { return mangleNamespacePrefixes; } MapEntries getMapEntries() { return mapEntries; } /** * Getter for rootProviderEntry, making it easier to extend * JcrResourceResolverFactoryImpl. See <a * href="https://issues.apache.org/jira/browse/SLING-730">SLING-730</a> * * @return Our rootProviderEntry */ protected ResourceProviderEntry getRootProviderEntry() { return rootProviderEntry; } // ---------- SCR Integration --------------------------------------------- /** Activates this component, called by SCR before registering as a service */ protected void activate(ComponentContext componentContext) { this.componentContext = componentContext; Dictionary<?, ?> properties = componentContext.getProperties(); BidiMap virtuals = new TreeBidiMap(); String[] virtualList = (String[]) properties.get(PROP_VIRTUAL); for (int i = 0; virtualList != null && i < virtualList.length; i++) { String[] parts = Mapping.split(virtualList[i]); virtuals.put(parts[0], parts[2]); } virtualURLMap = virtuals; List<Mapping> maps = new ArrayList<Mapping>(); String[] mappingList = (String[]) properties.get(PROP_MAPPING); for (int i = 0; mappingList != null && i < mappingList.length; i++) { maps.add(new Mapping(mappingList[i])); } Mapping[] tmp = maps.toArray(new Mapping[maps.size()]); // check whether direct mappings are allowed Boolean directProp = (Boolean) properties.get(PROP_ALLOW_DIRECT); allowDirect = (directProp != null) ? directProp.booleanValue() : true; if (allowDirect) { Mapping[] tmp2 = new Mapping[tmp.length + 1]; tmp2[0] = Mapping.DIRECT; System.arraycopy(tmp, 0, tmp2, 1, tmp.length); mappings = tmp2; } else { mappings = tmp; } // from configuration if available searchPath = OsgiUtil.toStringArray(properties.get(PROP_PATH)); if (searchPath != null && searchPath.length > 0) { for (int i = 0; i < searchPath.length; i++) { // ensure leading slash if (!searchPath[i].startsWith("/")) { searchPath[i] = "/" + searchPath[i]; } // ensure trailing slash if (!searchPath[i].endsWith("/")) { searchPath[i] += "/"; } } } if (searchPath == null) { searchPath = new String[] { "/" }; } // namespace mangling mangleNamespacePrefixes = OsgiUtil.toBoolean(properties.get(PROP_MANGLE_NAMESPACES), false); // bind resource providers not bound yet for (ServiceReference reference : delayedResourceProviders) { bindResourceProvider(reference); } delayedResourceProviders.clear(); this.processDelayedJcrResourceTypeProviders(); // set up the map entries from configuration try { mapEntries = new MapEntries(this, getRepository()); plugin = new JcrResourceResolverWebConsolePlugin(componentContext.getBundleContext(), this); } catch (Exception e) { log.error("activate: Cannot access repository, failed setting up Mapping Support", e); } } private JcrResourceResolverWebConsolePlugin plugin; /** Deativates this component, called by SCR to take out of service */ protected void deactivate(ComponentContext componentContext) { if (plugin != null) { plugin.dispose(); plugin = null; } if (mapEntries != null) { mapEntries.dispose(); mapEntries = MapEntries.EMPTY; } this.componentContext = null; } private ResourcePattern[] getResourcePatterns(String[] patternList) { // regexps List<ResourcePattern> patterns = new ArrayList<ResourcePattern>(); if (patternList != null) { for (final String p : patternList) { int pos = p.lastIndexOf('|'); if (pos == -1) { log.error("Invalid regexp: {}", p); } else { final String replString = p.substring(pos + 1); final Pattern pat = Pattern.compile(p.substring(0, pos)); patterns.add(new ResourcePattern(pat, replString)); } } } return patterns.toArray(new ResourcePattern[patterns.size()]); } protected void processDelayedJcrResourceTypeProviders() { synchronized (this.jcrResourceTypeProviders) { for (ServiceReference reference : delayedJcrResourceTypeProviders) { this.addJcrResourceTypeProvider(reference); } delayedJcrResourceTypeProviders.clear(); } } protected void addJcrResourceTypeProvider(final ServiceReference reference) { final Long id = (Long) reference.getProperty(Constants.SERVICE_ID); long ranking = -1; if (reference.getProperty(Constants.SERVICE_RANKING) != null) { ranking = (Long) reference.getProperty(Constants.SERVICE_RANKING); } this.jcrResourceTypeProviders .add(new JcrResourceTypeProviderEntry(id, ranking, (JcrResourceTypeProvider) this.componentContext .locateService("JcrResourceTypeProvider", reference))); Collections.sort(this.jcrResourceTypeProviders, new Comparator<JcrResourceTypeProviderEntry>() { public int compare(JcrResourceTypeProviderEntry o1, JcrResourceTypeProviderEntry o2) { if (o1.ranking < o2.ranking) { return 1; } else if (o1.ranking > o2.ranking) { return -1; } else { if (o1.serviceId < o2.serviceId) { return -1; } else if (o1.serviceId > o2.serviceId) { return 1; } } return 0; } }); } protected void bindResourceProvider(ServiceReference reference) { String serviceName = getServiceName(reference); if (componentContext == null) { log.debug("bindResourceProvider: Delaying {}", serviceName); // delay binding resource providers if called before activation delayedResourceProviders.add(reference); } else { log.debug("bindResourceProvider: Binding {}", serviceName); String[] roots = OsgiUtil.toStringArray(reference.getProperty(ResourceProvider.ROOTS)); if (roots != null && roots.length > 0) { ResourceProvider provider = (ResourceProvider) componentContext.locateService("ResourceProvider", reference); // synchronized insertion of new resource providers into // the tree to not inadvertandly loose an entry synchronized (this) { for (String root : roots) { // cut off trailing slash if (root.endsWith("/") && root.length() > 1) { root = root.substring(0, root.length() - 1); } try { rootProviderEntry.addResourceProvider(root, provider); log.debug("bindResourceProvider: {}={} ({})", new Object[] { root, provider, serviceName }); } catch (ResourceProviderEntryException rpee) { log.error( "bindResourceProvider: Cannot register ResourceProvider {} for {}: ResourceProvider {} is already registered", new Object[] { provider, root, rpee.getExisting().getResourceProvider() }); } } } } log.debug("bindResourceProvider: Bound {}", serviceName); } } protected void unbindResourceProvider(ServiceReference reference) { String serviceName = getServiceName(reference); log.debug("unbindResourceProvider: Unbinding {}", serviceName); String[] roots = OsgiUtil.toStringArray(reference.getProperty(ResourceProvider.ROOTS)); if (roots != null && roots.length > 0) { // synchronized insertion of new resource providers into // the tree to not inadvertandly loose an entry synchronized (this) { for (String root : roots) { // cut off trailing slash if (root.endsWith("/") && root.length() > 1) { root = root.substring(0, root.length() - 1); } // TODO: Do not remove this path, if another resource // owns it. This may be the case if adding the provider // yielded an ResourceProviderEntryException rootProviderEntry.removeResourceProvider(root); log.debug("unbindResourceProvider: root={} ({})", root, serviceName); } } } log.debug("unbindResourceProvider: Unbound {}", serviceName); } protected void bindJcrResourceTypeProvider(ServiceReference reference) { synchronized (this.jcrResourceTypeProviders) { if (componentContext == null) { delayedJcrResourceTypeProviders.add(reference); } else { this.addJcrResourceTypeProvider(reference); } } } protected void unbindJcrResourceTypeProvider(ServiceReference reference) { synchronized (this.jcrResourceTypeProviders) { delayedJcrResourceTypeProviders.remove(reference); final long id = (Long) reference.getProperty(Constants.SERVICE_ID); final Iterator<JcrResourceTypeProviderEntry> i = this.jcrResourceTypeProviders.iterator(); while (i.hasNext()) { final JcrResourceTypeProviderEntry current = i.next(); if (current.serviceId == id) { i.remove(); } } } } // ---------- internal helper ---------------------------------------------- /** Returns the JCR repository used by this factory */ protected SlingRepository getRepository() { return repository; } protected static final class JcrResourceTypeProviderEntry { final long serviceId; final long ranking; final JcrResourceTypeProvider provider; public JcrResourceTypeProviderEntry(final long id, final long ranking, final JcrResourceTypeProvider p) { this.serviceId = id; this.ranking = ranking; this.provider = p; } } private String getServiceName(ServiceReference reference) { if (log.isDebugEnabled()) { StringBuilder snBuilder = new StringBuilder(64); snBuilder.append('{'); snBuilder.append(reference.toString()); snBuilder.append('/'); snBuilder.append(reference.getProperty(Constants.SERVICE_ID)); snBuilder.append('}'); return snBuilder.toString(); } return null; } }