sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vram...@apache.org
Subject svn commit: r1239587 [3/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 GMT
Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverFactoryImpl.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,758 @@
+/*
+ * 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.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.collections.BidiMap;
+import org.apache.commons.collections.bidimap.TreeBidiMap;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.References;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceDecorator;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
+import org.apache.sling.commons.osgi.OsgiUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.apache.sling.jcr.resource.JcrResourceResolverFactory;
+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.RootResourceProviderEntry;
+import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderEntry;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+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>
+ *
+ * First attempt of an resource resolver factory implementation.
+ * WORK IN PROGRESS - see SLING-1262
+ */
+@Component(immediate=true, label="%resource.resolver.name", description="%resource.resolver.description", specVersion="1.1", metatype=true)
+@Service(value={JcrResourceResolverFactory.class, ResourceResolverFactory.class})
+@Properties({
+    @Property(name = Constants.SERVICE_DESCRIPTION, value="Sling JcrResourceResolverFactory Implementation"),
+    @Property(name = Constants.SERVICE_VENDOR, value="The Apache Software Foundation")
+    
+})
+@References({
+    @Reference(name="ResourceProvider", referenceInterface=ResourceProvider.class, cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, policy=ReferencePolicy.DYNAMIC),
+    @Reference(name="ResourceDecorator", referenceInterface=ResourceDecorator.class, cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, policy=ReferencePolicy.DYNAMIC)    
+})
+public class JcrResourceResolverFactoryImpl implements
+        JcrResourceResolverFactory, ResourceResolverFactory {
+
+    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;
+        }
+    }
+
+    private static final boolean DEFAULT_MULTIWORKSPACE = false;
+
+    /**
+     * Special value which, if passed to listener.workspaces, will have resource
+     * events fired for all workspaces.
+     */
+    public static final String ALL_WORKSPACES = "*";
+
+    @Property(value={"/apps", "/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>.
+     *
+     */
+    @Property(boolValue=true)
+    private static final String PROP_MANGLE_NAMESPACES = "resource.resolver.manglenamespaces";
+
+
+    @Property(boolValue=true)
+    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.
+     */
+    @Property(value="/:/", unbounded=PropertyUnbounded.ARRAY)
+    private static final String PROP_VIRTUAL = "resource.resolver.virtual";
+
+    @Property(value={"/:/", "/content/:/", "/system/docroot/:/"})
+    private static final String PROP_MAPPING = "resource.resolver.mapping";
+
+    @Property(value=MapEntries.DEFAULT_MAP_ROOT)
+    private static final String PROP_MAP_LOCATION = "resource.resolver.map.location";
+
+    @Property(boolValue=DEFAULT_MULTIWORKSPACE)
+    private static final String PROP_MULTIWORKSPACE = "resource.resolver.multiworkspace";
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    private SlingRepository repository;
+
+    /** Tracker for the resource decorators. */
+    private final ResourceDecoratorTracker resourceDecoratorTracker = new ResourceDecoratorTracker();
+
+    // helper for the new JcrResourceResolver
+    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;
+
+    // the root location of the /etc/map entries
+    private String mapRoot;
+
+    private final RootResourceProviderEntry rootProviderEntry;
+
+    // whether to mangle paths with namespaces or not
+    private boolean mangleNamespacePrefixes;
+
+    private boolean useMultiWorkspaces;
+
+    /** The resource listeners for the observation events. */
+    private Set<JcrResourceListener> resourceListeners;
+
+    /** The service tracker for the event admin
+     */
+    private ServiceTracker eventAdminTracker;
+
+    /** The dynamic class loader */
+    @Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY, policy=ReferencePolicy.DYNAMIC)
+    private DynamicClassLoaderManager dynamicClassLoaderManager;
+
+    private JcrItemAdapterFactory jcrItemAdapterFactory;
+
+    public JcrResourceResolverFactoryImpl() {
+        this.rootProviderEntry = new RootResourceProviderEntry();
+
+    }
+
+    public ResourceDecoratorTracker getResourceDecoratorTracker() {
+        return this.resourceDecoratorTracker;
+    }
+
+    // ---------- JcrResourceResolverFactory -----------------------------------
+
+    /**
+     * Returns a new <code>ResourceResolve</code> for the given session. Note
+     * that each call to this method returns a new resource manager instance.
+     *
+     * @see org.apache.sling.jcr.resource.JcrResourceResolverFactory#getResourceResolver(javax.jcr.Session)
+     */
+    public ResourceResolver getResourceResolver(Session session) {
+        Map<String, Object> authInfo = new HashMap<String, Object>(1);
+        authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
+        try {
+            return getResourceResolver(authInfo);
+        } catch (LoginException le) {
+            // we don't expect a LoginException here because just a
+            // ResourceResolver wrapping the given session is to be created.
+            throw new InternalError("Unexpected LoginException");
+        }
+    }
+
+    // ---------- Resource Resolver Factory ------------------------------------
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceResolverFactory#getAdministrativeResourceResolver(java.util.Map)
+     */
+    public ResourceResolver getAdministrativeResourceResolver(
+            final Map<String, Object> authenticationInfo) throws LoginException {
+        return getResourceResolverInternal(authenticationInfo, true);
+    }
+
+    /**
+     * @see org.apache.sling.api.resource.ResourceResolverFactory#getResourceResolver(java.util.Map)
+     */
+    public ResourceResolver getResourceResolver(
+            final Map<String, Object> authenticationInfo) throws LoginException {
+        return getResourceResolverInternal(authenticationInfo, false);
+    }
+
+    /**
+     * Create a new ResourceResolver wrapping a Session object. Carries map of
+     * authentication info in order to create a new resolver as needed.
+     */
+    private ResourceResolver getResourceResolverInternal(
+            final Map<String, Object> authenticationInfo, final boolean isAdmin)
+            throws LoginException {
+
+        // by default any session used by the resource resolver returned is
+        // closed when the resource resolver is closed
+        boolean logoutSession = true;
+
+        // derive the session to be used
+        Session session;
+        try {
+            final String workspace = getWorkspace(authenticationInfo);
+            if (isAdmin) {
+                // requested admin session to any workspace (or default)
+                session = getRepository().loginAdministrative(workspace);
+
+            } else {
+
+                session = getSession(authenticationInfo);
+                if (session == null) {
+                    // requested non-admin session to any workspace (or default)
+                    final Credentials credentials = getCredentials(authenticationInfo);
+                    session = getRepository().login(credentials, workspace);
+
+                } else if (workspace != null) {
+                    // session provided by map; but requested a different
+                    // workspace impersonate can only change the user not switch
+                    // the workspace as a workaround we login to the requested
+                    // workspace with admin and then switch to the provided
+                    // session's user (if required)
+                    Session tmpSession = null;
+                    try {
+                        tmpSession = getRepository().loginAdministrative(
+                            workspace);
+                        if (tmpSession.getUserID().equals(session.getUserID())) {
+                            session = tmpSession;
+                            tmpSession = null;
+                        } else {
+                            session = tmpSession.impersonate(new SimpleCredentials(
+                                session.getUserID(), new char[0]));
+                        }
+                    } finally {
+                        if (tmpSession != null) {
+                            tmpSession.logout();
+                        }
+                    }
+
+                } else {
+                    // session provided; no special workspace; just make sure
+                    // the session is not logged out when the resolver is closed
+                    logoutSession = false;
+                }
+            }
+        } catch (RepositoryException re) {
+            throw getLoginException(re);
+        }
+
+        session = handleImpersonation(session, authenticationInfo, logoutSession);
+
+        final JcrResourceProviderEntry sessionRoot = new JcrResourceProviderEntry(
+            session, rootProviderEntry, this.getDynamicClassLoader(),
+            useMultiWorkspaces);
+
+        if (logoutSession) {
+            return new JcrResourceResolver(sessionRoot, this, isAdmin,
+                authenticationInfo, useMultiWorkspaces);
+        }
+
+        return new JcrResourceResolver(sessionRoot, this, isAdmin,
+            authenticationInfo, useMultiWorkspaces) {
+            protected void closeSession() {
+            }
+        };
+    }
+
+    // ---------- Implementation helpers --------------------------------------
+
+    /** Get the dynamic class loader if available */
+    ClassLoader getDynamicClassLoader() {
+        final DynamicClassLoaderManager dclm = this.dynamicClassLoaderManager;
+        if ( dclm != null ) {
+            return dclm.getDynamicClassLoader();
+        }
+        return null;
+    }
+
+    /**
+     * This method is called from {@link MapEntries}
+     */
+    public BidiMap getVirtualURLMap() {
+        return virtualURLMap;
+    }
+
+    /**
+     * This method is called from {@link MapEntries}
+     */
+    public Mapping[] getMappings() {
+        return mappings;
+    }
+
+    String[] getSearchPath() {
+        return searchPath;
+    }
+
+    boolean isMangleNamespacePrefixes() {
+        return mangleNamespacePrefixes;
+
+    }
+
+    public String getMapRoot() {
+        return mapRoot;
+    }
+
+    MapEntries getMapEntries() {
+        return mapEntries;
+    }
+
+    String getDefaultWorkspaceName() {
+        return this.repository.getDefaultWorkspace();
+    }
+
+    /**
+     * 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(final ComponentContext componentContext) {
+        // setup tracker first as this is used in the bind/unbind methods
+        this.eventAdminTracker = new ServiceTracker(componentContext.getBundleContext(),
+                EventAdmin.class.getName(), null);
+        this.eventAdminTracker.open();
+
+        final Dictionary<?, ?> properties = componentContext.getProperties();
+
+        BidiMap virtuals = new TreeBidiMap();
+        String[] virtualList = OsgiUtil.toStringArray(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);
+
+        // the root of the resolver mappings
+        mapRoot = OsgiUtil.toString(properties.get(PROP_MAP_LOCATION),
+            MapEntries.DEFAULT_MAP_ROOT);
+
+        // set up the map entries from configuration
+        try {
+            mapEntries = new MapEntries(this, componentContext.getBundleContext(), this.eventAdminTracker);
+        } catch (Exception e) {
+            log.error(
+                "activate: Cannot access repository, failed setting up Mapping Support",
+                e);
+        }
+
+
+        // start observation listener
+        try {
+            this.resourceListeners = new HashSet<JcrResourceListener>();
+
+            // first - add a listener for the default workspace
+            this.resourceListeners.add(new JcrResourceListener(null, this, "/", "/", this.eventAdminTracker));
+
+            // check if multi workspace support is enabled
+            this.useMultiWorkspaces = OsgiUtil.toBoolean(properties.get(PROP_MULTIWORKSPACE), DEFAULT_MULTIWORKSPACE);
+            if (this.useMultiWorkspaces) {
+                final String[] listenerWorkspaces = getAllWorkspaces();
+                for (final String wspName : listenerWorkspaces) {
+                    if (!wspName.equals(this.repository.getDefaultWorkspace())) {
+                        this.resourceListeners.add(
+                            new JcrResourceListener(wspName, this, "/", "/", this.eventAdminTracker));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error(
+                "activate: Cannot create resource listener; resource events for JCR resources will be disabled.",
+                e);
+        }
+
+        try {
+            plugin = new JcrResourceResolverWebConsolePlugin(componentContext.getBundleContext(), this);
+        } catch (Throwable ignore) {
+            // an exception here propably means the web console plugin is not available
+            log.debug(
+                    "activate: unable to setup web console plugin.", ignore);
+        }
+        
+        jcrItemAdapterFactory = new JcrItemAdapterFactory(componentContext.getBundleContext(), this);
+    }
+
+    private JcrResourceResolverWebConsolePlugin plugin;
+
+    /** Deativates this component, called by SCR to take out of service */
+    protected void deactivate(final ComponentContext componentContext) {
+        if (jcrItemAdapterFactory != null) {
+            jcrItemAdapterFactory.dispose();
+            jcrItemAdapterFactory = null;
+        }
+        
+        if (plugin != null) {
+            plugin.dispose();
+            plugin = null;
+        }
+
+        if (mapEntries != null) {
+            mapEntries.dispose();
+            mapEntries = MapEntries.EMPTY;
+        }
+        if ( this.eventAdminTracker != null ) {
+            this.eventAdminTracker.close();
+            this.eventAdminTracker = null;
+        }
+        if ( this.resourceListeners != null && !this.resourceListeners.isEmpty() ) {
+            for ( JcrResourceListener resourceListener : this.resourceListeners ) {
+                resourceListener.dispose();
+            }
+            this.resourceListeners = null;
+        }
+        this.resourceDecoratorTracker.close();
+    }
+
+    protected void bindResourceProvider(final ResourceProvider provider, final Map<String, Object> props) {
+        this.rootProviderEntry.bindResourceProvider(provider, props, this.eventAdminTracker);
+    }
+
+    protected void unbindResourceProvider(final ResourceProvider provider, final Map<String, Object> props) {
+        this.rootProviderEntry.unbindResourceProvider(provider, props, this.eventAdminTracker);
+    }
+
+    protected void bindResourceDecorator(final ResourceDecorator decorator, final Map<String, Object> props) {
+        this.resourceDecoratorTracker.bindResourceDecorator(decorator, props);
+    }
+
+    protected void unbindResourceDecorator(final ResourceDecorator decorator, final Map<String, Object> props) {
+        this.resourceDecoratorTracker.unbindResourceDecorator(decorator, props);
+    }
+
+    // ---------- internal helper ----------------------------------------------
+
+    /** Returns the JCR repository used by this factory */
+    protected SlingRepository getRepository() {
+        return repository;
+    }
+
+    /**
+     * Create a login exception from a repository exception.
+     * If the repository exception is a  {@link javax.jcr.LoginException}
+     * a {@link LoginException} is created with the same information.
+     * Otherwise a {@link LoginException} is created which wraps the
+     * repository exception.
+     * @param re The repository exception.
+     * @return The login exception.
+     */
+    private LoginException getLoginException(final RepositoryException re) {
+        if ( re instanceof javax.jcr.LoginException ) {
+            return new LoginException(re.getMessage(), re.getCause());
+        }
+        return new LoginException("Unable to login " + re.getMessage(), re);
+    }
+
+    /**
+     * Get an array of all workspaces.
+     */
+    private String[] getAllWorkspaces() throws RepositoryException {
+        Session session =  null;
+        try {
+            session = repository.loginAdministrative(null);
+            return session.getWorkspace().getAccessibleWorkspaceNames();
+        } finally {
+            if (session != null) {
+                session.logout();
+            }
+        }
+    }
+
+    /**
+     * Returns the session provided as the user.jcr.session property of the
+     * <code>authenticationInfo</code> map or <code>null</code> if the
+     * property is not contained in the map or is not a <code>javax.jcr.Session</code>.
+     * @param authenticationInfo Optional authentication info.
+     * @return The user.jcr.session property or <code>null</code>
+     */
+    private Session getSession(final Map<String, Object> authenticationInfo) {
+        if (authenticationInfo != null) {
+            final Object sessionObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_SESSION);
+            if (sessionObject instanceof Session) {
+                return (Session) sessionObject;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the workspace name.
+     * If the workspace name is provided, it is returned, otherwise
+     * <code>null</code> is returned.
+     * @param authenticationInfo Optional authentication info.
+     * @return The configured workspace name or <code>null</code>
+     */
+    private String getWorkspace(final Map<String, Object> authenticationInfo) {
+        if (authenticationInfo != null) {
+            final Object workspaceObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE);
+            if (workspaceObject instanceof String) {
+                return (String) workspaceObject;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return the sudo user information.
+     * If the sudo user info is provided, it is returned, otherwise
+     * <code>null</code> is returned.
+     * @param authenticationInfo Optional authentication info.
+     * @return The configured sudo user information or <code>null</code>
+     */
+    private String getSudoUser(final Map<String, Object> authenticationInfo) {
+        if (authenticationInfo != null) {
+            final Object sudoObject = authenticationInfo.get(ResourceResolverFactory.USER_IMPERSONATION);
+            if (sudoObject instanceof String) {
+                return (String) sudoObject;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Handle the sudo if configured. If the authentication info does not
+     * contain a sudo info, this method simply returns the passed in session. If
+     * a sudo user info is available, the session is tried to be impersonated.
+     * The new impersonated session is returned. The original session is closed.
+     * The session is also closed if the impersonation fails.
+     *
+     * @param session The session.
+     * @param authenticationInfo The optional authentication info.
+     * @param logoutSession whether to logout the <code>session</code> after
+     *            impersonation or not.
+     * @return The original session or impersonated session.
+     * @throws LoginException If something goes wrong.
+     */
+    private Session handleImpersonation(final Session session,
+            final Map<String, Object> authenticationInfo, boolean logoutSession)
+            throws LoginException {
+        final String sudoUser = getSudoUser(authenticationInfo);
+        if (sudoUser != null && !session.getUserID().equals(sudoUser)) {
+            try {
+                final SimpleCredentials creds = new SimpleCredentials(sudoUser,
+                    new char[0]);
+                copyAttributes(creds, authenticationInfo);
+                creds.setAttribute(ResourceResolver.USER_IMPERSONATOR,
+                    session.getUserID());
+                return session.impersonate(creds);
+            } catch (RepositoryException re) {
+                throw getLoginException(re);
+            } finally {
+                if (logoutSession) {
+                    session.logout();
+                }
+            }
+        }
+        return session;
+    }
+
+    /**
+     * Create a credentials object from the provided authentication info.
+     * If no map is provided, <code>null</code> is returned.
+     * If a map is provided and contains a credentials object, this object is
+     * returned.
+     * If a map is provided but does not contain a credentials object nor a
+     * user, <code>null</code> is returned.
+     * if a map is provided with a user name but without a credentials object
+     * a new credentials object is created and all values from the authentication
+     * info are added as attributes.
+     * @param authenticationInfo Optional authentication info
+     * @return A credentials object or <code>null</code>
+     */
+    private Credentials getCredentials(final Map<String, Object> authenticationInfo) {
+        if (authenticationInfo == null) {
+            return null;
+        }
+
+        final Object credentialsObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS);
+        if (credentialsObject instanceof Credentials) {
+            return (Credentials) credentialsObject;
+        }
+
+        // otherwise try to create SimpleCredentials if the userId is set
+        final Object userId = authenticationInfo.get(USER);
+        if (userId instanceof String) {
+            final Object password = authenticationInfo.get(PASSWORD);
+            final SimpleCredentials credentials = new SimpleCredentials(
+                (String) userId, ((password instanceof char[])
+                        ? (char[]) password
+                        : new char[0]));
+
+            // add attributes
+            copyAttributes(credentials, authenticationInfo);
+
+            return credentials;
+        }
+
+        // no user id (or not a String)
+        return null;
+    }
+
+    /**
+     * Copies the contents of the source map as attributes into the target
+     * <code>SimpleCredentials</code> object with the exception of the
+     * <code>user.jcr.credentials</code> and <code>user.password</code>
+     * attributes to prevent leaking passwords into the JCR Session attributes
+     * which might be used for break-in attempts.
+     *
+     * @param target The <code>SimpleCredentials</code> object whose attributes
+     *            are to be augmented.
+     * @param source The map whose entries (except the ones listed above) are
+     *            copied as credentials attributes.
+     */
+    private void copyAttributes(final SimpleCredentials target,
+            final Map<String, Object> source) {
+        final Iterator<Map.Entry<String, Object>> i = source.entrySet().iterator();
+        while (i.hasNext()) {
+            final Map.Entry<String, Object> current = i.next();
+            if (isAttributeVisible(current.getKey())) {
+                target.setAttribute(current.getKey(), current.getValue());
+            }
+        }
+    }
+
+    /**
+     * Returns <code>true</code> unless the name is
+     * <code>user.jcr.credentials</code> (
+     * {@link JcrResourceConstants#AUTHENTICATION_INFO_CREDENTIALS}) or contains
+     * the string <code>password</code> as in <code>user.password</code> (
+     * {@link org.apache.sling.api.resource.ResourceResolverFactory#PASSWORD})
+     *
+     * @param name The name to check whether it is visible or not
+     * @return <code>true</code> if the name is assumed visible
+     * @throws NullPointerException if <code>name</code> is <code>null</code>
+     */
+    static boolean isAttributeVisible(final String name) {
+        return !name.equals(JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS)
+            && !name.contains("password");
+    }
+
+}
\ No newline at end of file

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverWebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverWebConsolePlugin.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverWebConsolePlugin.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/JcrResourceResolverWebConsolePlugin.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,385 @@
+/*
+ * 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.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.jcr.Session;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.request.ResponseUtil;
+import org.apache.sling.jcr.resource.internal.helper.MapEntries;
+import org.apache.sling.jcr.resource.internal.helper.MapEntry;
+import org.apache.sling.jcr.resource.internal.helper.URI;
+import org.apache.sling.jcr.resource.internal.helper.URIException;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+public class JcrResourceResolverWebConsolePlugin extends
+        HttpServlet {
+
+    private static final long serialVersionUID = 0;
+
+    private static final String ATTR_TEST = "plugin.test";
+
+    private static final String ATTR_SUBMIT = "plugin.submit";
+
+    private static final String PAR_MSG = "msg";
+    private static final String PAR_TEST = "test";
+
+    private final transient JcrResourceResolverFactoryImpl resolverFactory;
+
+    private transient ServiceRegistration service;
+
+    JcrResourceResolverWebConsolePlugin(BundleContext context,
+            JcrResourceResolverFactoryImpl resolverFactory) {
+        this.resolverFactory = resolverFactory;
+
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(Constants.SERVICE_DESCRIPTION,
+            "JCRResourceResolver Web Console Plugin");
+        props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+        props.put(Constants.SERVICE_PID, getClass().getName());
+        props.put("felix.webconsole.label", "jcrresolver");
+        props.put("felix.webconsole.title", "Sling Resource Resolver");
+        props.put("felix.webconsole.configprinter.modes", "always");
+
+        service = context.registerService(new String[] {
+                "javax.servlet.Servlet" },
+            this, props);
+    }
+
+    void dispose() {
+        if (service != null) {
+            service.unregister();
+            service = null;
+        }
+    }
+
+    @Override
+    protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
+    throws ServletException, IOException {
+        final String msg = request.getParameter(PAR_MSG);
+        final String test;
+        if ( msg != null ) {
+            test = request.getParameter(PAR_TEST);
+        } else {
+            test = null;
+        }
+
+        final PrintWriter pw = response.getWriter();
+
+        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
+
+        final MapEntries mapEntries = resolverFactory.getMapEntries();
+
+        titleHtml(pw, "Configuration", null);
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Resource Search Path</td>");
+        pw.print("<td class='content' colspan='2'>");
+        pw.print(Arrays.asList(resolverFactory.getSearchPath()).toString());
+        pw.print("</td>");
+        pw.println("</tr>");
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Namespace Mangling</td>");
+        pw.print("<td class='content' colspan='2'>");
+        pw.print(resolverFactory.isMangleNamespacePrefixes() ? "Enabled" : "Disabled");
+        pw.print("</td>");
+        pw.println("</tr>");
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Mapping Location</td>");
+        pw.print("<td class='content' colspan='2'>");
+        pw.print(resolverFactory.getMapRoot());
+        pw.print("</td>");
+        pw.println("</tr>");
+
+        separatorHtml(pw);
+
+        titleHtml(
+            pw,
+            "Configuration Test",
+            "To test the configuration, enter an URL or a resource path into " +
+            "the field and click 'Resolve' to resolve the URL or click 'Map' " +
+            "to map the resource path. To simulate a map call that takes the " +
+            "current request into account, provide a full URL whose " +
+            "scheme/host/port prefix will then be used as the request " +
+            "information. The path passed to map will always be the path part " +
+            "of the URL.");
+
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content'>Test</td>");
+        pw.print("<td class='content' colspan='2'>");
+        pw.print("<form method='post'>");
+        pw.print("<input type='text' name='" + ATTR_TEST + "' value='");
+        if ( test != null ) {
+            pw.print(ResponseUtil.escapeXml(test));
+        }
+        pw.println("' class='input' size='50'>");
+        pw.println("&nbsp;&nbsp;<input type='submit' name='" + ATTR_SUBMIT
+            + "' value='Resolve' class='submit'>");
+        pw.println("&nbsp;&nbsp;<input type='submit' name='" + ATTR_SUBMIT
+            + "' value='Map' class='submit'>");
+        pw.print("</form>");
+        pw.print("</td>");
+        pw.println("</tr>");
+
+        if (msg != null) {
+            pw.println("<tr class='content'>");
+            pw.println("<td class='content'>&nbsp;</td>");
+            pw.print("<td class='content' colspan='2'>");
+            pw.print(ResponseUtil.escapeXml(msg));
+            pw.println("</td>");
+            pw.println("</tr>");
+        }
+
+        separatorHtml(pw);
+
+        dumpMapHtml(
+            pw,
+            "Resolver Map Entries",
+            "Lists the entries used by the ResourceResolver.resolve methods to map URLs to Resources",
+            mapEntries.getResolveMaps());
+
+        separatorHtml(pw);
+
+        dumpMapHtml(
+            pw,
+            "Mapping Map Entries",
+            "Lists the entries used by the ResourceResolver.map methods to map Resource Paths to URLs",
+            mapEntries.getMapMaps());
+
+        pw.println("</table>");
+
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+
+        final String test = request.getParameter(ATTR_TEST);
+        String msg = null;
+        if (test != null && test.length() > 0) {
+
+            Session session = null;
+            try {
+                // prepare the request for the resource resolver
+                HttpServletRequest helper = new ResolverRequest(request, test);
+
+                // get the resource resolver with an administrative session
+                session = resolverFactory.getRepository().loginAdministrative(
+                    null);
+                ResourceResolver resolver = resolverFactory.getResourceResolver(session);
+
+                // map or resolve as instructed
+                Object result;
+                if ("Map".equals(request.getParameter(ATTR_SUBMIT))) {
+                    if (helper.getServerName() == null) {
+                        result = resolver.map(helper.getPathInfo());
+                    } else {
+                        result = resolver.map(helper, helper.getPathInfo());
+                    }
+                } else {
+                    result = resolver.resolve(helper, helper.getPathInfo());
+                }
+
+                // set the result to render the result
+                msg = result.toString();
+
+            } catch (final Throwable t) {
+
+                // some error occurred, report it as a result
+                msg = "Test Failure: " + t;
+
+            } finally {
+                if (session != null) {
+                    session.logout();
+                }
+            }
+
+        }
+
+        // finally redirect
+        final String path = request.getContextPath() + request.getServletPath() + request.getPathInfo();
+        final String redirectTo;
+        if ( msg == null ) {
+            redirectTo = path;
+        } else {
+            redirectTo = path + '?' + PAR_MSG + '=' + encodeParam(msg) + '&' + PAR_TEST + '=' + encodeParam(test);
+        }
+        response.sendRedirect(redirectTo);
+    }
+
+    private String encodeParam(final String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // should never happen
+            return value;
+        }
+    }
+
+    // ---------- ConfigurationPrinter
+
+    public void printConfiguration(PrintWriter pw) {
+        final MapEntries mapEntries = resolverFactory.getMapEntries();
+
+        dumpMapText(
+            pw,
+            "Resolver Map Entries",
+            mapEntries.getResolveMaps());
+
+         separatorText(pw);
+
+        dumpMapText(
+            pw,
+            "Mapping Map Entries",
+            mapEntries.getMapMaps());
+    }
+
+    // ---------- internal
+
+    private void dumpMapHtml(PrintWriter pw, String title, String description,
+            Collection<MapEntry> list) {
+
+        titleHtml(pw, title, description);
+
+        pw.println("<tr class='content'>");
+        pw.println("<th class='content'>Pattern</th>");
+        pw.println("<th class='content'>Replacement</th>");
+        pw.println("<th class='content'>Redirect</th>");
+        pw.println("</tr>");
+
+        for (MapEntry entry : list) {
+            pw.println("<tr class='content'>");
+            pw.println("<td class='content' style='vertical-align: top'>"
+                + entry.getPattern() + "</td>");
+
+            pw.print("<td class='content' style='vertical-align: top'>");
+            String[] repls = entry.getRedirect();
+            for (String repl : repls) {
+                pw.print(repl + "<br/>");
+            }
+            pw.println("</td>");
+
+            pw.print("<td class='content' style='vertical-align: top'>");
+            if (entry.isInternal()) {
+                pw.print("internal");
+            } else {
+                pw.print("external: " + entry.getStatus());
+            }
+            pw.println("</td>");
+
+        }
+    }
+
+    private void titleHtml(PrintWriter pw, String title, String description) {
+        pw.println("<tr class='content'>");
+        pw.println("<th colspan='3'class='content container'>" + title
+            + "</th>");
+        pw.println("</tr>");
+
+        if (description != null) {
+            pw.println("<tr class='content'>");
+            pw.println("<td colspan='3'class='content'>" + description
+                + "</th>");
+            pw.println("</tr>");
+        }
+    }
+
+    private void separatorHtml(PrintWriter pw) {
+        pw.println("<tr class='content'>");
+        pw.println("<td class='content' colspan='3'>&nbsp;</td>");
+        pw.println("</tr>");
+    }
+
+    private void dumpMapText(PrintWriter pw, String title,
+            Collection<MapEntry> list) {
+
+        pw.println(title);
+
+        final String format = "%25s%25s%15s\r\n";
+        pw.printf(format, "Pattern", "Replacement", "Redirect");
+
+        for (MapEntry entry : list) {
+            final List<String> redir = Arrays.asList(entry.getRedirect());
+            final String status = entry.isInternal()
+                    ? "internal"
+                    : "external: " + entry.getStatus();
+            pw.printf(format, entry.getPattern(), redir, status);
+        }
+    }
+
+    private void separatorText(PrintWriter pw) {
+        pw.println();
+    }
+
+    private static class ResolverRequest extends HttpServletRequestWrapper {
+
+        private final URI uri;
+
+        public ResolverRequest(HttpServletRequest request, String uriString)
+                throws URIException {
+            super(request);
+            uri = new URI(uriString, false);
+        }
+
+        @Override
+        public String getScheme() {
+            return uri.getScheme();
+        }
+
+        @Override
+        public String getServerName() {
+            try {
+                return uri.getHost();
+            } catch (URIException ue) {
+                return null;
+            }
+        }
+
+        @Override
+        public int getServerPort() {
+            return uri.getPort();
+        }
+
+        @Override
+        public String getPathInfo() {
+            try {
+                return uri.getPath();
+            } catch (URIException ue) {
+                return "";
+            }
+        }
+    }
+
+}

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceDecoratorTracker.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceDecoratorTracker.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceDecoratorTracker.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceDecoratorTracker.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,144 @@
+/*
+ * 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.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceDecorator;
+import org.apache.sling.commons.osgi.OsgiUtil;
+
+/**
+ * Helper class to track the resource decorators and keep
+ * them sorted by their service ranking.
+ */
+public class ResourceDecoratorTracker {
+
+    private static final ResourceDecorator[] EMPTY_ARRAY = new ResourceDecorator[0];
+
+    /**
+     * The (optional) resource decorators, working copy.
+     */
+    protected final List<ResourceDecoratorEntry> resourceDecorators = new ArrayList<ResourceDecoratorEntry>();
+
+    /**
+     * An array of the above, updates when changes are created.
+     */
+    private volatile ResourceDecorator[] resourceDecoratorsArray = EMPTY_ARRAY;
+
+    public void close() {
+        synchronized (this.resourceDecorators) {
+            this.resourceDecorators.clear();
+            this.resourceDecoratorsArray = EMPTY_ARRAY;
+        }
+    }
+
+    /** Decorate a resource.  */
+    public Resource decorate(final Resource resource, String workspaceName, final HttpServletRequest request) {
+        Resource result = resource;
+        final ResourceDecorator[] decorators = this.resourceDecoratorsArray;
+        for(final ResourceDecorator decorator : decorators) {
+            final Resource original = result;
+            if ( request == null ) {
+                result = decorator.decorate(original);
+            } else {
+                result = decorator.decorate(original, request);
+            }
+            if ( result == null ) {
+                result = original;
+            }
+        }
+        if (workspaceName != null) {
+            result = new WorkspaceDecoratedResource(result, workspaceName);
+        }
+        return result;
+    }
+
+    public ResourceDecorator[] getResourceDecorators() {
+        return this.resourceDecoratorsArray;
+    }
+
+    protected void bindResourceDecorator(final ResourceDecorator decorator, final Map<String, Object> props) {
+        synchronized (this.resourceDecorators) {
+            this.resourceDecorators.add(new ResourceDecoratorEntry(decorator, OsgiUtil.getComparableForServiceRanking(props)));
+            Collections.sort(this.resourceDecorators);
+            updateResourceDecoratorsArray();
+        }
+    }
+
+    protected void unbindResourceDecorator(final ResourceDecorator decorator, final Map<String, Object> props) {
+        synchronized (this.resourceDecorators) {
+            final Iterator<ResourceDecoratorEntry> i = this.resourceDecorators.iterator();
+            while (i.hasNext()) {
+                final ResourceDecoratorEntry current = i.next();
+                if (current.decorator == decorator) {
+                    i.remove();
+                    break;
+                }
+            }
+            updateResourceDecoratorsArray();
+        }
+    }
+
+    /**
+     * Updates the ResourceDecorators array, this method is not thread safe and should only be
+     * called from a synchronized block.
+     */
+    protected void updateResourceDecoratorsArray() {
+        final ResourceDecorator[] decorators;
+        if (this.resourceDecorators.size() > 0) {
+            decorators = new ResourceDecorator[this.resourceDecorators.size()];
+            int index = 0;
+            final Iterator<ResourceDecoratorEntry> i = this.resourceDecorators.iterator();
+            while (i.hasNext()) {
+                decorators[index] = i.next().decorator;
+                index++;
+            }
+        } else {
+            decorators = EMPTY_ARRAY;
+        }
+        this.resourceDecoratorsArray = decorators;
+    }
+
+    /**
+     * Internal class to keep track of the resource decorators.
+     */
+    private static final class ResourceDecoratorEntry implements Comparable<ResourceDecoratorEntry> {
+
+        final Comparable<Object> comparable;
+
+        final ResourceDecorator decorator;
+
+        public ResourceDecoratorEntry(final ResourceDecorator d,
+                final Comparable<Object> comparable) {
+            this.comparable = comparable;
+            this.decorator = d;
+        }
+
+        public int compareTo(ResourceDecoratorEntry o) {
+            return comparable.compareTo(o.comparable);
+        }
+    }
+}

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceIteratorDecorator.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceIteratorDecorator.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceIteratorDecorator.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/ResourceIteratorDecorator.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,55 @@
+/*
+ * 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.Iterator;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Resource iterator handling the decoration of resources.
+ */
+public class ResourceIteratorDecorator implements Iterator<Resource> {
+
+    private final ResourceDecoratorTracker tracker;
+
+    private final String workspaceName;
+
+    private final Iterator<Resource> iterator;
+
+    public ResourceIteratorDecorator(final ResourceDecoratorTracker tracker,
+            final String workspaceName,
+            final Iterator<Resource> iterator) {
+        this.tracker = tracker;
+        this.iterator = iterator;
+        this.workspaceName = workspaceName;
+    }
+
+    public boolean hasNext() {
+        return this.iterator.hasNext();
+    }
+
+    public Resource next() {
+        return this.tracker.decorate(this.iterator.next(), workspaceName, null);
+    }
+
+    public void remove() {
+        this.iterator.remove();
+    }
+}

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceAuthInfoPostProcessor.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceAuthInfoPostProcessor.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceAuthInfoPostProcessor.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceAuthInfoPostProcessor.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,75 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.auth.core.spi.AbstractAuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.AuthenticationInfoPostProcessor;
+import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.osgi.framework.Constants;
+
+/**
+ * The <code>WorkspaceAuthInfoPostProcessor</code> is a simple
+ * AuthenticationInfo post processor which sets the
+ * {@link JcrResourceConstants#AUTHENTICATION_INFO_WORKSPACE} property (unless
+ * set already) if the {@link #J_WORKSPACE} request attribute or parameter is
+ * set to a non-empty string.
+ * <p>
+ * This allows logging into any workspace for a given request provided the
+ * requested workspace exists.
+ */
+@Component
+@Service
+@Property(name = Constants.SERVICE_DESCRIPTION, value = "JCR Workspace property setter")
+public class WorkspaceAuthInfoPostProcessor implements AuthenticationInfoPostProcessor {
+
+    /**
+     * The name of the request parameter (or request attribute) indicating the
+     * workspace to use.
+     * <p>
+     * The {@link AuthenticationSupport} service implemented by this bundle will
+     * respect this parameter and attribute and ensure the
+     * <code>jcr.user.workspace</code> attribute of the
+     * {@link org.apache.sling.auth.core.spi.AuthenticationInfo} used for
+     * accessing the resource resolver is set to this value (unless the property
+     * has already been set by the
+     * {@link org.apache.sling.auth.core.spi.AuthenticationHandler} providing
+     * the {@link org.apache.sling.auth.core.spi.AuthenticationInfo} instance).
+     */
+    public static final String J_WORKSPACE = "j_workspace";
+
+    /**
+     * Sets the {@link JcrResourceConstants#AUTHENTICATION_INFO_WORKSPACE} if
+     * the {@link #J_WORKSPACE} request parameter or attribute is defined and
+     * the {@link JcrResourceConstants#AUTHENTICATION_INFO_WORKSPACE} does not
+     * exist yet in the authentication info.
+     */
+    public void postProcess(AuthenticationInfo info, HttpServletRequest request, HttpServletResponse response) {
+        final String workspace = AbstractAuthenticationHandler.getAttributeOrParameter(request, J_WORKSPACE, "");
+        if (workspace.length() > 0 && !info.containsKey(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE)) {
+            info.put(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE, workspace);
+        }
+    }
+}

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceDecoratedResource.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceDecoratedResource.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceDecoratedResource.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/WorkspaceDecoratedResource.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,41 @@
+/*
+ * 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 org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceWrapper;
+
+/**
+ * Decorated resource which prepends the workspace name to
+ * a delegate resource's path.
+ */
+class WorkspaceDecoratedResource extends ResourceWrapper {
+
+    private final String workspaceName;
+
+    WorkspaceDecoratedResource(Resource resource, String workspaceName) {
+        super(resource);
+        this.workspaceName = workspaceName;
+    }
+
+    public String getPath() {
+        if (workspaceName != null) {
+            return workspaceName + ":" + super.getPath();
+        }
+        return super.getPath();
+    }
+}

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,128 @@
+/*
+ * 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.helper;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.sling.jcr.resource.JcrResourceUtil;
+
+public class JcrPropertyMapCacheEntry {
+    public final Property property;
+    public final boolean isMulti;
+    public final Value[] values;
+
+    private final Object defaultValue;
+
+    /**
+     * Create a value for the object.
+     * If the value type is supported directly through a jcr property type,
+     * the corresponding value is created. If the value is serializable,
+     * it is serialized through an object stream. Otherwise null is returned.
+     */
+    private Value createValue(final Object obj, final Session session)
+    throws RepositoryException {
+        Value value = JcrResourceUtil.createValue(obj, session);
+        if ( value == null && obj instanceof Serializable ) {
+            try {
+                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                final ObjectOutputStream oos = new ObjectOutputStream(baos);
+                oos.writeObject(obj);
+                oos.close();
+                final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+                value = session.getValueFactory().createValue(bais);
+            } catch (IOException ioe) {
+                // we ignore this here and return null
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Create a new cache entry from a property.
+     */
+    public JcrPropertyMapCacheEntry(final Property prop)
+    throws RepositoryException {
+        this.property = prop;
+        if ( prop.getDefinition().isMultiple() ) {
+            isMulti = true;
+            values = prop.getValues();
+        } else {
+            isMulti = false;
+            values = new Value[] {prop.getValue()};
+        }
+        Object tmp = JcrResourceUtil.toJavaObject(prop);
+        if (isDefaultValueCacheable()) {
+            this.defaultValue = tmp;
+        } else {
+            this.defaultValue = null;
+        }
+    }
+
+    /**
+     * Create a new cache entry from a value.
+     */
+    public JcrPropertyMapCacheEntry(final Object value, final Session session)
+    throws RepositoryException {
+        this.property = null;
+        this.defaultValue = value;
+        if ( value.getClass().isArray() ) {
+            this.isMulti = true;
+            final Object[] values = (Object[])value;
+            this.values = new Value[values.length];
+            for(int i=0; i<values.length; i++) {
+                this.values[i] = this.createValue(values[i], session);
+                if ( this.values[i] == null ) {
+                    throw new IllegalArgumentException("Value can't be stored in the repository: " + values[i]);
+                }
+            }
+        } else {
+            this.isMulti = false;
+            this.values = new Value[] {this.createValue(value, session)};
+            if ( this.values[0] == null ) {
+                throw new IllegalArgumentException("Value can't be stored in the repository: " + value);
+            }
+        }
+    }
+
+    public Object getDefaultValue() throws RepositoryException {
+        return this.defaultValue != null ? this.defaultValue : JcrResourceUtil.toJavaObject(property);
+    }
+
+    public Object getDefaultValueOrNull() {
+        try {
+            return getDefaultValue();
+        } catch (RepositoryException e) {
+            return null;
+        }
+    }
+
+    private boolean isDefaultValueCacheable() throws RepositoryException {
+        return property.getType() != PropertyType.BINARY;
+    }
+    
+    
+}
\ No newline at end of file

Added: sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/LazyInputStream.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/LazyInputStream.java?rev=1239587&view=auto
==============================================================================
--- sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/LazyInputStream.java (added)
+++ sling/whiteboard/resourceresolverfactory/jcr-resource/src/main/java/org/apache/sling/jcr/resource/internal/helper/LazyInputStream.java Thu Feb  2 12:46:58 2012
@@ -0,0 +1,114 @@
+/*
+ * 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.helper;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/**
+ * Lazily acquired InputStream which only accesses the JCR Value InputStream if
+ * data is to be read from the stream.
+ */
+public class LazyInputStream extends InputStream {
+
+    /** The JCR Value from which the input stream is requested on demand */
+    private final Value value;
+
+    /** The inputstream created on demand, null if not used */
+    private InputStream delegatee;
+
+    public LazyInputStream(Value value) {
+        this.value = value;
+    }
+
+    /**
+     * Closes the input stream if acquired otherwise does nothing.
+     */
+    @Override
+    public void close() throws IOException {
+        if (delegatee != null) {
+            delegatee.close();
+        }
+    }
+
+    @Override
+    public int available() throws IOException {
+        return getStream().available();
+    }
+
+    @Override
+    public int read() throws IOException {
+        return getStream().read();
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        return getStream().read(b);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        return getStream().read(b, off, len);
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        return getStream().skip(n);
+    }
+
+    @Override
+    public boolean markSupported() {
+        try {
+            return getStream().markSupported();
+        } catch (IOException ioe) {
+            // ignore
+        }
+        return false;
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+        try {
+            getStream().mark(readlimit);
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        getStream().reset();
+    }
+
+    /** Actually retrieves the input stream from the underlying JCR Value */
+    private InputStream getStream() throws IOException {
+        if (delegatee == null) {
+            try {
+                delegatee = value.getStream();
+            } catch (RepositoryException re) {
+                throw (IOException) new IOException(re.getMessage()).initCause(re);
+            }
+        }
+        return delegatee;
+    }
+
+}



Mime
View raw message