Package org.apache.sling.jcr.resource.internal.helper.jcr

Source Code of org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderFactory

/*
* 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.jcr;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executor;

import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.query.Query;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
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.Service;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.QueriableResourceProvider;
import org.apache.sling.api.resource.ResourceProvider;
import org.apache.sling.api.resource.ResourceProviderFactory;
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.PropertiesUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.apache.sling.jcr.resource.internal.JcrResourceListener;
import org.apache.sling.jcr.resource.internal.OakResourceListener;
import org.apache.sling.jcr.resource.internal.ObservationListenerSupport;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The <code>JcrResourceProviderFactory</code> creates
* resource providers based on JCR.
*/
@Component(metatype = true,
           label = "Apache Sling JCR Resource Provider Factory",
           description = "This provider adds  JCR resources to the resource tree")
@Service(value = ResourceProviderFactory.class )
@Properties({
    @Property(name=ResourceProviderFactory.PROPERTY_REQUIRED, boolValue=true, propertyPrivate=true),
    @Property(name=ResourceProvider.ROOTS, value="/", propertyPrivate=true),
    @Property(name = Constants.SERVICE_DESCRIPTION, value = "Apache Sling JCR Resource Provider Factory"),
    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
    @Property(name = QueriableResourceProvider.LANGUAGES, value = {Query.XPATH, Query.SQL, Query.JCR_SQL2}, propertyPrivate=true)
})
public class JcrResourceProviderFactory implements ResourceProviderFactory {

    /** TODO - this is a copy from ResourceResolverFactory to avoid a dependency to the new 2.8.2 API just for this constants.
     * This should be replaced once we update.
     */
    private static final String NEW_PASSWORD = "user.newpassword";

    /** Logger */
    private final Logger log = LoggerFactory.getLogger(getClass());

    private static final boolean DEFAULT_OPTIMIZE_FOR_OAK = true;
    @Property(boolValue=DEFAULT_OPTIMIZE_FOR_OAK,
              label="Optimize For Oak",
              description="If this switch is enabled, and Oak is used as the repository implementation, some optimized components are used.")
    private static final String PROPERTY_OPTIMIZE_FOR_OAK = "optimize.oak";

    private static final String REPOSITORY_REFERNENCE_NAME = "repository";

    /** The dynamic class loader */
    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC)
    private volatile DynamicClassLoaderManager dynamicClassLoaderManager;

    @Reference(name = REPOSITORY_REFERNENCE_NAME, referenceInterface = SlingRepository.class)
    private ServiceReference repositoryReference;

    private SlingRepository repository;

    /** This service is only available on OAK, therefore optional and static) */
    @Reference(policy=ReferencePolicy.STATIC, cardinality=ReferenceCardinality.OPTIONAL_UNARY)
    private Executor executor;

    /** The JCR observation listener. */
    private Closeable listener;

    @Activate
    protected void activate(final ComponentContext context) throws RepositoryException {

        SlingRepository repository = (SlingRepository) context.locateService(REPOSITORY_REFERNENCE_NAME,
            this.repositoryReference);
        if (repository == null) {
            // concurrent unregistration of SlingRepository service
            // don't care, this component is going to be deactivated
            // so we just stop working
            log.warn("activate: Activation failed because SlingRepository may have been unregistered concurrently");
            return;
        }

        this.repository = repository;
        // check for Oak
        final boolean optimizeForOak = PropertiesUtil.toBoolean(context.getProperties().get(PROPERTY_OPTIMIZE_FOR_OAK), DEFAULT_OPTIMIZE_FOR_OAK);
        boolean isOak = false;
        if ( optimizeForOak ) {
            final String repoDesc = this.repository.getDescriptor(Repository.REP_NAME_DESC);
            if ( repoDesc != null && repoDesc.toLowerCase().contains(" oak") ) {
                if ( this.executor != null ) {
                    isOak = true;
                } else {
                   log.error("Detected Oak based repository but no executor service available! Unable to use improved JCR Resource listener");
                }
            }
        }
        final String root = PropertiesUtil.toString(context.getProperties().get(ResourceProvider.ROOTS), "/");
        final ObservationListenerSupport support = new ObservationListenerSupport(context.getBundleContext(), repository);
        boolean closeSupport = true;
        try {
            if ( isOak ) {
                try {
                    this.listener = new OakResourceListener(root, support, context.getBundleContext(), executor);
                    log.info("Detected Oak based repository. Using improved JCR Resource Listener");
                } catch ( final RepositoryException re ) {
                    throw re;
                } catch ( final Throwable t ) {
                    log.error("Unable to instantiate improved JCR Resource listener for Oak. Using fallback.", t);
                }
            }
            if ( this.listener == null ) {
                this.listener = new JcrResourceListener(root, support);
            }
            closeSupport = false;
        } finally {
            if ( closeSupport ) {
                support.dispose();
            }
        }
    }

    @Deactivate
    protected void deactivate() {
        if ( this.listener != null ) {
            try {
                this.listener.close();
            } catch (final IOException e) {
                // ignore this as the method above does not throw it
            }
            this.listener = null;
        }
    }

    /** Get the dynamic class loader if available */
    ClassLoader getDynamicClassLoader() {
        final DynamicClassLoaderManager dclm = this.dynamicClassLoaderManager;
        if (dclm != null) {
            return dclm.getDynamicClassLoader();
        }
        return null;
    }

    @SuppressWarnings("unused")
    private void bindRepository(final ServiceReference ref) {
        this.repositoryReference = ref;
        this.repository = null; // make sure ...
    }

    @SuppressWarnings("unused")
    private void unbindRepository(final ServiceReference ref) {
        if (this.repositoryReference == ref) {
            this.repositoryReference = null;
            this.repository = null; // make sure ...
        }
    }

    /**
     * @see org.apache.sling.api.resource.ResourceProviderFactory#getResourceProvider(java.util.Map)
     */
    public ResourceProvider getResourceProvider(final Map<String, Object> authenticationInfo)
    throws LoginException {
        return getResourceProviderInternal(authenticationInfo, false);
    }

    /**
     * @see org.apache.sling.api.resource.ResourceProviderFactory#getAdministrativeResourceProvider(java.util.Map)
     */
    public ResourceProvider getAdministrativeResourceProvider(final Map<String, Object> authenticationInfo)
    throws LoginException {
        return getResourceProviderInternal(authenticationInfo, true);
    }

    /**
     * Create a new ResourceResolver wrapping a Session object. Carries map of
     * authentication info in order to create a new resolver as needed.
     */
    private ResourceProvider getResourceProviderInternal(
            Map<String, Object> authenticationInfo, final boolean isAdmin)
            throws LoginException {

        // Make sure authenticationInfo is not null
        if (authenticationInfo == null) {
            authenticationInfo = Collections.emptyMap();
        }

        // by default any session used by the resource resolver returned is
        // closed when the resource resolver is closed
        boolean logoutSession = true;
        RepositoryHolder holder = new RepositoryHolder();

        // derive the session to be used
        Session session;
        try {
            final String workspace = getWorkspace(authenticationInfo);
            if (isAdmin) {

                /*
                 * This implements the deprecated getAdministrativeResourceResolver
                 * method which is implemented in terms of the deprecated
                 * SlingRepository.loginAdministrative(String) method. Either
                 * one can fail, so it is ok to use the deprecated method
                 * here instead of a properly configured call to
                 * SlingRepository.loginService(String, String)
                 */

                // requested admin session to any workspace (or default)
                session = repository.loginAdministrative(workspace);

            } else {

                session = getSession(authenticationInfo);
                if (session == null) {

                    final Object serviceBundleObject = authenticationInfo.get(SERVICE_BUNDLE);
                    if (serviceBundleObject instanceof Bundle) {

                        final String subServiceName = (authenticationInfo.get(ResourceResolverFactory.SUBSERVICE) instanceof String)
                                ? (String) authenticationInfo.get(ResourceResolverFactory.SUBSERVICE)
                                : null;

                        final BundleContext bc = ((Bundle) serviceBundleObject).getBundleContext();

                        final SlingRepository repo = (SlingRepository) bc.getService(repositoryReference);
                        if (repo == null) {
                            log.warn(
                                "getResourceProviderInternal: Cannot login service because cannot get SlingRepository on behalf of bundle {} ({})",
                                bc.getBundle().getSymbolicName(), bc.getBundle().getBundleId());
                            throw new LoginException(); // TODO: correct ??
                        }

                        try {
                            session = repo.loginService(subServiceName, workspace);
                            holder.setRepositoryReference(bc, repositoryReference);
                            holder.setSession(session);
                        } finally {
                            // unget the repository if the service cannot
                            // login to it, otherwise the repository service
                            // is let go off when the resource resolver is
                            // closed and the session logged out
                            if (session == null) {
                                bc.ungetService(repositoryReference);
                            }
                        }

                    } else {

                        // requested non-admin session to any workspace (or
                        // default)
                        final Credentials credentials = getCredentials(authenticationInfo);
                        session = repository.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 {

                        /*
                         * TODO: Instead of using the deprecated loginAdministrative
                         * method, this bundle could be configured with an appropriate
                         * user for service authentication and do:
                         *     tmpSession = repository.loginService(null, workspace);
                         * For now, we keep loginAdministrative
                         */

                        tmpSession = repository.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 (final RepositoryException re) {
            throw getLoginException(re);
        }

        session = handleImpersonation(session, authenticationInfo, logoutSession);

        if (logoutSession) {
            holder.setSession(session);
        }

        return new JcrResourceProvider(session, this.getDynamicClassLoader(), holder);
    }

    /**
     * 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,
            final 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 (final RepositoryException re) {
                throw getLoginException(re);
            } finally {
                if (logoutSession) {
                    session.logout();
                }
            }
        }
        return session;
    }

    /**
     * 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);
    }

    /**
     * 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) {

        Credentials creds = null;
        if (authenticationInfo != null) {

            final Object credentialsObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS);

            if (credentialsObject instanceof Credentials) {
                creds = (Credentials) credentialsObject;
            } else {
                // otherwise try to create SimpleCredentials if the userId is set
                final Object userId = authenticationInfo.get(ResourceResolverFactory.USER);
                if (userId instanceof String) {
                    final Object password = authenticationInfo.get(ResourceResolverFactory.PASSWORD);
                    final SimpleCredentials credentials = new SimpleCredentials(
                            (String) userId, ((password instanceof char[])
                            ? (char[]) password
                            : new char[0]));

                    // add attributes
                    copyAttributes(credentials, authenticationInfo);

                    creds = credentials;
                }
            }
        }

        if (creds instanceof SimpleCredentials
                && authenticationInfo.get(NEW_PASSWORD) instanceof String) {
            ((SimpleCredentials) creds).setAttribute(NEW_PASSWORD, authenticationInfo.get(NEW_PASSWORD));
        }

        return creds;
    }

    /**
     * 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>
     */
    public static boolean isAttributeVisible(final String name) {
        return !name.equals(JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS)
            && !name.contains("password");
    }

    /**
     * Return the sudo user information.
     * If the sudo user info is provided, it is returned, otherwise
     * <code>null</code> is returned.
     * @param authenticationInfo Authentication info (not {@code null}).
     * @return The configured sudo user information or <code>null</code>
     */
    private String getSudoUser(final Map<String, Object> authenticationInfo) {
        final Object sudoObject = authenticationInfo.get(ResourceResolverFactory.USER_IMPERSONATION);
        if (sudoObject instanceof String) {
            return (String) sudoObject;
        }
        return null;
    }

    /**
     * Return the workspace name.
     * If the workspace name is provided, it is returned, otherwise
     * <code>null</code> is returned.
     * @param authenticationInfo Authentication info (not {@code null}).
     * @return The configured workspace name or <code>null</code>
     */
    private String getWorkspace(final Map<String, Object> authenticationInfo) {
        final Object workspaceObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_WORKSPACE);
        if (workspaceObject instanceof String) {
            return (String) workspaceObject;
        }
        return null;
    }

    /**
     * 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 Authentication info (not {@code null}).
     * @return The user.jcr.session property or <code>null</code>
     */
    private Session getSession(final Map<String, Object> authenticationInfo) {
        final Object sessionObject = authenticationInfo.get(JcrResourceConstants.AUTHENTICATION_INFO_SESSION);
        if (sessionObject instanceof Session) {
            return (Session) sessionObject;
        }
        return null;
    }
}
TOP

Related Classes of org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.