All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.jcr.base.AbstractSlingRepository Maven / Gradle / Ivy

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Dictionary;

import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Value;
import javax.jcr.Workspace;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.base.util.RepositoryAccessor;
import org.apache.sling.serviceusermapping.ServiceUserMapper;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;
import org.slf4j.LoggerFactory;

import aQute.bnd.annotation.ProviderType;

/**
 * The AbstractSlingRepository is an abstract implementation of the
 * {@link SlingRepository} interface which provides default support for attached
 * repositories as well as ensuring live repository connection, reconnecting if
 * needed. Implementations of the SlingRepository interface may
 * wish to extend this class to benefit from a default implementation.
 * 

* As of version 2.2 (bundle version 2.2.0) the registration of repository * services based on this abstract base class works differently. To be able to * know the calling bundle to implement the * {@link #loginService(String, String)} method the service is registered as a * service factory. Yet this component is registered as a non-service component * with Declarative Services handling its registration itself so the the * {@code ServiceFactory} cannot simply create instances of this class. The * solution is for the service factory to create a proxy to the actual component * object. All method calls are just routed through with the exception of the * {@link #loginService(String, String)} method which is routed to a new * internal method taking the calling bundle as an additional argument. *

* The changes to support this new registration mechanism are as follows: *

    *
  • The {@link #registerService()} method is now final.
  • *
  • The {@link #getServiceRegistrationInterfaces()} and * {@link #getServiceRegistrationProperties()} methods have been added and can * be overwritten by implementations of this class. The * {@link #registerService()} method calls these new methods to get the * interfaces and properties for the service registration.
  • *
* Implementations of this class overwriting the {@link #registerService()} * method must replace this overwritten method with overwriting the new * {@link #getServiceRegistrationInterfaces()} and/or * {@link #getServiceRegistrationProperties()} methods. * * @deprecated as of API version 2.3 (bundle version 2.2.2). Use * {@link AbstractSlingRepositoryManager} and * {@link AbstractSlingRepository2} instead. */ @Deprecated @ProviderType @Component(componentAbstract=true) public abstract class AbstractSlingRepository extends AbstractNamespaceMappingRepository implements SlingRepository, Runnable { public static final String DEFAULT_ANONYMOUS_USER = "anonymous"; public static final String DEFAULT_ANONYMOUS_PASS = "anonymous"; public static final String DEFAULT_ADMIN_USER = "admin"; public static final String DEFAULT_ADMIN_PASS = "admin"; // For backwards compatibility loginAdministrative is still enabled // In future releases, this default may change to false. public static final boolean DEFAULT_LOGIN_ADMIN_ENABLED = true; @Property public static final String PROPERTY_DEFAULT_WORKSPACE = "defaultWorkspace"; @Property(value=DEFAULT_ANONYMOUS_USER) public static final String PROPERTY_ANONYMOUS_USER = "anonymous.name"; @Property(value=DEFAULT_ANONYMOUS_PASS) public static final String PROPERTY_ANONYMOUS_PASS = "anonymous.password"; @Property(value=DEFAULT_ADMIN_USER) public static final String PROPERTY_ADMIN_USER = "admin.name"; @Property(value=DEFAULT_ADMIN_PASS) public static final String PROPERTY_ADMIN_PASS = "admin.password"; @Property(boolValue = DEFAULT_LOGIN_ADMIN_ENABLED) public static final String PROPERTY_LOGIN_ADMIN_ENABLED = "admin.login.enabled"; /** * The default value for the number of seconds to wait between two * consecutive checks while the repository is active (value is 10). */ public static final int DEFAULT_POLL_ACTIVE = 10; /** * The default value for the number of seconds to wait between two * consecutive checks while the repository is not active (value is 10). */ public static final int DEFAULT_POLL_INACTIVE = 10; @Property(intValue=DEFAULT_POLL_ACTIVE) public static final String PROPERTY_POLL_ACTIVE = "poll.active"; @Property(intValue=DEFAULT_POLL_INACTIVE) public static final String PROPERTY_POLL_INACTIVE = "poll.inactive"; /** The minimum number of seconds allowed for any of the two poll times */ public static final int MIN_POLL = 2; @Reference private LogService log; @Reference() private ServiceUserMapper serviceUserMapper; private ComponentContext componentContext; private Repository repository; private ServiceRegistration repositoryService; private String defaultWorkspace; private String anonUser; private char[] anonPass; private String adminUser; private char[] adminPass; private boolean disableLoginAdministrative; // the poll interval used while the repository is not active private long pollTimeInActiveSeconds; // the poll interval used while the repository is active private long pollTimeActiveSeconds; // whether the repository checker task should be active. this field // is managed by the startRepositoryPinger and stopRepositoryPinger methods private boolean running; // the background thread constantly checking the repository private Thread repositoryPinger; protected AbstractSlingRepository() { } /** * Returns the default workspace, which may be null meaning * to use the repository provided default workspace. Declared final to make * sure the SLING-256 rule is enforced. */ @Override public final String getDefaultWorkspace() { return defaultWorkspace; } private void setDefaultWorkspace(String defaultWorkspace) { // normalize the default workspace name: trim leading and trailing // blanks and set to null in case the trimmed name is empty if (defaultWorkspace != null) { defaultWorkspace = defaultWorkspace.trim(); if (defaultWorkspace.length() == 0) { defaultWorkspace = null; } } log(LogService.LOG_DEBUG, "setDefaultWorkspace: Setting the default workspace to " + defaultWorkspace); this.defaultWorkspace = defaultWorkspace; } /** * Logs in as an anonymous user. This implementation simply returns the * result of calling {@link #login(Credentials, String)} */ @Override public Session login() throws LoginException, RepositoryException { return this.login(null, null); } @Override public final Session loginAdministrative(String workspace) throws RepositoryException { if (this.disableLoginAdministrative) { log(LogService.LOG_ERROR, "SlingRepository.loginAdministrative is disabled. Please use SlingRepository.loginService."); throw new LoginException(); } log(LogService.LOG_WARNING, "SlingRepository.loginAdministrative is deprecated. Please use SlingRepository.loginService."); return loginAdministrativeInternal(workspace); } /** * This method always throws {@code LoginException} because it does * not directly have the calling bundle at its disposition to decide * on the required service name. *

* This method is final and cannot be overwritten by extensions. See the * class comments for full details on how this works. * * @since 2.2 (bundle version 2.2.0) */ @Override public final Session loginService(String subServiceName, String workspace) throws LoginException, RepositoryException { log(LogService.LOG_ERROR, "loginService: Cannot get using Bundle because this SlingRepository service is not a ServiceFactory"); throw new LoginException(); } @Override public Session login(Credentials credentials) throws LoginException, RepositoryException { return this.login(credentials, null); } @Override public Session login(String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException { return this.login(null, workspace); } @Override public Session login(Credentials credentials, String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException { // if already stopped, don't retrieve a session if (this.componentContext == null || this.getRepository() == null) { throw new RepositoryException("Sling Repository not ready"); } if (credentials == null) { credentials = getAnonCredentials(this.anonUser); } // check the workspace if (workspace == null) { workspace = this.getDefaultWorkspace(); } try { log(LogService.LOG_DEBUG, "login: Logging in to workspace '" + workspace + "'"); Session session = getRepository().login(credentials, workspace); // if the defualt workspace is null, acquire a session from the pool // and use the workspace used as the new default workspace if (workspace == null) { String defaultWorkspace = session.getWorkspace().getName(); log(LogService.LOG_DEBUG, "login: Using " + defaultWorkspace + " as the default workspace instead of 'null'"); setDefaultWorkspace(defaultWorkspace); } return this.getNamespaceAwareSession(session); } catch (NoSuchWorkspaceException nswe) { // if the desired workspace is the default workspace, try to create // (but not if using the repository-supplied default workspace) if (workspace != null && workspace.equals(this.getDefaultWorkspace()) && this.createWorkspace(workspace)) { return this.getRepository().login(credentials, workspace); } // otherwise (any workspace) or if workspace creation fails // just forward the original exception throw nswe; } catch (RuntimeException re) { // SLING-702: Jackrabbit throws IllegalStateException if the // repository has already been shut down ... throw new RepositoryException(re.getMessage(), re); } } /** * Actual implementation of the {@link #loginService(String, String)} method * taking into account the bundle calling this method. *

* This method is final and cannot be overwritten by extensions. See the * class comments for full details on how this works. * * @param usingBundle The bundle requesting access * @param subServiceName Subservice name (may be {@code null}) * @param workspace The workspace to access * @return The session authenticated with the service user * @throws LoginException If authentication fails or if no user is defined * for the requesting service (bundle) * @throws RepositoryException If a general error occurrs creating the * session * * @since 2.2 (bundle version 2.2.0) */ final Session loginService(final Bundle usingBundle, final String subServiceName, final String workspace) throws LoginException, RepositoryException { final String userName = this.serviceUserMapper.getServiceUserID(usingBundle, subServiceName); if (userName == null) { throw new LoginException("Cannot derive user name for bundle " + usingBundle + " and sub service " + subServiceName); } final SimpleCredentials creds = new SimpleCredentials(userName, new char[0]); Session admin = null; try { admin = this.loginAdministrativeInternal(workspace); return admin.impersonate(creds); } finally { if (admin != null) { admin.logout(); } } } /** * Actual (unprotected) implementation of administrative login. *

* This methods is internally used to administratively login. * * @param workspace The workspace to login to (or {@code null} to use the * {@link #getDefaultWorkspace() default workspace}. * @return The administrative session * @throws RepositoryException if an error occurrs. */ protected Session loginAdministrativeInternal(String workspace) throws RepositoryException { Credentials sc = getAdministrativeCredentials(this.adminUser); return this.login(sc, workspace); } /** * @param anonUser the user name of the anon user. * @return a Credentials implementation that represents the anon user. */ protected Credentials getAnonCredentials(String anonUser) { // NB: this method is overridden in the Jackrabbit Service bundle to avoid using the anon password. SLING-1282 return new SimpleCredentials(anonUser, anonPass); } /** * @param adminUser the name of the administrative user. * @return a Credentials implementation that represents the administrative user. */ protected Credentials getAdministrativeCredentials(String adminUser){ // NB: this method is overridden in the Jackrabbit Service bundle to avoid using the admin password. SLING-1282 return new SimpleCredentials(adminUser, adminPass); } /* * (non-Javadoc) * * @see javax.jcr.Repository#getDescriptor(java.lang.String) */ @Override public String getDescriptor(String name) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptor(name); } log(LogService.LOG_ERROR, "getDescriptor: Repository not available"); return null; } /* * (non-Javadoc) * * @see javax.jcr.Repository#getDescriptorKeys() */ @Override public String[] getDescriptorKeys() { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorKeys(); } log(LogService.LOG_ERROR, "getDescriptorKeys: Repository not available"); return new String[0]; } /** * {@inheritDoc} */ @Override public Value getDescriptorValue(String key) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorValue(key); } log(LogService.LOG_ERROR, "getDescriptorValue: Repository not available"); return null; } /** * {@inheritDoc} */ @Override public Value[] getDescriptorValues(String key) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorValues(key); } log(LogService.LOG_ERROR, "getDescriptorValues: Repository not available"); return null; } /** * {@inheritDoc} */ @Override public boolean isSingleValueDescriptor(String key) { Repository repo = getRepository(); if (repo != null) { return repo.isSingleValueDescriptor(key); } log(LogService.LOG_ERROR, "isSingleValueDescriptor: Repository not available"); return false; } /** * {@inheritDoc} */ @Override public boolean isStandardDescriptor(String key) { Repository repo = getRepository(); if (repo != null) { return repo.isStandardDescriptor(key); } log(LogService.LOG_ERROR, "isStandardDescriptor: Repository not available"); return false; } // ---------- logging ------------------------------------------------------ protected void log(int level, String message) { this.log(level, message, null); } protected void log(int level, String message, Throwable t) { LogService log = this.log; if (log != null) { if (componentContext != null) { log.log(componentContext.getServiceReference(), level, message, t); } else { log.log(level, message, t); } } } // ---------- Repository Access ------------------------------------------- /** * Returns a new instance of the {@link RepositoryAccessor} class to access * a repository over RMI or through JNDI. *

* Extensions of this method may return an extension of the * {@link RepositoryAccessor} class if the provide extended functionality. */ protected RepositoryAccessor getRepositoryAccessor() { return new RepositoryAccessor(); } /** * Acquires the repository by calling the * {@link org.apache.sling.jcr.base.util.RepositoryAccessor#getRepositoryFromURL(String)} * with the value of the * {@link org.apache.sling.jcr.base.util.RepositoryAccessor#REPOSITORY_URL_OVERRIDE_PROPERTY} * framework or configuration property. If the property exists and a * repository can be accessed using this property, that repository is * returned. Otherwise null is returned. *

* Extensions of this class may overwrite this method with implementation * specific acquisition semantics and may call this base class method or not * as the implementation sees fit. *

* This method does not throw any Throwable but instead just * returns null if not repository is available. Any problems * trying to acquire the repository must be caught and logged as * appropriate. * * @return The acquired JCR Repository or null * if not repository can be acquired. */ protected Repository acquireRepository() { // if the environment provides a repository override URL, other settings // are ignored String overrideUrl = (String) componentContext.getProperties().get( RepositoryAccessor.REPOSITORY_URL_OVERRIDE_PROPERTY); if (overrideUrl == null) { overrideUrl = componentContext.getBundleContext().getProperty( RepositoryAccessor.REPOSITORY_URL_OVERRIDE_PROPERTY); } if (overrideUrl != null && overrideUrl.length() > 0) { log(LogService.LOG_INFO, "acquireRepository: Will not use embedded repository due to property " + RepositoryAccessor.REPOSITORY_URL_OVERRIDE_PROPERTY + "=" + overrideUrl + ", acquiring repository using that URL"); return getRepositoryAccessor().getRepositoryFromURL(overrideUrl); } log(LogService.LOG_DEBUG, "acquireRepository: No existing repository to access"); return null; } /** * This method is called after a repository has been acquired by * {@link #acquireRepository()} but before the repository is registered as a * service. *

* Implementations may overwrite this method but MUST call this base class * implementation first. * * @param repository The JCR Repository to setup. */ protected void setupRepository(Repository repository) { this.setup(componentContext.getBundleContext()); } /** * Registers this component as an OSGi service with the types provided by * the {@link #getServiceRegistrationInterfaces()} method and properties * provided by the {@link #getServiceRegistrationProperties()} method. *

* As of version 2.2 (bundle version 2.2.0) this method is final and cannot * be overwritten because the mechanism of service registration using a * service factory is required to fully implement the * {@link #loginService(String, String)} method. See the class comments for * full details on how this works. * * @return The OSGi ServiceRegistration object representing the * registered service. */ protected final ServiceRegistration registerService() { final Dictionary props = getServiceRegistrationProperties(); final String[] interfaces = getServiceRegistrationInterfaces(); return componentContext.getBundleContext().registerService(interfaces, new ServiceFactory() { @Override public Object getService(Bundle bundle, ServiceRegistration registration) { return SlingRepositoryProxyHandler.createProxy(interfaces, AbstractSlingRepository.this, bundle); } @Override public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) { // nothing to do (GC does the work for us) } }, props); } /** * Return the service registration properties to be used to register the * repository service in {@link #registerService()}. *

* This method may be overwritten to return additional service registration * properties. But it is strongly recommended to always include the * properties returned from this method. * * @return The service registration properties to be used to register the * repository service in {@link #registerService()} * * @since 2.2 (bundle version 2.2.0) */ @SuppressWarnings("unchecked") protected Dictionary getServiceRegistrationProperties() { return componentContext.getProperties(); } /** * Returns the service types to be used to register the repository service * in {@link #registerService()}. All interfaces returned must be accessible * to the class loader of the class of this instance. *

* This method may be overwritten to return additional types but the types * returned from this base implementation must always be included. * * @return The service types to be used to register the repository service * in {@link #registerService()} * * @since 2.2 (bundle version 2.2.0) */ protected String[] getServiceRegistrationInterfaces() { return new String[] { SlingRepository.class.getName(), Repository.class.getName() }; } /** * Returns the repository underlying this instance or null if * no repository is currently being available. */ protected Repository getRepository() { return repository; } /** * Checks that the given repository is still available. This * implementation tries to get the Repository.SPEC_NAME_DESC * descriptor from the repository and returns true if the * returned value is not null. *

* Extensions of this class may overwrite this method to implement different * access checks. The contract of this method must be obeyed, though in a * sense, the true must only be returned if * repository is actually usable. * * @param repository The JCR Repository to check for * availability. * @return true if repository is not * null and accessible. */ protected boolean pingRepository(Repository repository) { if (repository != null) { try { return repository.getDescriptor(Repository.SPEC_NAME_DESC) != null; } catch (Throwable t) { log(LogService.LOG_DEBUG, "pingRepository: Repository " + repository + " does not seem to be available any more", t); } } // fall back to unavailable return false; } /** Ping our current repository and check that admin login (required by Sling) works. */ protected boolean pingAndCheck() { if(repository == null) { throw new IllegalStateException("Repository is null"); } boolean result = false; if(pingRepository(repository)) { try { final Session s = loginAdministrativeInternal(getDefaultWorkspace()); s.logout(); result = true; } catch(RepositoryException re) { log.log(LogService.LOG_INFO, "pingAndCheck; loginAdministrative failed", re); } } return result; } /** * Unregisters the service represented by the * serviceRegistration. *

* This method may be overwritten by extensions of this class as long as it * is made sure, the given service registration is unregistered. */ protected void unregisterService(ServiceRegistration serviceRegistration) { serviceRegistration.unregister(); } /** * Performs any cleanups before the repository is actually disposed off by * the {@link #disposeRepository(Repository)} method. *

* This method is meant for cleanup tasks before the repository is actually * disposed off. Extensions of this class may overwrite but must call this * base class implementation. * * @param repository */ protected void tearDown(Repository repository) { this.tearDown(); } /** * Disposes off the given repository. This base class * implementation does nothing. Extensions should overwrite if any special * disposal operation is required. * * @param repository */ protected void disposeRepository(Repository repository) { // nothing to do here ... } // --------- SCR integration ----------------------------------------------- protected ComponentContext getComponentContext() { return this.componentContext; } /** * This method must be called if overwritten by implementations !! * * @throws nothing, but allow derived classes to throw any Exception */ protected void activate(final ComponentContext componentContext) throws Exception { this.componentContext = componentContext; @SuppressWarnings("unchecked") Dictionary properties = componentContext.getProperties(); setDefaultWorkspace(this.getProperty(properties, PROPERTY_DEFAULT_WORKSPACE, null)); this.anonUser = this.getProperty(properties, PROPERTY_ANONYMOUS_USER, DEFAULT_ANONYMOUS_USER); this.anonPass = this.getProperty(properties, PROPERTY_ANONYMOUS_PASS, DEFAULT_ANONYMOUS_PASS).toCharArray(); this.adminUser = this.getProperty(properties, PROPERTY_ADMIN_USER, DEFAULT_ADMIN_USER); this.adminPass = this.getProperty(properties, PROPERTY_ADMIN_PASS, DEFAULT_ADMIN_PASS).toCharArray(); this.disableLoginAdministrative = !this.getProperty(properties, PROPERTY_LOGIN_ADMIN_ENABLED, DEFAULT_LOGIN_ADMIN_ENABLED); setPollTimeActive(getIntProperty(properties, PROPERTY_POLL_ACTIVE)); setPollTimeInActive(getIntProperty(properties, PROPERTY_POLL_INACTIVE)); // immediately try to start the repository while activating // this component instance try { if (startRepository()) { log(LogService.LOG_INFO, "Repository started successfully"); } else { log(LogService.LOG_WARNING, "Repository startup failed, will try later"); } } catch (Throwable t) { log(LogService.LOG_WARNING, "activate: Unexpected problem starting repository", t); } // launch the background repository checker now startRepositoryPinger(); } /** * This method must be called if overwritten by implementations !! * * @param componentContext */ protected void deactivate(final ComponentContext componentContext) { // stop the background thread stopRepositoryPinger(); // ensure the repository is really disposed off if (repository != null || repositoryService != null) { log(LogService.LOG_INFO, "deactivate: Repository still running, forcing shutdown"); try { stopRepository(); } catch (Throwable t) { log(LogService.LOG_WARNING, "deactivate: Unexpected problem stopping repository", t); } } this.componentContext = null; } protected void bindLog(LogService log) { this.log = log; } protected void unbindLog(LogService log) { if (this.log == log) { this.log = null; } } // ---------- internal ----------------------------------------------------- private String getProperty(Dictionary properties, String name, String defaultValue) { Object prop = properties.get(name); return (prop instanceof String) ? (String) prop : defaultValue; } private int getIntProperty(Dictionary properties, String name) { Object prop = properties.get(name); if (prop instanceof Number) { return ((Number) prop).intValue(); } else if (prop != null) { try { return Integer.decode(String.valueOf(prop)).intValue(); } catch (NumberFormatException nfe) { // don't really care } } return -1; } private boolean getProperty(Dictionary properties, String name, boolean defaultValue) { Object prop = properties.get(name); if (prop instanceof Boolean) { return ((Boolean) prop).booleanValue(); } else if (prop instanceof String) { return Boolean.valueOf((String) prop); } return defaultValue; } private boolean createWorkspace(String workspace) { this.log(LogService.LOG_INFO, "createWorkspace: Requested workspace " + workspace + " does not exist, trying to create"); Session tmpSession = null; try { Credentials sc = getAdministrativeCredentials(this.adminUser); tmpSession = this.getRepository().login(sc); Workspace defaultWs = tmpSession.getWorkspace(); defaultWs.createWorkspace(workspace); return true; } catch (Throwable t) { this.log(LogService.LOG_ERROR, "createWorkspace: Cannot create requested workspace " + workspace, t); } finally { if (tmpSession != null) { tmpSession.logout(); } } // fall back to failure return false; } // ---------- Background operation checking repository availability -------- private void setPollTimeActive(int seconds) { if (seconds < MIN_POLL) { seconds = DEFAULT_POLL_ACTIVE; } pollTimeActiveSeconds = seconds; } private void setPollTimeInActive(int seconds) { if (seconds < MIN_POLL) { seconds = DEFAULT_POLL_INACTIVE; } pollTimeInActiveSeconds = seconds; } private void startRepositoryPinger() { if (repositoryPinger == null) { // make sure the ping will be running running = true; // create and start the thread repositoryPinger = new Thread(this, "Repository Pinger"); repositoryPinger.start(); } } private void stopRepositoryPinger() { // make sure the thread is terminating running = false; // nothing to do if the thread is not running at all Thread rpThread = repositoryPinger; if (rpThread == null) { return; } // clear the repositoryPinger thread field repositoryPinger = null; // notify the thread for it to be able to shut down synchronized (rpThread) { rpThread.notifyAll(); } // wait at most 10 seconds for the thread to terminate try { rpThread.join(10000L); } catch (InterruptedException ie) { // don't care here } // consider it an error if the thread is still running !! if (rpThread.isAlive()) { log(LogService.LOG_ERROR, "stopRepositoryPinger: Timed waiting for thread " + rpThread + " to terminate"); } } private boolean startRepository() { try { log(LogService.LOG_DEBUG, "startRepository: calling acquireRepository()"); Repository newRepo = acquireRepository(); if (newRepo != null) { // ensure we really have the repository log(LogService.LOG_DEBUG, "startRepository: got a Repository, calling pingRepository()"); if (pingRepository(newRepo)) { repository = newRepo; if(pingAndCheck()) { log(LogService.LOG_DEBUG, "startRepository: pingRepository() and pingAndCheck() successful, calling setupRepository()"); setupRepository(newRepo); log(LogService.LOG_DEBUG, "startRepository: calling registerService()"); repositoryService = registerService(); log(LogService.LOG_DEBUG, "registerService() successful, registration=" + repositoryService); return true; } // ping succeeded but pingAndCheck fail, we have to drop // the repository in this situation and restart from // scratch later log( LogService.LOG_DEBUG, "pingRepository() successful but pingAndCheck() fails, calling disposeRepository()"); // drop reference repository = null; } else { // otherwise let go of the repository and fail startup log(LogService.LOG_DEBUG, "startRepository: pingRepository() failed, calling disposeRepository()"); } // ping or pingAndCheck failed: dispose off repository disposeRepository(newRepo); } } catch (Throwable t) { // consider an uncaught problem an error log( LogService.LOG_ERROR, "startRepository: Uncaught Throwable trying to access Repository, calling stopRepository()", t); // repository might be partially started, stop anything left stopRepository(); } return false; } private void stopRepository() { if (repositoryService != null) { try { log(LogService.LOG_DEBUG, "Unregistering SlingRepository service, registration=" + repositoryService); unregisterService(repositoryService); } catch (Throwable t) { log( LogService.LOG_INFO, "stopRepository: Uncaught problem unregistering the repository service", t); } repositoryService = null; } if (repository != null) { Repository oldRepo = repository; repository = null; try { tearDown(oldRepo); } catch (Throwable t) { log( LogService.LOG_INFO, "stopRepository: Uncaught problem tearing down the repository", t); } try { disposeRepository(oldRepo); } catch (Throwable t) { log( LogService.LOG_INFO, "stopRepository: Uncaught problem disposing the repository", t); } } } @Override public void run() { // start polling with a small value to be faster at system startup // we'll increase the polling time after each try long pollTimeMsec = 100L; final long MSEC = 1000L; final int pollTimeFactor = 2; Object waitLock = repositoryPinger; try { while (running) { // wait first before starting to check synchronized (waitLock) { try { // no debug logging, see SLING-505 // log(LogService.LOG_DEBUG, "Waiting " + pollTime + " seconds before checking repository"); waitLock.wait(pollTimeMsec); } catch (InterruptedException ie) { // don't care, go ahead } } long newPollTime = pollTimeMsec; if (running) { Repository repo = repository; boolean ok = false; if (repo == null) { // No Repository yet, try to start if (startRepository()) { log(LogService.LOG_INFO, "Repository started successfully"); ok = true; newPollTime = pollTimeActiveSeconds * MSEC; } else { // ramp up poll time, up to the max of our configured times newPollTime = Math.min(pollTimeMsec * pollTimeFactor, Math.max(pollTimeInActiveSeconds, pollTimeActiveSeconds) * MSEC); } } else if (pingAndCheck()) { ok = true; newPollTime = pollTimeActiveSeconds * MSEC; } else { // Repository disappeared log(LogService.LOG_INFO, "run: Repository not accessible anymore, unregistering service"); stopRepository(); newPollTime = pollTimeInActiveSeconds * MSEC; } if(newPollTime != pollTimeMsec) { pollTimeMsec = newPollTime; log(LogService.LOG_DEBUG, "Repository Pinger interval set to " + pollTimeMsec + " msec, repository is " + (ok ? "available" : "NOT available") ); } } } // thread is terminating due to "running" being set to false log(LogService.LOG_INFO, "Repository Pinger stopping on request"); } catch (Throwable t) { // try to log the cause for thread termination log(LogService.LOG_ERROR, "Repository Pinger caught unexpected issue", t); } finally { // whatever goes on, make sure the repository is disposed of // at the end of the thread.... log(LogService.LOG_INFO, "Stopping repository on shutdown"); stopRepository(); } } /** * The SlingRepositoryProxyHandler class implements a proxy for all * service interfaces under which the {@link AbstractSlingRepository} * implementation is registered. *

* All calls a directly handed through to the object except for the * {@code loginService} call which is routed through * {@code AbstractSlingRepository.loginService(Bundle, String, String)} method * to influence logging in by the calling bundle. * * @since 2.2 (bundle version 2.2.0) */ private static class SlingRepositoryProxyHandler implements InvocationHandler { // The name of the method to re-route private static final String LOGIN_SERVICE_NAME = "loginService"; // The delegatee object to which all calls are routed private final AbstractSlingRepository delegatee; // The bundle using this proxy service instance private final Bundle usingBundle; /** * Creates a new proxy instance for the given {@code delegatee} object. The * proxy is handled by a new instance of this * {@code SlingRepositoryProxyHandler} handler. * * @param interfaceNames The list of interfaces to implement and expose in * the proxy * @param delegatee The object to which to route all method calls * @param usingBundle The bundle making use of the proxy * @return The proxy to be used by client code or {@code null} if not all * service interfaces can be loaded by the class loader of the * {@code delegatee} object. */ static Object createProxy(final String[] interfaceNames, final AbstractSlingRepository delegatee, final Bundle usingBundle) { // get the interface classes to create the proxy final ClassLoader cl = delegatee.getClass().getClassLoader(); final Class[] interfaces = new Class[interfaceNames.length]; for (int i = 0; i < interfaces.length; i++) { try { interfaces[i] = cl.loadClass(interfaceNames[i]); } catch (ClassNotFoundException e) { LoggerFactory.getLogger(SlingRepositoryProxyHandler.class).error( "createProxy: Cannot load interface class " + interfaceNames[i], e); return null; } } // create the proxy final InvocationHandler handler = new SlingRepositoryProxyHandler(delegatee, usingBundle); return Proxy.newProxyInstance(cl, interfaces, handler); } private SlingRepositoryProxyHandler(final AbstractSlingRepository delegatee, final Bundle usingBundle) { this.delegatee = delegatee; this.usingBundle = usingBundle; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (SlingRepositoryProxyHandler.LOGIN_SERVICE_NAME.equals(method.getName()) && args != null && args.length == 2) { return this.delegatee.loginService(this.usingBundle, (String) args[0], (String) args[1]); } // otherwise forward to the AbstractSlingRepository implementation try { return method.invoke(this.delegatee, args); } catch (InvocationTargetException ite) { throw ite.getTargetException(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy