| /* |
| * 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.installer.provider.jcr.impl; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import javax.jcr.Node; |
| import javax.jcr.NodeIterator; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.observation.Event; |
| import javax.jcr.observation.EventIterator; |
| import javax.jcr.observation.EventListener; |
| |
| import org.apache.felix.cm.file.ConfigurationHandler; |
| 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.Service; |
| import org.apache.sling.installer.api.InstallableResource; |
| import org.apache.sling.installer.api.OsgiInstaller; |
| import org.apache.sling.installer.api.UpdateHandler; |
| import org.apache.sling.installer.api.UpdateResult; |
| import org.apache.sling.jcr.api.SlingRepository; |
| import org.apache.sling.settings.SlingSettingsService; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.cm.ConfigurationException; |
| import org.osgi.service.cm.ManagedService; |
| import org.osgi.service.component.ComponentConstants; |
| import org.osgi.service.component.ComponentContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Main class of jcrinstall, runs as a service, observes the |
| * repository for changes in folders having names that match |
| * configurable regular expressions, and registers resources |
| * found in those folders with the OSGi installer for installation. |
| */ |
| @Component(label="%jcrinstall.name", description="%jcrinstall.description", immediate=true, metatype=true) |
| @Properties({ |
| @Property(name=Constants.SERVICE_DESCRIPTION, value="Sling JCR Install Service"), |
| @Property(name=Constants.SERVICE_VENDOR, value="The Apache Software Foundation"), |
| @Property(name=UpdateHandler.PROPERTY_SCHEMES, value=JcrInstaller.URL_SCHEME, unbounded=PropertyUnbounded.ARRAY), |
| @Property(name=Constants.SERVICE_RANKING, intValue=100) |
| }) |
| @Service(value=UpdateHandler.class) |
| public class JcrInstaller implements UpdateHandler, ManagedService { |
| |
| public static final long RUN_LOOP_DELAY_MSEC = 500L; |
| public static final String URL_SCHEME = "jcrinstall"; |
| |
| /** PID before refactoring. */ |
| public static final String OLD_PID = "org.apache.sling.jcr.install.impl.JcrInstaller"; |
| |
| private final Logger logger = LoggerFactory.getLogger(getClass()); |
| |
| /** Counters, used for statistics and testing */ |
| private final long [] counters = new long[COUNTERS_COUNT]; |
| public static final int SCAN_FOLDERS_COUNTER = 0; |
| public static final int UPDATE_FOLDERS_LIST_COUNTER = 1; |
| public static final int RUN_LOOP_COUNTER = 2; |
| public static final int COUNTERS_COUNT = 3; |
| |
| private static final String NT_FILE = "nt:file"; |
| private static final String NT_RESOURCE = "nt:resource"; |
| private static final String PROP_DATA = "jcr:data"; |
| private static final String PROP_MODIFIED = "jcr:lastModified"; |
| private static final String PROP_ENC = "jcr:encoding"; |
| private static final String PROP_MIME = "jcr:mimeType"; |
| private static final String MIME_TXT = "text/plain"; |
| private static final String ENCODING = "UTF-8"; |
| |
| /** Default regexp for watched folders */ |
| public static final String DEFAULT_FOLDER_NAME_REGEXP = ".*/install$"; |
| |
| /** |
| * ComponentContext property that overrides the folder name regexp |
| */ |
| @Property(value=DEFAULT_FOLDER_NAME_REGEXP) |
| public static final String FOLDER_NAME_REGEXP_PROPERTY = "sling.jcrinstall.folder.name.regexp"; |
| |
| public static final int DEFAULT_FOLDER_MAX_DEPTH = 4; |
| |
| /** |
| * Configurable max. path depth for watched folders |
| */ |
| @Property(intValue=DEFAULT_FOLDER_MAX_DEPTH) |
| public static final String PROP_INSTALL_FOLDER_MAX_DEPTH = "sling.jcrinstall.folder.max.depth"; |
| |
| /** |
| * Configurable search path, with per-path priorities. |
| * We could get it from the ResourceResolver, but introducing a dependency on this just to get those |
| * values is too much for this module that's meant to bootstrap other services. |
| */ |
| @Property(value={"/libs:100", "/apps:200"}, unbounded=PropertyUnbounded.ARRAY) |
| public static final String PROP_SEARCH_PATH = "sling.jcrinstall.search.path"; |
| public static final String [] DEFAULT_SEARCH_PATH = { "/libs:100", "/apps:200" }; |
| |
| public static final String DEFAULT_NEW_CONFIG_PATH = "sling/install"; |
| @Property(value=DEFAULT_NEW_CONFIG_PATH) |
| public static final String PROP_NEW_CONFIG_PATH = "sling.jcrinstall.new.config.path"; |
| |
| public static final String PAUSE_SCAN_NODE_PATH = "/system/sling/installer/jcr/pauseInstallation"; |
| @Property(value= PAUSE_SCAN_NODE_PATH) |
| public static final String PROP_SCAN_PROP_PATH = "sling.jcrinstall.signal.path"; |
| |
| private volatile boolean pauseMessageLogged = false; |
| |
| public static final boolean DEFAULT_ENABLE_WRITEBACK = true; |
| @Property(boolValue=DEFAULT_ENABLE_WRITEBACK) |
| public static final String PROP_ENABLE_WRITEBACK = "sling.jcrinstall.enable.writeback"; |
| |
| /** |
| * This class watches the repository for installable resources |
| */ |
| @Reference |
| private SlingRepository repository; |
| |
| /** |
| * Additional installation folders are activated based |
| * on the current RunMode. For example, /libs/foo/install.dev |
| * if the current run mode is "dev". |
| */ |
| @Reference |
| private SlingSettingsService settings; |
| |
| /** |
| * The OsgiInstaller installs resources in the OSGi framework. |
| */ |
| @Reference |
| private OsgiInstaller installer; |
| |
| /** The component context. */ |
| private volatile ComponentContext componentContext; |
| |
| /** Service reg for managed service. */ |
| private volatile ServiceRegistration managedServiceRef; |
| |
| /** Configuration from managed service (old pid) */ |
| private volatile Dictionary<?, ?> oldConfiguration; |
| |
| /** Convert Nodes to InstallableResources */ |
| static interface NodeConverter { |
| InstallableResource convertNode(Node n, int priority) |
| throws RepositoryException; |
| } |
| |
| /** Timer used to call updateFoldersList() */ |
| private final RescanTimer updateFoldersListTimer = new RescanTimer(); |
| |
| /** Thread that can be cleanly stopped with a flag */ |
| private final static AtomicInteger bgThreadCounter = new AtomicInteger(); |
| |
| class StoppableThread extends Thread implements EventListener { |
| |
| /** Used for synchronizing. */ |
| final Object lock = new Object(); |
| |
| final AtomicBoolean active = new AtomicBoolean(false); |
| |
| private final AtomicBoolean running = new AtomicBoolean(false); |
| |
| private final InstallerConfig cfg; |
| |
| /** Detect newly created folders that we must watch */ |
| private final List<RootFolderListener> listeners = new LinkedList<RootFolderListener>(); |
| |
| private volatile EventListener moveEventListener; |
| |
| /** Session shared by all WatchedFolder */ |
| private volatile Session session; |
| |
| StoppableThread(final InstallerConfig cfg) throws RepositoryException { |
| this.cfg = cfg; |
| setName("JcrInstaller." + String.valueOf(bgThreadCounter.incrementAndGet())); |
| setDaemon(true); |
| |
| try { |
| // open session |
| session = repository.loginAdministrative(repository.getDefaultWorkspace()); |
| |
| for (final String path : cfg.getRoots()) { |
| listeners.add(new RootFolderListener(session, path, updateFoldersListTimer)); |
| logger.debug("Configured root folder: {}", path); |
| } |
| |
| // Watch for events on the root - that might be one of our root folders |
| session.getWorkspace().getObservationManager().addEventListener(this, |
| Event.NODE_ADDED | Event.NODE_REMOVED, |
| "/", |
| false, // isDeep |
| null, |
| null, |
| true); // noLocal |
| // add special observation listener for move events |
| this.moveEventListener = new EventListener() { |
| |
| /** |
| * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) |
| */ |
| public void onEvent(final EventIterator events) { |
| try { |
| while (events.hasNext()) { |
| final Event e = events.nextEvent(); |
| checkChanges(e.getIdentifier()); |
| checkChanges(e.getPath()); |
| } |
| } catch (final RepositoryException re) { |
| logger.warn("RepositoryException in onEvent", re); |
| } |
| } |
| }; |
| session.getWorkspace().getObservationManager().addEventListener( |
| moveEventListener, |
| Event.NODE_MOVED, |
| "/", |
| true, // isDeep |
| null, |
| null, |
| true); // noLocal |
| |
| logger.debug("Watching for node events on / to detect removal/add of our root folders"); |
| |
| |
| // Find paths to watch and create WatchedFolders to manage them |
| for(final String root : cfg.getRoots()) { |
| findPathsToWatch(cfg, session, root); |
| } |
| |
| // Scan watchedFolders and register resources with installer |
| final List<InstallableResource> resources = new LinkedList<InstallableResource>(); |
| for(final WatchedFolder f : cfg.getWatchedFolders()) { |
| f.start(); |
| final WatchedFolder.ScanResult r = f.scan(); |
| logger.debug("Startup: {} provides resources {}", f, r.toAdd); |
| resources.addAll(r.toAdd); |
| } |
| logger.debug("Registering {} resources with OSGi installer: {}", resources.size(), resources); |
| installer.registerResources(URL_SCHEME, resources.toArray(new InstallableResource[resources.size()])); |
| this.active.set(true); |
| } finally { |
| if ( !this.active.get() ) { |
| shutdown(); |
| } |
| } |
| } |
| |
| public void shutdown() { |
| while ( running.get() ) { |
| try { |
| Thread.sleep(10); |
| } catch (final InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| try { |
| if (session != null) { |
| for(final RootFolderListener wfc : listeners) { |
| wfc.cleanup(session); |
| } |
| session.getWorkspace().getObservationManager().removeEventListener(this); |
| if ( moveEventListener != null ) { |
| session.getWorkspace().getObservationManager().removeEventListener(moveEventListener); |
| moveEventListener = null; |
| } |
| } |
| } catch (final RepositoryException e) { |
| logger.warn("Exception in stop()", e); |
| } |
| if ( session != null ) { |
| session.logout(); |
| session = null; |
| } |
| listeners.clear(); |
| } |
| |
| /** |
| * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) |
| */ |
| public void onEvent(final EventIterator it) { |
| // Got a DELETE or ADD on root - schedule folders rescan if one |
| // of our root folders is impacted |
| try { |
| while(it.hasNext()) { |
| final Event e = it.nextEvent(); |
| logger.debug("Got event {}", e); |
| |
| this.checkChanges(e.getPath()); |
| } |
| } catch(final RepositoryException re) { |
| logger.warn("RepositoryException in onEvent", re); |
| } |
| } |
| |
| /** |
| * Check for changes in any of the root folders |
| */ |
| private void checkChanges(final String path) { |
| for(final String root : cfg.getRoots()) { |
| if (path.startsWith(root)) { |
| logger.info("Got event for root {}, scheduling scanning of new folders", root); |
| updateFoldersListTimer.scheduleScan(); |
| } |
| } |
| } |
| |
| @Override |
| public final void run() { |
| logger.info("Background thread {} starting", Thread.currentThread().getName()); |
| while (this.active.get()) { |
| running.set(true); |
| try { |
| runOneCycle(cfg, session); |
| } finally { |
| running.set(false); |
| } |
| } |
| logger.info("Background thread {} done", Thread.currentThread().getName()); |
| counters[RUN_LOOP_COUNTER] = -1; |
| } |
| |
| public InstallerConfig getConfiguration() { |
| return this.cfg; |
| } |
| }; |
| private volatile StoppableThread backgroundThread; |
| |
| /** |
| * Activate this component. |
| */ |
| protected void activate(final ComponentContext context) { |
| this.componentContext = context; |
| this.start(); |
| final Dictionary<String, Object> props = new Hashtable<String, Object>(); |
| props.put(Constants.SERVICE_PID, OLD_PID); |
| this.managedServiceRef = this.componentContext.getBundleContext().registerService(ManagedService.class.getName(), |
| this, |
| props); |
| } |
| |
| private void start() { |
| logger.info("Activating Apache Sling JCR Installer"); |
| final InstallerConfig cfg = new InstallerConfig(logger, componentContext, oldConfiguration, settings); |
| |
| try { |
| this.backgroundThread = new StoppableThread(cfg); |
| backgroundThread.start(); |
| } catch (final RepositoryException re) { |
| logger.error("Repository exception during startup - deactivating installer!", re); |
| final ComponentContext ctx = componentContext; |
| if ( ctx != null ) { |
| final String name = (String) componentContext.getProperties().get( |
| ComponentConstants.COMPONENT_NAME); |
| ctx.disableComponent(name); |
| } |
| } |
| } |
| |
| /** |
| * Deactivate this component |
| */ |
| protected void deactivate(final ComponentContext context) { |
| if ( this.managedServiceRef != null ) { |
| this.managedServiceRef.unregister(); |
| this.managedServiceRef = null; |
| } |
| this.stop(); |
| this.componentContext = null; |
| } |
| |
| private void stop() { |
| logger.info("Deactivating Apache Sling JCR Installer"); |
| |
| if ( backgroundThread != null ) { |
| synchronized ( backgroundThread.lock ) { |
| backgroundThread.active.set(false); |
| backgroundThread.lock.notify(); |
| } |
| logger.debug("Waiting for " + backgroundThread.getName() + " Thread to end..."); |
| |
| this.backgroundThread.shutdown(); |
| this.backgroundThread = null; |
| } |
| } |
| |
| |
| /** |
| * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary) |
| */ |
| public void updated(@SuppressWarnings("rawtypes") Dictionary properties) |
| throws ConfigurationException { |
| final boolean restart; |
| if ( this.oldConfiguration == null ) { |
| restart = properties != null; |
| } else { |
| restart = true; |
| } |
| this.oldConfiguration = properties; |
| if ( restart ) { |
| try { |
| this.stop(); |
| this.start(); |
| } catch (final Exception e) { |
| logger.error("Error restarting", e); |
| } |
| } |
| } |
| |
| /** Find the paths to watch under rootPath, according to our folderNameFilter, |
| * and add them to result */ |
| private void findPathsToWatch(final InstallerConfig cfg, final Session session, |
| final String rootPath) throws RepositoryException { |
| Session s = null; |
| |
| try { |
| s = repository.loginAdministrative(repository.getDefaultWorkspace()); |
| if (!s.itemExists(rootPath) || !s.getItem(rootPath).isNode() ) { |
| logger.info("Bundles root node {} not found, ignored", rootPath); |
| } else { |
| logger.debug("Bundles root node {} found, looking for bundle folders inside it", rootPath); |
| final Node n = (Node)s.getItem(rootPath); |
| findPathsUnderNode(cfg, session, n); |
| } |
| } finally { |
| if (s != null) { |
| s.logout(); |
| } |
| } |
| } |
| |
| /** |
| * Add n to result if it is a folder that we must watch, and recurse into its children |
| * to do the same. |
| */ |
| void findPathsUnderNode(final InstallerConfig cfg, final Session session, |
| final Node n) throws RepositoryException { |
| final String path = n.getPath(); |
| final int priority = cfg.getFolderNameFilter().getPriority(path); |
| if (priority > 0) { |
| addWatchedFolder(cfg, new WatchedFolder(session, path, priority, cfg.getConverters())); |
| } |
| final int depth = path.split("/").length; |
| if(depth > cfg.getMaxWatchedFolderDepth()) { |
| logger.debug("Not recursing into {} due to maxWatchedFolderDepth={}", path, cfg.getMaxWatchedFolderDepth()); |
| return; |
| } |
| final NodeIterator it = n.getNodes(); |
| while (it.hasNext()) { |
| findPathsUnderNode(cfg, session, it.nextNode()); |
| } |
| } |
| |
| /** |
| * Add WatchedFolder to our list if it doesn't exist yet. |
| */ |
| private void addWatchedFolder(final InstallerConfig cfg, final WatchedFolder toAdd) |
| throws RepositoryException { |
| WatchedFolder existing = null; |
| for(WatchedFolder wf : cfg.getWatchedFolders()) { |
| if (wf.getPath().equals(toAdd.getPath())) { |
| existing = wf; |
| break; |
| } |
| } |
| if (existing == null) { |
| toAdd.start(); |
| cfg.getWatchedFolders().add(toAdd); |
| } |
| } |
| |
| /** |
| * Add new folders to watch if any have been detected |
| * @return a list of InstallableResource that must be unregistered, |
| * for folders that have been removed |
| */ |
| private List<String> updateFoldersList(final InstallerConfig cfg, final Session session) throws Exception { |
| logger.debug("Updating folder list."); |
| |
| for(String root : cfg.getRoots()) { |
| findPathsToWatch(cfg, session, root); |
| } |
| |
| // Check all WatchedFolder, in case some were deleted |
| final List<String> removedResources = new LinkedList<String>(); |
| |
| final Iterator<WatchedFolder> i = cfg.getWatchedFolders().iterator(); |
| while ( i.hasNext() ) { |
| final WatchedFolder wf = i.next(); |
| |
| logger.debug("Item {} exists? {}", wf.getPath(), session.itemExists(wf.getPath())); |
| if (!session.itemExists(wf.getPath())) { |
| logger.info("Deleting {}, path does not exist anymore", wf); |
| removedResources.addAll(wf.scan().toRemove); |
| wf.stop(); |
| i.remove(); |
| } |
| } |
| |
| return removedResources; |
| } |
| |
| InstallerConfig getConfiguration() { |
| InstallerConfig cfg = null; |
| final StoppableThread st = this.backgroundThread; |
| if ( st != null ) { |
| cfg = st.getConfiguration(); |
| } |
| return cfg; |
| } |
| |
| Session getSession() { |
| return this.backgroundThread.session; |
| } |
| |
| /** |
| * Run periodic scans of our watched folders, and watch for folders creations/deletions. |
| */ |
| public void runOneCycle(final InstallerConfig cfg, final Session session) { |
| logger.debug("Running watch cycle."); |
| |
| try { |
| boolean didRefresh = false; |
| |
| if (anyWatchFolderNeedsScan(cfg)) { |
| session.refresh(false); |
| didRefresh = true; |
| if (scanningIsPaused(cfg, session)) { |
| if (!pauseMessageLogged) { |
| //Avoid flooding the logs every 500 msec so log at info level once |
| logger.info("Detected signal for pausing the JCR Provider i.e. child nodes found under path {}. " + |
| "JCR Provider scanning would not be performed", cfg.getPauseScanNodePath()); |
| pauseMessageLogged = true; |
| } |
| return; |
| } else if (pauseMessageLogged) { |
| pauseMessageLogged = false; |
| } |
| } |
| |
| // Rescan WatchedFolders if needed |
| boolean scanWf = false; |
| for(final WatchedFolder wf : cfg.getWatchedFolders()) { |
| if (!wf.needsScan()) { |
| continue; |
| } |
| scanWf = true; |
| if ( !didRefresh ) { |
| session.refresh(false); |
| didRefresh = true; |
| } |
| counters[SCAN_FOLDERS_COUNTER]++; |
| final WatchedFolder.ScanResult sr = wf.scan(); |
| boolean toDo = false; |
| if ( sr.toAdd.size() > 0 ) { |
| logger.info("Registering resource with OSGi installer: {}",sr.toAdd); |
| toDo = true; |
| } |
| if ( sr.toRemove.size() > 0 ) { |
| logger.info("Removing resource from OSGi installer: {}", sr.toRemove); |
| toDo = true; |
| } |
| if ( toDo ) { |
| installer.updateResources(URL_SCHEME, sr.toAdd.toArray(new InstallableResource[sr.toAdd.size()]), |
| sr.toRemove.toArray(new String[sr.toRemove.size()])); |
| } |
| } |
| |
| // Update list of WatchedFolder if we got any relevant events, |
| // or if there were any WatchedFolder events |
| if (scanWf || updateFoldersListTimer.expired()) { |
| if (!didRefresh) { |
| session.refresh(false); |
| didRefresh = true; |
| } |
| updateFoldersListTimer.reset(); |
| counters[UPDATE_FOLDERS_LIST_COUNTER]++; |
| final List<String> toRemove = updateFoldersList(cfg, session); |
| if ( toRemove.size() > 0 ) { |
| logger.info("Removing resource from OSGi installer (folder deleted): {}", toRemove); |
| installer.updateResources(URL_SCHEME, null, |
| toRemove.toArray(new String[toRemove.size()])); |
| } |
| } |
| |
| |
| } catch (final Exception e) { |
| logger.warn("Exception in runOneCycle()", e); |
| } |
| |
| if ( backgroundThread.active.get() ) { |
| synchronized ( backgroundThread.lock ) { |
| try { |
| backgroundThread.lock.wait(RUN_LOOP_DELAY_MSEC); |
| } catch (final InterruptedException ignore) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| counters[RUN_LOOP_COUNTER]++; |
| } |
| |
| boolean scanningIsPaused(final InstallerConfig cfg, final Session session) throws RepositoryException { |
| if (session.nodeExists(cfg.getPauseScanNodePath())) { |
| Node node = session.getNode(cfg.getPauseScanNodePath()); |
| boolean result = node.hasNodes(); |
| if (result && logger.isDebugEnabled()) { |
| List<String> nodeNames = new ArrayList<String>(); |
| NodeIterator childItr = node.getNodes(); |
| while (childItr.hasNext()) { |
| nodeNames.add(childItr.nextNode().getName()); |
| } |
| logger.debug("Found child nodes {} at path {}. Scanning would be paused", nodeNames, cfg.getPauseScanNodePath()); |
| } |
| return result; |
| } |
| return false; |
| } |
| |
| private boolean anyWatchFolderNeedsScan(final InstallerConfig cfg) { |
| for (WatchedFolder wf : cfg.getWatchedFolders()) { |
| if (wf.needsScan()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| long [] getCounters() { |
| return counters; |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.UpdateHandler#handleRemoval(java.lang.String, java.lang.String, java.lang.String) |
| */ |
| public UpdateResult handleRemoval(final String resourceType, |
| final String id, |
| final String url) { |
| // get configuration |
| final InstallerConfig cfg = this.getConfiguration(); |
| if ( cfg == null || !cfg.isWriteBack() ) { |
| return null; |
| } |
| final int pos = url.indexOf(':'); |
| final String path = url.substring(pos + 1); |
| |
| // check path (SLING-2407) |
| // 0. Check protocol |
| if ( !url.startsWith(URL_SCHEME) ) { |
| logger.debug("Not removing unmanaged artifact from repository: {}", url); |
| return null; |
| } |
| // 1. Is this a system configuration then don't delete |
| final String[] rootPaths = cfg.getFolderNameFilter().getRootPaths(); |
| final String systemConfigRootPath = rootPaths[rootPaths.length - 1]; |
| if ( path.startsWith(systemConfigRootPath) ) { |
| logger.debug("Not removing system artifact from repository at {}", path); |
| return null; |
| } |
| // 2. Is this configuration provisioned by us |
| boolean found = false; |
| int lastSlash = path.lastIndexOf('/'); |
| while (!found && lastSlash > 1) { |
| final String prefix = path.substring(0, lastSlash); |
| if ( cfg.getFolderNameFilter().getPriority(prefix) != -1 ) { |
| found = true; |
| } else { |
| lastSlash = prefix.lastIndexOf('/'); |
| } |
| } |
| if ( found ) { |
| // remove |
| logger.debug("Removing artifact at {}", path); |
| Session session = null; |
| try { |
| session = this.repository.loginAdministrative(null); |
| if ( session.itemExists(path) ) { |
| session.getItem(path).remove(); |
| session.save(); |
| } |
| } catch (final RepositoryException re) { |
| logger.error("Unable to remove resource from " + path, re); |
| return null; |
| } finally { |
| if ( session != null ) { |
| session.logout(); |
| } |
| } |
| return new UpdateResult(url); |
| } |
| // not provisioned by us |
| logger.debug("Not removing unmanaged artifact from repository at {}", path); |
| return null; |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.UpdateHandler#handleUpdate(java.lang.String, java.lang.String, java.lang.String, java.util.Dictionary, Map) |
| */ |
| public UpdateResult handleUpdate(final String resourceType, |
| final String id, |
| final String url, |
| final Dictionary<String, Object> dict, |
| final Map<String, Object> attributes) { |
| return this.handleUpdate(resourceType, id, url, null, dict, attributes); |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.UpdateHandler#handleUpdate(java.lang.String, java.lang.String, java.lang.String, java.io.InputStream, Map) |
| */ |
| public UpdateResult handleUpdate(final String resourceType, |
| final String id, |
| final String url, |
| final InputStream is, |
| final Map<String, Object> attributes) { |
| return this.handleUpdate(resourceType, id, url, is, null, attributes); |
| } |
| |
| private String getPathWithHighestPrio(final InstallerConfig cfg, final String oldPath) { |
| final String path; |
| // check root path, we use the path with highest prio |
| final String rootPath = cfg.getFolderNameFilter().getRootPaths()[0] + '/'; |
| if ( !oldPath.startsWith(rootPath) ) { |
| final int slashPos = oldPath.indexOf('/', 1); |
| path = rootPath + oldPath.substring(slashPos + 1); |
| } else { |
| path = oldPath; |
| } |
| return path; |
| } |
| |
| /** |
| * Internal implementation of update handling |
| */ |
| private UpdateResult handleUpdate(final String resourceType, |
| final String id, |
| final String url, |
| final InputStream is, |
| final Dictionary<String, Object> dict, |
| final Map<String, Object> attributes) { |
| // get configuration |
| final InstallerConfig cfg = this.getConfiguration(); |
| if ( cfg == null || !cfg.isWriteBack() ) { |
| return null; |
| } |
| |
| // we only handle add/update of configs for now |
| if ( !resourceType.equals(InstallableResource.TYPE_CONFIG) ) { |
| return null; |
| } |
| |
| Session session = null; |
| try { |
| session = this.repository.loginAdministrative(null); |
| |
| final String path; |
| boolean resourceIsMoved = true; |
| if ( url != null ) { |
| // update |
| final int pos = url.indexOf(':'); |
| final String oldPath = url.substring(pos + 1); |
| |
| // calculate the new node path |
| final String nodePath; |
| if ( url.startsWith(URL_SCHEME + ':') ) { |
| nodePath = getPathWithHighestPrio(cfg, oldPath); |
| } else { |
| final int lastSlash = url.lastIndexOf('/'); |
| final int lastPos = url.lastIndexOf('.'); |
| final String name; |
| if ( lastSlash == -1 || lastPos < lastSlash ) { |
| name = id; |
| } else { |
| name = url.substring(lastSlash + 1, lastPos); |
| } |
| nodePath = getPathWithHighestPrio(cfg, cfg.getNewConfigPath() + name + ".config"); |
| } |
| // ensure extension 'config' |
| if ( !nodePath.endsWith(".config") ) { |
| if ( session.itemExists(nodePath) ) { |
| session.getItem(nodePath).remove(); |
| } |
| path = nodePath + ".config"; |
| } else { |
| path = nodePath; |
| } |
| |
| resourceIsMoved = nodePath.equals(oldPath); |
| logger.debug("Update of {} at {}", resourceType, path); |
| } else { |
| // add |
| final String name; |
| if ( attributes != null && attributes.get(InstallableResource.RESOURCE_URI_HINT) != null ) { |
| name = (String)attributes.get(InstallableResource.RESOURCE_URI_HINT); |
| } else { |
| name = id; |
| } |
| path = cfg.getNewConfigPath() + name + ".config"; |
| logger.debug("Add of {} at {}", resourceType, path); |
| } |
| |
| // write to a byte array stream |
| final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| baos.write("# Configuration created by Apache Sling JCR Installer\n".getBytes("UTF-8")); |
| ConfigurationHandler.write(baos, dict); |
| baos.close(); |
| |
| // get or create file node |
| JcrUtil.createPath(session, path, NT_FILE); |
| // get or create resource node |
| final Node dataNode = JcrUtil.createPath(session, path + "/jcr:content", NT_RESOURCE); |
| |
| dataNode.setProperty(PROP_DATA, new ByteArrayInputStream(baos.toByteArray())); |
| dataNode.setProperty(PROP_MODIFIED, Calendar.getInstance()); |
| dataNode.setProperty(PROP_ENC, ENCODING); |
| dataNode.setProperty(PROP_MIME, MIME_TXT); |
| session.save(); |
| |
| final UpdateResult result = new UpdateResult(JcrInstaller.URL_SCHEME + ':' + path); |
| // priority |
| final int lastSlash = path.lastIndexOf('/'); |
| final String parentPath = path.substring(0, lastSlash); |
| result.setPriority(cfg.getFolderNameFilter().getPriority(parentPath)); |
| result.setResourceIsMoved(resourceIsMoved); |
| return result; |
| } catch (final RepositoryException re) { |
| logger.error("Unable to add/update resource " + resourceType + ':' + id, re); |
| return null; |
| } catch (final IOException e) { |
| logger.error("Unable to add/update resource " + resourceType + ':' + id, e); |
| return null; |
| } finally { |
| if ( session != null ) { |
| session.logout(); |
| } |
| } |
| } |
| |
| } |