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(" <input type='submit' name='" + ATTR_SUBMIT
+ + "' value='Resolve' class='submit'>");
+ pw.println(" <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'> </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'> </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;
+ }
+
+}
|