Return-Path: X-Original-To: apmail-sling-commits-archive@www.apache.org Delivered-To: apmail-sling-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id A78E994DB for ; Thu, 2 Feb 2012 12:47:41 +0000 (UTC) Received: (qmail 83011 invoked by uid 500); 2 Feb 2012 12:47:41 -0000 Delivered-To: apmail-sling-commits-archive@sling.apache.org Received: (qmail 82932 invoked by uid 500); 2 Feb 2012 12:47:41 -0000 Mailing-List: contact commits-help@sling.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@sling.apache.org Delivered-To: mailing list commits@sling.apache.org Received: (qmail 82925 invoked by uid 99); 2 Feb 2012 12:47:41 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 02 Feb 2012 12:47:41 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 02 Feb 2012 12:47:26 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 6413A2388A40; Thu, 2 Feb 2012 12:47:04 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1239587 [2/9] - in /sling/whiteboard/resourceresolverfactory/jcr-resource: ./ src/ src/main/ src/main/appended-resources/ src/main/appended-resources/META-INF/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/a... Date: Thu, 02 Feb 2012 12:47:01 -0000 To: commits@sling.apache.org From: vramdal@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120202124704.6413A2388A40@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java?rev=1239587&view=auto ============================================================================== --- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java (added) +++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceListener.java Thu Feb 2 12:46:58 2012 @@ -0,0 +1,407 @@ +/* + * 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.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; + +import org.apache.jackrabbit.api.observation.JackrabbitEvent; +import org.apache.sling.api.SlingConstants; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.jcr.resource.JcrResourceConstants; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.event.EventConstants; +import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The JcrResourceListener listens for JCR observation + * events and creates resource events which are sent through the + * OSGi event admin. + */ +public class JcrResourceListener implements EventListener { + + /** Logger */ + private final Logger logger = LoggerFactory.getLogger(JcrResourceListener.class); + + /** The workspace for observation. */ + private final String workspaceName; + + /** The session for observation. */ + private final Session session; + + /** Everything below this path is observed. */ + private final String startPath; + + /** The repository is mounted under this path. */ + private final String mountPrefix; + + /** The resource resolver. */ + private final ResourceResolver resolver; + + /** The event admin tracker. */ + private final ServiceTracker eventAdminTracker; + + /** Is the Jackrabbit event class available? */ + private final boolean hasJackrabbitEventClass; + + /** + * A queue of OSGi Events created by + * {@link #sendOsgiEvent(String, Event, String, EventAdmin, ChangedAttributes)} + * waiting for actual dispatching to the OSGi Event Admin in + * {@link #processOsgiEventQueue()} + */ + private final LinkedBlockingQueue> osgiEventQueue; + + /** + * Marker event for {@link #processOsgiEventQueue()} to be signaled to + * terminate processing Events. + */ + private final Dictionary TERMINATE_PROCESSING = new Hashtable(1); + + /** + * Constructor. + * @param workspaceName The workspace name to observe + * @param factory The resource resolver factory. + * @param startPath The observation root path + * @param mountPrefix The mount path in the repository + * @param eventAdminTracker The service tracker for the event admin. + * @throws RepositoryException + */ + public JcrResourceListener(final String workspaceName, + final ResourceResolverFactory factory, + final String startPath, + final String mountPrefix, + final ServiceTracker eventAdminTracker) + throws LoginException, RepositoryException { + this.workspaceName = workspaceName; + final Map authInfo = new HashMap(); + if (workspaceName != null) { + authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE, + workspaceName); + } + this.resolver = factory.getAdministrativeResourceResolver(authInfo); + this.session = resolver.adaptTo(Session.class); + this.startPath = startPath; + this.eventAdminTracker = eventAdminTracker; + this.mountPrefix = (mountPrefix.equals("/") ? null : mountPrefix); + + this.osgiEventQueue = new LinkedBlockingQueue>(); + Thread oeqt = new Thread(new Runnable() { + public void run() { + processOsgiEventQueue(); + } + }, "JCR Resource Event Queue Processor"); + oeqt.start(); + + this.session.getWorkspace().getObservationManager().addEventListener(this, + Event.NODE_ADDED|Event.NODE_REMOVED|Event.PROPERTY_ADDED|Event.PROPERTY_CHANGED|Event.PROPERTY_REMOVED, + this.startPath, true, null, null, false); + boolean foundClass = false; + try { + this.getClass().getClassLoader().loadClass(JackrabbitEvent.class.getName()); + foundClass = true; + } catch (final Throwable t) { + // we ignore this + } + this.hasJackrabbitEventClass = foundClass; + } + + /** + * Dispose this listener. + */ + public void dispose() { + + // unregister from observations + try { + this.session.getWorkspace().getObservationManager().removeEventListener(this); + } catch (RepositoryException e) { + logger.warn("Unable to remove session listener: " + this, e); + } + this.resolver.close(); + + // drop any remaining OSGi Events not processed yet + this.osgiEventQueue.clear(); + this.osgiEventQueue.offer(TERMINATE_PROCESSING); + } + + /** + * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) + */ + public void onEvent(EventIterator events) { + // if the event admin is currently not available, we just skip this + final EventAdmin localEA = (EventAdmin) this.eventAdminTracker.getService(); + if ( localEA == null ) { + return; + } + final Map addedEvents = new HashMap(); + final Map changedEvents = new HashMap(); + final Map removedEvents = new HashMap(); + while ( events.hasNext() ) { + final Event event = events.nextEvent(); + try { + final String eventPath; + if ( this.mountPrefix != null ) { + eventPath = this.mountPrefix + event.getPath(); + } else { + eventPath = event.getPath(); + } + if ( event.getType() == Event.PROPERTY_ADDED + || event.getType() == Event.PROPERTY_REMOVED + || event.getType() == Event.PROPERTY_CHANGED ) { + final int lastSlash = eventPath.lastIndexOf('/'); + final String nodePath = eventPath.substring(0, lastSlash); + final String propName = eventPath.substring(lastSlash + 1); + if ( !addedEvents.containsKey(nodePath) ) { + this.updateChangedEvent(changedEvents, nodePath, event, propName); + } + } else if ( event.getType() == Event.NODE_ADDED ) { + // check if this is a remove/add operation + if ( removedEvents.remove(eventPath) != null ) { + this.updateChangedEvent(changedEvents, eventPath, event, null); + } else { + changedEvents.remove(eventPath); + addedEvents.put(eventPath, event); + } + + } else if ( event.getType() == Event.NODE_REMOVED) { + // remove is the strongest operation, therefore remove all removed + // paths from changed and added + addedEvents.remove(eventPath); + changedEvents.remove(eventPath); + + removedEvents.put(eventPath, event); + } + } catch (final RepositoryException e) { + logger.error("Error during modification: {}", e.getMessage()); + } + } + + for (final Entry e : removedEvents.entrySet()) { + // Launch an OSGi event + sendOsgiEvent(e.getKey(), e.getValue(), SlingConstants.TOPIC_RESOURCE_REMOVED, null); + } + + for (final Entry e : addedEvents.entrySet()) { + // Launch an OSGi event. + sendOsgiEvent(e.getKey(), e.getValue(), SlingConstants.TOPIC_RESOURCE_ADDED, null); + } + + // Send the changed events. + for (final Entry e : changedEvents.entrySet()) { + // Launch an OSGi event. + sendOsgiEvent(e.getKey(), e.getValue().firstEvent, SlingConstants.TOPIC_RESOURCE_CHANGED, e.getValue()); + } + } + + private static final class ChangedAttributes { + + private final Event firstEvent; + + public ChangedAttributes(final Event event) { + this.firstEvent = event; + } + + public Set addedAttributes, changedAttributes, removedAttributes; + + public void addEvent(final Event event, final String propName) { + if ( event.getType() == Event.PROPERTY_ADDED ) { + if ( removedAttributes != null ) { + removedAttributes.remove(propName); + } + if ( addedAttributes == null ) { + addedAttributes = new HashSet(); + } + addedAttributes.add(propName); + } else if ( event.getType() == Event.PROPERTY_REMOVED ) { + if ( addedAttributes != null ) { + addedAttributes.remove(propName); + } + if ( removedAttributes == null ) { + removedAttributes = new HashSet(); + } + removedAttributes.add(propName); + } else if ( event.getType() == Event.PROPERTY_CHANGED ) { + if ( changedAttributes == null ) { + changedAttributes = new HashSet(); + } + changedAttributes.add(propName); + } + } + + public void addProperties(final Dictionary properties) { + // we're not using the Constants from SlingConstants here to avoid the requirement of the latest + // SLING API to be available!! + if ( addedAttributes != null ) { + properties.put("resourceAddedAttributes", addedAttributes.toArray(new String[addedAttributes.size()])); + } + if ( changedAttributes != null ) { + properties.put("resourceChangedAttributes", changedAttributes.toArray(new String[changedAttributes.size()])); + } + if ( removedAttributes != null ) { + properties.put("resourceRemovedAttributes", removedAttributes.toArray(new String[removedAttributes.size()])); + } + } + } + + private void updateChangedEvent(final Map changedEvents, final String path, + final Event event, final String propName) { + ChangedAttributes storedEvent = changedEvents.get(path); + if ( storedEvent == null ) { + storedEvent = new ChangedAttributes(event); + changedEvents.put(path, storedEvent); + } + storedEvent.addEvent(event, propName); + } + + /** + * Send an OSGi event based on a JCR Observation Event. + * + * @param path The path too the node where the event occurred. + * @param event The JCR observation event. + * @param topic The topic that should be used for the OSGi event. + */ + private void sendOsgiEvent(String path, final Event event, final String topic, + final ChangedAttributes changedAttributes) { + + path = createWorkspacePath(path); + + final Dictionary properties = new Hashtable(); + properties.put(SlingConstants.PROPERTY_USERID, event.getUserID()); + if (this.isExternal(event)) { + properties.put("event.application", "unknown"); + } + if (changedAttributes != null) { + changedAttributes.addProperties(properties); + } + + // set the path (might have been changed for nt:file content) + properties.put(SlingConstants.PROPERTY_PATH, path); + properties.put(EventConstants.EVENT_TOPIC, topic); + + // enqueue event for dispatching + this.osgiEventQueue.offer(properties); + } + + /** + * Called by the Runnable.run method of the JCR Event Queue processor to + * process the {@link #osgiEventQueue} until the + * {@link #TERMINATE_PROCESSING} event is received. + */ + void processOsgiEventQueue() { + while (true) { + final Dictionary event; + try { + event = this.osgiEventQueue.take(); + } catch (InterruptedException e) { + // interrupted waiting for the event; keep on waiting + continue; + } + + if (event == null || event == TERMINATE_PROCESSING) { + break; + } + + try { + final EventAdmin localEa = (EventAdmin) this.eventAdminTracker.getService(); + if (localEa != null) { + final String topic = (String) event.remove(EventConstants.EVENT_TOPIC); + if (!SlingConstants.TOPIC_RESOURCE_REMOVED.equals(topic)) { + final String path = (String) event.get(SlingConstants.PROPERTY_PATH); + Resource resource = this.resolver.getResource(path); + if (resource != null) { + // check for nt:file nodes + if (path.endsWith("/jcr:content")) { + final Node node = resource.adaptTo(Node.class); + if (node != null) { + try { + if (node.getParent().isNodeType("nt:file")) { + @SuppressWarnings("deprecation") + final Resource parentResource = ResourceUtil.getParent(resource); + if (parentResource != null) { + resource = parentResource; + event.put(SlingConstants.PROPERTY_PATH, resource.getPath()); + } + } + } catch (RepositoryException re) { + // ignore this + } + } + } + + final String resourceType = resource.getResourceType(); + if (resourceType != null) { + event.put(SlingConstants.PROPERTY_RESOURCE_TYPE, resource.getResourceType()); + } + final String resourceSuperType = resource.getResourceSuperType(); + if (resourceSuperType != null) { + event.put(SlingConstants.PROPERTY_RESOURCE_SUPER_TYPE, resource.getResourceSuperType()); + } + } else { + // take a quite silent note of not being able to + // resolve the resource + logger.debug( + "processOsgiEventQueue: Resource at {} not found, which is not expected for an added or modified node", + path); + } + } + + localEa.sendEvent(new org.osgi.service.event.Event(topic, event)); + } + } catch (Exception e) { + logger.warn("processOsgiEventQueue: Unexpected problem processing event " + event, e); + } + } + + this.osgiEventQueue.clear(); + } + + private boolean isExternal(final Event event) { + if ( this.hasJackrabbitEventClass && event instanceof JackrabbitEvent) { + final JackrabbitEvent jEvent = (JackrabbitEvent)event; + return jEvent.isExternal(); + } + return false; + } + + private String createWorkspacePath(final String path) { + if (workspaceName == null) { + return path; + } + return workspaceName + ":" + path; + } +} Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java?rev=1239587&view=auto ============================================================================== --- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java (added) +++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolver.java Thu Feb 2 12:46:58 2012 @@ -0,0 +1,1361 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.jcr.Credentials; +import javax.jcr.NamespaceException; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; +import javax.jcr.query.Row; +import javax.jcr.query.RowIterator; +import javax.servlet.http.HttpServletRequest; + +import org.apache.sling.adapter.SlingAdaptable; +import org.apache.sling.adapter.annotations.Adaptable; +import org.apache.sling.adapter.annotations.Adapter; +import org.apache.sling.api.SlingException; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.NonExistingResource; +import org.apache.sling.api.resource.QuerySyntaxException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceNotFoundException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.jcr.resource.JcrResourceConstants; +import org.apache.sling.jcr.resource.JcrResourceUtil; +import org.apache.sling.jcr.resource.internal.helper.MapEntry; +import org.apache.sling.jcr.resource.internal.helper.RedirectResource; +import org.apache.sling.jcr.resource.internal.helper.ResourceIterator; +import org.apache.sling.jcr.resource.internal.helper.ResourcePathIterator; +import org.apache.sling.jcr.resource.internal.helper.URI; +import org.apache.sling.jcr.resource.internal.helper.URIException; +import org.apache.sling.jcr.resource.internal.helper.jcr.JcrNodeResourceIterator; +import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderEntry; +import org.apache.sling.jcr.resource.internal.helper.starresource.StarResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Adaptable(adaptableClass=ResourceResolver.class, adapters={ @Adapter(Session.class) }) +public class JcrResourceResolver + extends SlingAdaptable implements ResourceResolver { + + /** default logger */ + private final Logger LOGGER = LoggerFactory.getLogger(JcrResourceResolver.class); + + private static final String MANGLE_NAMESPACE_IN_SUFFIX = "_"; + + private static final String MANGLE_NAMESPACE_IN_PREFIX = "/_"; + + private static final String MANGLE_NAMESPACE_IN = "/_([^_/]+)_"; + + private static final String MANGLE_NAMESPACE_OUT_SUFFIX = ":"; + + private static final String MANGLE_NAMESPACE_OUT_PREFIX = "/"; + + private static final String MANGLE_NAMESPACE_OUT = "/([^:/]+):"; + + public static final String PROP_REG_EXP = "sling:match"; + + public static final String PROP_REDIRECT_INTERNAL = "sling:internalRedirect"; + + public static final String PROP_ALIAS = "sling:alias"; + + public static final String PROP_REDIRECT_EXTERNAL = "sling:redirect"; + + public static final String PROP_REDIRECT_EXTERNAL_STATUS = "sling:status"; + + // The suffix of a resource being a content node of some parent + // such as nt:file. The slash is included to prevent false + // positives for the String.endsWith check for names like + // "xyzjcr:content" + private static final String JCR_CONTENT_LEAF = "/jcr:content"; + + @SuppressWarnings("deprecation") + private static final String DEFAULT_QUERY_LANGUAGE = Query.XPATH; + + /** column name for node path */ + private static final String QUERY_COLUMN_PATH = "jcr:path"; + + /** column name for score value */ + private static final String QUERY_COLUMN_SCORE = "jcr:score"; + + /** The root provider for the resource tree. */ + private final JcrResourceProviderEntry rootProvider; + + /** The factory which created this resource resolver. */ + private final JcrResourceResolverFactoryImpl factory; + + /** Is this a resource resolver for an admin? */ + private final boolean isAdmin; + + /** The original authentication information - this is used for further resource resolver creations. */ + private final Map originalAuthInfo; + + /** Resolvers for different workspaces. */ + private Map createdResolvers; + + /** Closed marker. */ + private volatile boolean closed = false; + + /** a resolver with the workspace which was specifically requested via a request attribute. */ + private ResourceResolver requestBoundResolver; + + private final boolean useMultiWorkspaces; + + public JcrResourceResolver(final JcrResourceProviderEntry rootProvider, + final JcrResourceResolverFactoryImpl factory, + final boolean isAdmin, + final Map originalAuthInfo, + boolean useMultiWorkspaces) { + this.rootProvider = rootProvider; + this.factory = factory; + this.isAdmin = isAdmin; + this.originalAuthInfo = originalAuthInfo; + this.useMultiWorkspaces = useMultiWorkspaces; + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#clone(Map) + */ + public ResourceResolver clone(Map authenticationInfo) + throws LoginException { + + // ensure resolver is still live + checkClosed(); + + // create the merged map + Map newAuthenticationInfo = new HashMap(); + if (originalAuthInfo != null) { + newAuthenticationInfo.putAll(originalAuthInfo); + } + if (authenticationInfo != null) { + newAuthenticationInfo.putAll(authenticationInfo); + } + + // get an administrative resolver if this resolver isAdmin unless + // credentials and/or user name are present in the credentials and/or + // a session is present + if (isAdmin + && !(newAuthenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS) instanceof Credentials) + && !(newAuthenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_SESSION) instanceof Session) + && !(newAuthenticationInfo.get(ResourceResolverFactory.USER) instanceof String)) { + return factory.getAdministrativeResourceResolver(newAuthenticationInfo); + } + + // create a regular resource resolver + return factory.getResourceResolver(newAuthenticationInfo); + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#isLive() + */ + public boolean isLive() { + return !this.closed && getSession().isLive(); + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#close() + */ + public void close() { + if (!this.closed) { + this.closed = true; + closeCreatedResolvers(); + closeSession(); + } + } + + /** + * Closes the session underlying this resource resolver. This method is + * called by the {@link #close()} method. + *

+ * Extensions can overwrite this method to do other work (or not close the + * session at all). Handle with care ! + */ + protected void closeSession() { + try { + getSession().logout(); + } catch (Throwable t) { + LOGGER.debug( + "closeSession: Unexpected problem closing the session; ignoring", + t); + } + } + + /** + * Closes any helper resource resolver created while this resource resolver + * was used. + *

+ * Extensions can overwrite this method to do other work (or not close the + * created resource resovlers at all). Handle with care ! + */ + protected void closeCreatedResolvers() { + if (this.createdResolvers != null) { + for (final ResourceResolver resolver : createdResolvers.values()) { + try { + resolver.close(); + } catch (Throwable t) { + LOGGER.debug( + "closeCreatedResolvers: Unexpected problem closing the created resovler " + + resolver + "; ignoring", t); + } + } + } + } + + /** + * Calls the {@link #close()} method to ensure the resolver is properly + * cleaned up before it is being collected by the garbage collector because + * it is not referred to any more. + */ + protected void finalize() { + close(); + } + + /** + * Check if the resource resolver is already closed. + * + * @throws IllegalStateException If the resolver is already closed + */ + private void checkClosed() { + if ( this.closed ) { + throw new IllegalStateException("Resource resolver is already closed."); + } + } + + // ---------- attributes + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getAttributeNames() + */ + public Iterator getAttributeNames() { + checkClosed(); + final Set names = new HashSet(); + names.addAll(Arrays.asList(getSession().getAttributeNames())); + if (originalAuthInfo != null) { + names.addAll(originalAuthInfo.keySet()); + } + return new Iterator() { + final Iterator keys = names.iterator(); + + String nextKey = seek(); + + private String seek() { + while (keys.hasNext()) { + final String key = keys.next(); + if (JcrResourceResolverFactoryImpl.isAttributeVisible(key)) { + return key; + } + } + return null; + } + + public boolean hasNext() { + return nextKey != null; + } + + public String next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + String toReturn = nextKey; + nextKey = seek(); + return toReturn; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getAttribute(String) + */ + public Object getAttribute(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + if (JcrResourceResolverFactoryImpl.isAttributeVisible(name)) { + final Object sessionAttr = getSession().getAttribute(name); + if (sessionAttr != null) { + return sessionAttr; + } + if (originalAuthInfo != null) { + return originalAuthInfo.get(name); + } + } + + // not a visible attribute + return null; + } + + // ---------- resolving resources + + /** + * @see org.apache.sling.api.resource.ResourceResolver#resolve(java.lang.String) + */ + public Resource resolve(String absPath) { + checkClosed(); + return resolve(null, absPath); + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#resolve(javax.servlet.http.HttpServletRequest) + */ + public Resource resolve(HttpServletRequest request) { + checkClosed(); + // throws NPE if request is null as required + return resolve(request, request.getPathInfo()); + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#resolve(javax.servlet.http.HttpServletRequest, java.lang.String) + */ + public Resource resolve(HttpServletRequest request, String absPath) { + checkClosed(); + + String workspaceName = null; + + // make sure abspath is not null and is absolute + if (absPath == null) { + absPath = "/"; + } else if (!absPath.startsWith("/")) { + if (useMultiWorkspaces) { + final int wsSepPos = absPath.indexOf(":/"); + if (wsSepPos != -1) { + workspaceName = absPath.substring(0, wsSepPos); + absPath = absPath.substring(wsSepPos + 1); + } else { + absPath = "/" + absPath; + } + } else { + absPath = "/" + absPath; + } + } + + // check for special namespace prefix treatment + absPath = unmangleNamespaces(absPath); + if (useMultiWorkspaces) { + if (workspaceName == null) { + // check for workspace info from request + workspaceName = (request == null ? null : + (String)request.getAttribute(ResourceResolver.REQUEST_ATTR_WORKSPACE_INFO)); + } + if (workspaceName != null && !workspaceName.equals(getSession().getWorkspace().getName())) { + LOGGER.debug("Delegating resolving to resolver for workspace {}", workspaceName); + try { + final ResourceResolver wsResolver = getResolverForWorkspace(workspaceName); + requestBoundResolver = wsResolver; + return wsResolver.resolve(request, absPath); + } catch (LoginException e) { + // requested a resource in a workspace I don't have access to. + // we treat this as a not found resource + LOGGER.debug( + "resolve: Path {} does not resolve, returning NonExistingResource", + absPath); + + final Resource res = new NonExistingResource(this, absPath); + // SLING-864: if the path contains a dot we assume this to be + // the start for any selectors, extension, suffix, which may be + // used for further request processing. + int index = absPath.indexOf('.'); + if (index != -1) { + res.getResourceMetadata().setResolutionPathInfo(absPath.substring(index)); + } + return this.factory.getResourceDecoratorTracker().decorate(res, workspaceName, request); + } + + } + } + // Assume http://localhost:80 if request is null + String[] realPathList = { absPath }; + String requestPath; + if (request != null) { + requestPath = getMapPath(request.getScheme(), + request.getServerName(), request.getServerPort(), absPath); + } else { + requestPath = getMapPath("http", "localhost", 80, absPath); + } + + LOGGER.debug("resolve: Resolving request path {}", requestPath); + + // loop while finding internal or external redirect into the + // content out of the virtual host mapping tree + // the counter is to ensure we are not caught in an endless loop here + // TODO: might do better to be able to log the loop and help the user + for (int i = 0; i < 100; i++) { + + String[] mappedPath = null; + for (MapEntry mapEntry : this.factory.getMapEntries().getResolveMaps()) { + mappedPath = mapEntry.replace(requestPath); + if (mappedPath != null) { + if ( LOGGER.isDebugEnabled() ) { + LOGGER.debug( + "resolve: MapEntry {} matches, mapped path is {}", + mapEntry, Arrays.toString(mappedPath)); + } + if (mapEntry.isInternal()) { + // internal redirect + LOGGER.debug("resolve: Redirecting internally"); + break; + } + + // external redirect + LOGGER.debug("resolve: Returning external redirect"); + return this.factory.getResourceDecoratorTracker().decorate( + new RedirectResource(this, absPath, mappedPath[0], + mapEntry.getStatus()), workspaceName, + request); + } + } + + // if there is no virtual host based path mapping, abort + // and use the original realPath + if (mappedPath == null) { + LOGGER.debug( + "resolve: Request path {} does not match any MapEntry", + requestPath); + break; + } + + // if the mapped path is not an URL, use this path to continue + if (!mappedPath[0].contains("://")) { + LOGGER.debug("resolve: Mapped path is for resource tree"); + realPathList = mappedPath; + break; + } + + // otherwise the mapped path is an URI and we have to try to + // resolve that URI now, using the URI's path as the real path + try { + URI uri = new URI(mappedPath[0], false); + requestPath = getMapPath(uri.getScheme(), uri.getHost(), + uri.getPort(), uri.getPath()); + realPathList = new String[] { uri.getPath() }; + + LOGGER.debug( + "resolve: Mapped path is an URL, using new request path {}", + requestPath); + } catch (URIException use) { + // TODO: log and fail + throw new ResourceNotFoundException(absPath); + } + } + + // now we have the real path resolved from virtual host mapping + // this path may be absolute or relative, in which case we try + // to resolve it against the search path + + Resource res = null; + for (int i = 0; res == null && i < realPathList.length; i++) { + String realPath = realPathList[i]; + + // first check whether the requested resource is a StarResource + if (StarResource.appliesTo(realPath)) { + LOGGER.debug("resolve: Mapped path {} is a Star Resource", + realPath); + res = new StarResource(this, ensureAbsPath(realPath)); + + } else { + + if (realPath.startsWith("/")) { + + // let's check it with a direct access first + LOGGER.debug("resolve: Try absolute mapped path {}", realPath); + res = resolveInternal(realPath); + + } else { + + String[] searchPath = getSearchPath(); + for (int spi = 0; res == null && spi < searchPath.length; spi++) { + LOGGER.debug( + "resolve: Try relative mapped path with search path entry {}", + searchPath[spi]); + res = resolveInternal(searchPath[spi] + realPath); + } + + } + } + + } + + // if no resource has been found, use a NonExistingResource + if (res == null) { + String resourcePath = ensureAbsPath(realPathList[0]); + LOGGER.debug( + "resolve: Path {} does not resolve, returning NonExistingResource at {}", + absPath, resourcePath); + + res = new NonExistingResource(this, resourcePath); + // SLING-864: if the path contains a dot we assume this to be + // the start for any selectors, extension, suffix, which may be + // used for further request processing. + int index = resourcePath.indexOf('.'); + if (index != -1) { + res.getResourceMetadata().setResolutionPathInfo(resourcePath.substring(index)); + } + } else { + LOGGER.debug("resolve: Path {} resolves to Resource {}", absPath, res); + } + + return this.factory.getResourceDecoratorTracker().decorate(res, workspaceName, request); + } + + /** + * calls map(HttpServletRequest, String) as map(null, resourcePath) + * @see org.apache.sling.api.resource.ResourceResolver#map(java.lang.String) + */ + public String map(String resourcePath) { + checkClosed(); + return map(null, resourcePath); + } + + /** + * full implementation + * - apply sling:alias from the resource path + * - apply /etc/map mappings (inkl. config backwards compat) + * - return absolute uri if possible + * @see org.apache.sling.api.resource.ResourceResolver#map(javax.servlet.http.HttpServletRequest, java.lang.String) + */ + public String map(final HttpServletRequest request, final String resourcePath) { + checkClosed(); + + // find a fragment or query + int fragmentQueryMark = resourcePath.indexOf('#'); + if (fragmentQueryMark < 0) { + fragmentQueryMark = resourcePath.indexOf('?'); + } + + // cut fragment or query off the resource path + String mappedPath; + final String fragmentQuery; + if (fragmentQueryMark >= 0) { + fragmentQuery = resourcePath.substring(fragmentQueryMark); + mappedPath = resourcePath.substring(0, fragmentQueryMark); + LOGGER.debug("map: Splitting resource path '{}' into '{}' and '{}'", + new Object[] { resourcePath, mappedPath, fragmentQuery }); + } else { + fragmentQuery = null; + mappedPath = resourcePath; + } + + + // cut off scheme and host, if the same as requested + final String schemehostport; + final String schemePrefix; + if (request != null) { + schemehostport = MapEntry.getURI(request.getScheme(), + request.getServerName(), request.getServerPort(), "/"); + schemePrefix = request.getScheme().concat("://"); + LOGGER.debug( + "map: Mapping path {} for {} (at least with scheme prefix {})", + new Object[] { resourcePath, schemehostport, schemePrefix }); + + } else { + + schemehostport = null; + schemePrefix = null; + LOGGER.debug("map: Mapping path {} for default", resourcePath); + + } + + Resource res = null; + String workspaceName = null; + + if (useMultiWorkspaces) { + final int wsSepPos = mappedPath.indexOf(":/"); + if (wsSepPos != -1) { + workspaceName = mappedPath.substring(0, wsSepPos); + if (workspaceName.equals(getSession().getWorkspace().getName())) { + mappedPath = mappedPath.substring(wsSepPos + 1); + } else { + try { + JcrResourceResolver wsResolver = getResolverForWorkspace(workspaceName); + mappedPath = mappedPath.substring(wsSepPos + 1); + res = wsResolver.resolveInternal(mappedPath); + } catch (LoginException e) { + // requested a resource in a workspace I don't have access to. + // we treat this as a not found resource + return null; + } + } + } else { + // check for workspace info in request + workspaceName = (request == null ? null : + (String)request.getAttribute(ResourceResolver.REQUEST_ATTR_WORKSPACE_INFO)); + if ( workspaceName != null && !workspaceName.equals(getSession().getWorkspace().getName())) { + LOGGER.debug("Delegating resolving to resolver for workspace {}", workspaceName); + try { + JcrResourceResolver wsResolver = getResolverForWorkspace(workspaceName); + res = wsResolver.resolveInternal(mappedPath); + } catch (LoginException e) { + // requested a resource in a workspace I don't have access to. + // we treat this as a not found resource + return null; + } + + } + } + } + + if (res == null) { + res = resolveInternal(mappedPath); + } + + if (res != null) { + + // keep, what we might have cut off in internal resolution + final String resolutionPathInfo = res.getResourceMetadata().getResolutionPathInfo(); + + LOGGER.debug("map: Path maps to resource {} with path info {}", res, + resolutionPathInfo); + + // find aliases for segments. we can't walk the parent chain + // since the request session might not have permissions to + // read all parents SLING-2093 + final LinkedList names = new LinkedList(); + + Resource current = res; + String path = res.getPath(); + while ( path != null ) { + String alias = null; + if ( current != null && !path.endsWith(JCR_CONTENT_LEAF)) { + alias = getProperty(current, PROP_ALIAS); + } + if (alias == null || alias.length() == 0) { + alias = ResourceUtil.getName(path); + } + names.add(alias); + path = ResourceUtil.getParent(path); + if ( "/".equals(path) ) { + path = null; + } else if ( path != null ) { + current = res.getResourceResolver().resolve(path); + } + } + + // build path from segment names + final StringBuilder buf = new StringBuilder(); + + // construct the path from the segments (or root if none) + if (names.isEmpty()) { + buf.append('/'); + } else { + while (!names.isEmpty()) { + buf.append('/'); + buf.append(names.removeLast()); + } + } + + // reappend the resolutionPathInfo + if (resolutionPathInfo != null) { + buf.append(resolutionPathInfo); + } + + // and then we have the mapped path to work on + mappedPath = buf.toString(); + + LOGGER.debug("map: Alias mapping resolves to path {}", mappedPath); + + } + + boolean mappedPathIsUrl = false; + for (final MapEntry mapEntry : this.factory.getMapEntries().getMapMaps()) { + final String[] mappedPaths = mapEntry.replace(mappedPath); + if (mappedPaths != null) { + + LOGGER.debug("map: Match for Entry {}", mapEntry); + + mappedPathIsUrl = !mapEntry.isInternal(); + + if (mappedPathIsUrl && schemehostport != null) { + + mappedPath = null; + + for (final String candidate : mappedPaths) { + if (candidate.startsWith(schemehostport)) { + mappedPath = candidate.substring(schemehostport.length() - 1); + mappedPathIsUrl = false; + LOGGER.debug( + "map: Found host specific mapping {} resolving to {}", + candidate, mappedPath); + break; + } else if (candidate.startsWith(schemePrefix) + && mappedPath == null) { + mappedPath = candidate; + } + } + + if (mappedPath == null) { + mappedPath = mappedPaths[0]; + } + + } else { + + // we can only go with assumptions selecting the first entry + mappedPath = mappedPaths[0]; + + } + + LOGGER.debug( + "resolve: MapEntry {} matches, mapped path is {}", + mapEntry, mappedPath); + + break; + } + } + + // this should not be the case, since mappedPath is primed + if (mappedPath == null) { + mappedPath = resourcePath; + } + + // [scheme:][//authority][path][?query][#fragment] + try { + // use commons-httpclient's URI instead of java.net.URI, as it can + // actually accept *unescaped* URIs, such as the "mappedPath" and + // return them in proper escaped form, including the path, via toString() + URI uri = new URI(mappedPath, false); + + // 1. mangle the namespaces in the path + String path = mangleNamespaces(uri.getPath()); + + // 2. prepend servlet context path if we have a request + if (request != null && request.getContextPath() != null + && request.getContextPath().length() > 0) { + path = request.getContextPath().concat(path); + } + // update the path part of the URI + uri.setPath(path); + + mappedPath = uri.toString(); + } catch (URIException e) { + LOGGER.warn("map: Unable to mangle namespaces for " + mappedPath + + " returning unmangled", e); + } + + LOGGER.debug("map: Returning URL {} as mapping for path {}", + mappedPath, resourcePath); + + // reappend fragment and/or query + if (fragmentQuery != null) { + mappedPath = mappedPath.concat(fragmentQuery); + } + + return mappedPath; + } + + // ---------- search path for relative resoures + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getSearchPath() + */ + public String[] getSearchPath() { + checkClosed(); + return factory.getSearchPath().clone(); + } + + // ---------- direct resource access without resolution + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getResource(java.lang.String) + */ + public Resource getResource(String path) { + checkClosed(); + + if (useMultiWorkspaces) { + final int wsSepPos = path.indexOf(":/"); + if (wsSepPos != -1) { + final String workspaceName = path.substring(0, wsSepPos); + if (workspaceName.equals(getSession().getWorkspace().getName())) { + path = path.substring(wsSepPos + 1); + } else { + try { + ResourceResolver wsResolver = getResolverForWorkspace(workspaceName); + return wsResolver.getResource(path.substring(wsSepPos + 1)); + } catch (LoginException e) { + // requested a resource in a workspace I don't have access to. + // we treat this as a not found resource + return null; + } + } + } + } + + // if the path is absolute, normalize . and .. segements and get res + if (path.startsWith("/")) { + path = ResourceUtil.normalize(path); + Resource result = (path != null) ? getResourceInternal(path) : null; + if ( result != null ) { + String workspacePrefix = null; + if ( useMultiWorkspaces && !getSession().getWorkspace().getName().equals(this.factory.getDefaultWorkspaceName()) ) { + workspacePrefix = getSession().getWorkspace().getName(); + } + + result = this.factory.getResourceDecoratorTracker().decorate(result, workspacePrefix, null); + return result; + } + return null; + } + + // otherwise we have to apply the search path + // (don't use this.getSearchPath() to save a few cycle for not cloning) + String[] paths = factory.getSearchPath(); + if (paths != null) { + for (String prefix : factory.getSearchPath()) { + Resource res = getResource(prefix + path); + if (res != null) { + return res; + } + } + } + + // no resource found, if we get here + return null; + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getResource(org.apache.sling.api.resource.Resource, java.lang.String) + */ + public Resource getResource(Resource base, String path) { + checkClosed(); + + if (!path.startsWith("/") && base != null) { + path = base.getPath() + "/" + path; + } + + return getResource(path); + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#listChildren(org.apache.sling.api.resource.Resource) + */ + @SuppressWarnings("unchecked") + public Iterator listChildren(Resource parent) { + checkClosed(); + final String path = parent.getPath(); + final int wsSepPos = path.indexOf(":/"); + if (wsSepPos != -1) { + final String workspaceName = path.substring(0, wsSepPos); + if (!workspaceName.equals(getSession().getWorkspace().getName())) { + if (useMultiWorkspaces) { + try { + ResourceResolver wsResolver = getResolverForWorkspace(workspaceName); + return wsResolver.listChildren(parent); + } catch (LoginException e) { + // requested a resource in a workspace I don't have access to. + // we treat this as a not found resource + return Collections.EMPTY_LIST.iterator(); + } + } + // this is illegal + return Collections.EMPTY_LIST.iterator(); + } else if (parent instanceof WorkspaceDecoratedResource) { + parent = ((WorkspaceDecoratedResource) parent).getResource(); + } else { + LOGGER.warn("looking for children of workspace path {}, but with an undecorated resource.", + parent.getPath()); + } + } + + String workspacePrefix = null; + if ( useMultiWorkspaces && !getSession().getWorkspace().getName().equals(this.factory.getDefaultWorkspaceName()) ) { + workspacePrefix = getSession().getWorkspace().getName(); + } + + return new ResourceIteratorDecorator( + this.factory.getResourceDecoratorTracker(), workspacePrefix, + new ResourceIterator(parent, rootProvider)); + } + + // ---------- Querying resources + + /** + * @see org.apache.sling.api.resource.ResourceResolver#findResources(java.lang.String, java.lang.String) + */ + public Iterator findResources(final String query, final String language) + throws SlingException { + checkClosed(); + try { + Session session = null; + String workspaceName = null; + if (requestBoundResolver != null) { + session = requestBoundResolver.adaptTo(Session.class); + workspaceName = session.getWorkspace().getName(); + } else { + session = getSession(); + } + final QueryResult res = JcrResourceUtil.query(session, query, language); + return new ResourceIteratorDecorator(this.factory.getResourceDecoratorTracker(), workspaceName, + new JcrNodeResourceIterator(this, res.getNodes(), factory.getDynamicClassLoader())); + } catch (javax.jcr.query.InvalidQueryException iqe) { + throw new QuerySyntaxException(iqe.getMessage(), query, language, iqe); + } catch (RepositoryException re) { + throw new SlingException(re.getMessage(), re); + } + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#queryResources(java.lang.String, java.lang.String) + */ + public Iterator> queryResources(final String query, + final String language) + throws SlingException { + checkClosed(); + + final String queryLanguage = isSupportedQueryLanguage(language) ? language : DEFAULT_QUERY_LANGUAGE; + + try { + QueryResult result = JcrResourceUtil.query(adaptTo(Session.class), query, + queryLanguage); + final String[] colNames = result.getColumnNames(); + final RowIterator rows = result.getRows(); + return new Iterator>() { + public boolean hasNext() { + return rows.hasNext(); + }; + + public Map next() { + Map row = new HashMap(); + try { + Row jcrRow = rows.nextRow(); + boolean didPath = false; + boolean didScore = false; + Value[] values = jcrRow.getValues(); + for (int i = 0; i < values.length; i++) { + Value v = values[i]; + if (v != null) { + String colName = colNames[i]; + row.put(colName, + JcrResourceUtil.toJavaObject(values[i])); + if (colName.equals(QUERY_COLUMN_PATH)) { + didPath = true; + } + if (colName.equals(QUERY_COLUMN_SCORE)) { + didScore = true; + } + } + } + if (!didPath) { + row.put(QUERY_COLUMN_PATH, jcrRow.getPath()); + } + if (!didScore) { + row.put(QUERY_COLUMN_SCORE, jcrRow.getScore()); + } + + } catch (RepositoryException re) { + LOGGER.error( + "queryResources$next: Problem accessing row values", + re); + } + return row; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; + } catch (javax.jcr.query.InvalidQueryException iqe) { + throw new QuerySyntaxException(iqe.getMessage(), query, language, + iqe); + } catch (RepositoryException re) { + throw new SlingException(re.getMessage(), re); + } + } + + /** + * @see org.apache.sling.api.resource.ResourceResolver#getUserID() + */ + public String getUserID() { + checkClosed(); + return getSession().getUserID(); + } + + // ---------- Adaptable interface + + /** + * @see org.apache.sling.adapter.SlingAdaptable#adaptTo(java.lang.Class) + */ + @SuppressWarnings("unchecked") + public AdapterType adaptTo(Class type) { + checkClosed(); + if (type == Session.class) { + if (requestBoundResolver != null) { + return (AdapterType) requestBoundResolver.adaptTo(Session.class); + } + return (AdapterType) getSession(); + } + + // fall back to default behaviour + return super.adaptTo(type); + } + + // ---------- internal + + /** + * Returns the JCR Session of the root resource provider which provides + * access to the repository. + */ + private Session getSession() { + return rootProvider.getSession(); + } + + /** + * Get a resolver for the workspace. + */ + private synchronized JcrResourceResolver getResolverForWorkspace( + final String workspaceName) throws LoginException { + if (createdResolvers == null) { + createdResolvers = new HashMap(); + } + JcrResourceResolver wsResolver = createdResolvers.get(workspaceName); + if (wsResolver == null) { + final Map newAuthInfo = new HashMap(); + newAuthInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE, + workspaceName); + wsResolver = (JcrResourceResolver) clone(newAuthInfo); + createdResolvers.put(workspaceName, wsResolver); + } + return wsResolver; + } + + /** + * Returns a string used for matching map entries against the given request + * or URI parts. + * + * @param scheme The URI scheme + * @param host The host name + * @param port The port number. If this is negative, the default value used + * is 80 unless the scheme is "https" in which case the default + * value is 443. + * @param path The (absolute) path + * @return The request path string {scheme}/{host}.{port}{path}. + */ + public static String getMapPath(String scheme, String host, int port, String path) { + if (port < 0) { + port = ("https".equals(scheme)) ? 443 : 80; + } + + return scheme + "/" + host + "." + port + path; + } + + /** + * Internally resolves the absolute path. The will almost always contain + * request selectors and an extension. Therefore this method uses the + * {@link ResourcePathIterator} to cut off parts of the path to find the + * actual resource. + *

+ * This method operates in two steps: + *

    + *
  1. Check the path directly + *
  2. Drill down the resource tree from the root down to the resource + * trying to get the child as per the respective path segment or finding a + * child whose sling:alias property is set to the respective + * name. + *
+ *

+ * If neither mechanism (direct access and drill down) resolves to a + * resource this method returns null. + * + * @param absPath The absolute path of the resource to return. + * @return The resource found or null if the resource could + * not be found. The + * {@link org.apache.sling.api.resource.ResourceMetadata#getResolutionPathInfo() resolution path info} + * field of the resource returned is set to the part of the + * absPath which has been cut off by the + * {@link ResourcePathIterator} to resolve the resource. + */ + private Resource resolveInternal(String absPath) { + Resource resource = null; + String curPath = absPath; + try { + final ResourcePathIterator it = new ResourcePathIterator(absPath); + while (it.hasNext() && resource == null) { + curPath = it.next(); + resource = getResourceInternal(curPath); + } + } catch (Exception ex) { + throw new SlingException("Problem trying " + curPath + + " for request path " + absPath, ex); + } + + // SLING-627: set the part cut off from the uriPath as + // sling.resolutionPathInfo property such that + // uriPath = curPath + sling.resolutionPathInfo + if (resource != null) { + + String rpi = absPath.substring(curPath.length()); + resource.getResourceMetadata().setResolutionPathInfo(rpi); + + LOGGER.debug( + "resolveInternal: Found resource {} with path info {} for {}", + new Object[] { resource, rpi, absPath }); + + } else { + + // no direct resource found, so we have to drill down into the + // resource tree to find a match + resource = getResourceInternal("/"); + StringBuilder resolutionPath = new StringBuilder(); + StringTokenizer tokener = new StringTokenizer(absPath, "/"); + while (resource != null && tokener.hasMoreTokens()) { + String childNameRaw = tokener.nextToken(); + + Resource nextResource = getChildInternal(resource, childNameRaw); + if (nextResource != null) { + + resource = nextResource; + resolutionPath.append("/").append(childNameRaw); + + } else { + + String childName = null; + ResourcePathIterator rpi = new ResourcePathIterator( + childNameRaw); + while (rpi.hasNext() && nextResource == null) { + childName = rpi.next(); + nextResource = getChildInternal(resource, childName); + } + + // switch the currentResource to the nextResource (may be + // null) + resource = nextResource; + resolutionPath.append("/").append(childName); + + // terminate the search if a resource has been found + // with the extension cut off + if (nextResource != null) { + break; + } + } + } + + // SLING-627: set the part cut off from the uriPath as + // sling.resolutionPathInfo property such that + // uriPath = curPath + sling.resolutionPathInfo + if (resource != null) { + final String path = resolutionPath.toString(); + final String pathInfo = absPath.substring(path.length()); + + resource.getResourceMetadata().setResolutionPath(path); + resource.getResourceMetadata().setResolutionPathInfo(pathInfo); + + LOGGER.debug( + "resolveInternal: Found resource {} with path info {} for {}", + new Object[] { resource, pathInfo, absPath }); + } + } + + return resource; + } + + private Resource getChildInternal(Resource parent, String childName) { + Resource child = getResource(parent, childName); + if (child != null) { + String alias = getProperty(child, PROP_REDIRECT_INTERNAL); + if (alias != null) { + // TODO: might be a redirect ?? + LOGGER.warn( + "getChildInternal: Internal redirect to {} for Resource {} is not supported yet, ignoring", + alias, child); + } + + // we have the resource name, continue with the next level + return child; + } + + // we do not have a child with the exact name, so we look for + // a child, whose alias matches the childName + Iterator children = listChildren(parent); + while (children.hasNext()) { + child = children.next(); + if (!child.getPath().endsWith(JCR_CONTENT_LEAF)){ + String[] aliases = getProperty(child, PROP_ALIAS, String[].class); + if (aliases != null) { + for (String alias : aliases) { + if (childName.equals(alias)) { + LOGGER.debug( + "getChildInternal: Found Resource {} with alias {} to use", + child, childName); + return child; + } + } + } + } + } + + // no match for the childName found + LOGGER.debug("getChildInternal: Resource {} has no child {}", parent, + childName); + return null; + } + + /** + * Creates a JcrNodeResource with the given path if existing + */ + protected Resource getResourceInternal(String path) { + + Resource resource = rootProvider.getResource(this, path); + if (resource != null) { + resource.getResourceMetadata().setResolutionPath(path); + return resource; + } + + LOGGER.debug( + "getResourceInternal: Cannot resolve path '{}' to a resource", path); + return null; + } + + public String getProperty(Resource res, String propName) { + return getProperty(res, propName, String.class); + } + + public Type getProperty(Resource res, String propName, + Class type) { + + // check the property in the resource itself + ValueMap props = res.adaptTo(ValueMap.class); + if (props != null) { + Type prop = props.get(propName, type); + if (prop != null) { + LOGGER.debug("getProperty: Resource {} has property {}={}", + new Object[] { res, propName, prop }); + return prop; + } + // otherwise, check it in the jcr:content child resource + // This is a special case checking for JCR based resources + // we directly use the deep resolution of properties of the + // JCR value map implementation - this does not work + // in non JCR environments, however in non JCR envs there + // is usually no "jcr:content" child node anyway + prop = props.get("jcr:content/" + propName, type); + return prop; + } + + return null; + } + + /** + * Returns the path as an absolute path. If the path is + * already absolute it is returned unmodified (the same instance actually). + * If the path is relative it is made absolute by prepending the first entry + * of the {@link #getSearchPath() search path}. + * + * @param path The path to ensure absolute + * @return The absolute path as explained above + */ + private String ensureAbsPath(String path) { + if (!path.startsWith("/")) { + path = getSearchPath()[0] + path; + } + return path; + } + + private String mangleNamespaces(String absPath) { + if (factory.isMangleNamespacePrefixes() && absPath.contains(MANGLE_NAMESPACE_OUT_SUFFIX)) { + Pattern p = Pattern.compile(MANGLE_NAMESPACE_OUT); + Matcher m = p.matcher(absPath); + + StringBuffer buf = new StringBuffer(); + while (m.find()) { + String replacement = MANGLE_NAMESPACE_IN_PREFIX + m.group(1) + MANGLE_NAMESPACE_IN_SUFFIX; + m.appendReplacement(buf, replacement); + } + + m.appendTail(buf); + + absPath = buf.toString(); + } + + return absPath; + } + + private String unmangleNamespaces(String absPath) { + if (factory.isMangleNamespacePrefixes() && absPath.contains(MANGLE_NAMESPACE_IN_PREFIX)) { + Pattern p = Pattern.compile(MANGLE_NAMESPACE_IN); + Matcher m = p.matcher(absPath); + StringBuffer buf = new StringBuffer(); + while (m.find()) { + String namespace = m.group(1); + try { + + // throws if "namespace" is not a registered + // namespace prefix + getSession().getNamespaceURI(namespace); + + String replacement = MANGLE_NAMESPACE_OUT_PREFIX + + namespace + MANGLE_NAMESPACE_OUT_SUFFIX; + m.appendReplacement(buf, replacement); + + } catch (NamespaceException ne) { + + // not a valid prefix + LOGGER.debug( + "unmangleNamespaces: '{}' is not a prefix, not unmangling", + namespace); + + } catch (RepositoryException re) { + + LOGGER.warn( + "unmangleNamespaces: Problem checking namespace '{}'", + namespace, re); + + } + } + m.appendTail(buf); + absPath = buf.toString(); + } + + return absPath; + } + + private boolean isSupportedQueryLanguage(String language) { + try { + String[] supportedLanguages = adaptTo(Session.class).getWorkspace(). + getQueryManager().getSupportedQueryLanguages(); + for (String lang : supportedLanguages) { + if (lang.equals(language)) { + return true; + } + } + } catch (RepositoryException e) { + LOGGER.error("Unable to discover supported query languages", e); + } + return false; + } +}