Package org.apache.sling.installer.provider.jcr.impl

Source Code of org.apache.sling.installer.provider.jcr.impl.JcrInstaller$StoppableThread

/*
* 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.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

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.commons.osgi.PropertiesUtil;
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="service.description", value="Sling JCR Install Service"),
    @Property(name="service.vendor", value="The Apache Software Foundation"),
    @Property(name=UpdateHandler.PROPERTY_SCHEMES, value=JcrInstaller.URL_SCHEME, unbounded=PropertyUnbounded.ARRAY),
    @Property(name="service.ranking", intValue=100)
})
@Service(value=UpdateHandler.class)
public class JcrInstaller implements EventListener, UpdateHandler, ManagedService {

  public static final long RUN_LOOP_DELAY_MSEC = 500L;
  public static final String URL_SCHEME = "jcrinstall";

  /** PID before refactoring. */
  private 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";

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

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

    private int maxWatchedFolderDepth;

    /** Filter for folder names */
    private FolderNameFilter folderNameFilter;

    /** List of watched folders */
    private List<WatchedFolder> watchedFolders;

    /** Session shared by all WatchedFolder */
    private Session session;

    /** The root folders that we watch */
    private String [] roots;

    /** The component context. */
    private ComponentContext componentContext;

    /** Service reg for managed service. */
    private ServiceRegistration managedServiceRef;

    /** Configuration from managed service (old pid) */
    private Dictionary<?, ?> oldConfiguration;

    private static final String DEFAULT_NEW_CONFIG_PATH = "sling/install";
    @Property(value=DEFAULT_NEW_CONFIG_PATH)
    private static final String PROP_NEW_CONFIG_PATH = "sling.jcrinstall.new.config.path";

    /** The path for new configurations. */
    private String newConfigPath;

    public static final String PAUSE_SCAN_NODE_PATH = "/system/sling/installer/jcr/pauseInstallation";
    @Property(value= PAUSE_SCAN_NODE_PATH)
    private static final String PROP_SCAN_PROP_PATH = "sling.jcrinstall.signal.path";

    /** The path for pauseInstallation property */
    private String pauseScanNodePath;

    private volatile boolean pauseMessageLogged = false;

    private static final boolean DEFAULT_ENABLE_WRITEBACK = true;
    @Property(boolValue=DEFAULT_ENABLE_WRITEBACK)
    private static final String PROP_ENABLE_WRITEBACK = "sling.jcrinstall.enable.writeback";

    /** Write back enabled? */
    private boolean writeBack;

    private EventListener moveEventListener;

    /** Convert Nodes to InstallableResources */
    static interface NodeConverter {
      InstallableResource convertNode(Node n, int priority)
      throws RepositoryException;
    }

    /** Our NodeConverters*/
    private final Collection <NodeConverter> converters = new ArrayList<NodeConverter>();

    /** Detect newly created folders that we must watch */
    private final List<RootFolderListener> listeners = new LinkedList<RootFolderListener>();

    /** Timer used to call updateFoldersList() */
    private final RescanTimer updateFoldersListTimer = new RescanTimer();

    /** Thread that can be cleanly stopped with a flag */
    static int bgThreadCounter;
    class StoppableThread extends Thread {
        boolean active = true;
        StoppableThread() {
            synchronized (JcrInstaller.class) {
                setName("JcrInstaller." + (++bgThreadCounter));
            }
            setDaemon(true);
        }

        @Override
        public final void run() {
            logger.info("Background thread {} starting", Thread.currentThread().getName());
            try {
                // open session
                session = repository.loginAdministrative(repository.getDefaultWorkspace());

                for (String path : roots) {
                    listeners.add(new RootFolderListener(session, folderNameFilter, 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(JcrInstaller.this,
                        Event.NODE_ADDED | Event.NODE_REMOVED,
                        "/",
                        false, // isDeep
                        null,
                        null,
                        true); // noLocal
                // add special observation listener for move events
                JcrInstaller.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();
                                JcrInstaller.this.checkChanges(e.getIdentifier());
                                JcrInstaller.this.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
                watchedFolders = new LinkedList<WatchedFolder>();
                for(String root : roots) {
                    findPathsToWatch(root, watchedFolders);
                }

                // Scan watchedFolders and register resources with installer
                final List<InstallableResource> resources = new LinkedList<InstallableResource>();
                for(WatchedFolder f : watchedFolders) {
                    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()]));
            } catch (final RepositoryException re) {
                logger.error("Repository exception during startup - deactivating installer!", re);
                active = false;
                final ComponentContext ctx = componentContext;
                if ( ctx  != null ) {
                    final String name = (String) componentContext.getProperties().get(
                            ComponentConstants.COMPONENT_NAME);
                    ctx.disableComponent(name);
                }
            }
            while (active) {
                runOneCycle();
            }
            logger.info("Background thread {} done", Thread.currentThread().getName());
            counters[RUN_LOOP_COUNTER] = -1;
        }
    };
    private StoppableThread backgroundThread;

    /**
     * Activate this component.
     */
    protected void activate(final ComponentContext context) {
        if (backgroundThread != null) {
            throw new IllegalStateException("Expected backgroundThread to be null in activate()");
        }
        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");
        this.writeBack = PropertiesUtil.toBoolean(getPropertyValue(PROP_ENABLE_WRITEBACK), DEFAULT_ENABLE_WRITEBACK);

      // Setup converters
      converters.add(new FileNodeConverter());
      converters.add(new ConfigNodeConverter());

      // Configurable max depth, system property (via bundle context) overrides default value
      final Object obj = getPropertyValue(PROP_INSTALL_FOLDER_MAX_DEPTH);
      if (obj != null) {
        // depending on where it's coming from, obj might be a string or integer
        maxWatchedFolderDepth = Integer.valueOf(String.valueOf(obj)).intValue();
            logger.debug("Using configured ({}) folder name max depth '{}'", PROP_INSTALL_FOLDER_MAX_DEPTH, maxWatchedFolderDepth);
      } else {
            maxWatchedFolderDepth = DEFAULT_FOLDER_MAX_DEPTH;
            logger.debug("Using default folder max depth {}, not provided by {}", maxWatchedFolderDepth, PROP_INSTALL_FOLDER_MAX_DEPTH);
      }

      // Configurable folder regexp, system property overrides default value
      String folderNameRegexp = (String)getPropertyValue(FOLDER_NAME_REGEXP_PROPERTY);
      if(folderNameRegexp != null) {
        folderNameRegexp = folderNameRegexp.trim();
            logger.debug("Using configured ({}) folder name regexp '{}'", FOLDER_NAME_REGEXP_PROPERTY, folderNameRegexp);
      } else {
          folderNameRegexp = DEFAULT_FOLDER_NAME_REGEXP;
            logger.debug("Using default folder name regexp '{}', not provided by {}", folderNameRegexp, FOLDER_NAME_REGEXP_PROPERTY);
      }

      // Setup folder filtering and watching
        folderNameFilter = new FolderNameFilter(PropertiesUtil.toStringArray(getPropertyValue(PROP_SEARCH_PATH), DEFAULT_SEARCH_PATH),
                folderNameRegexp, settings.getRunModes());
        roots = folderNameFilter.getRootPaths();

        // setup default path for new configurations
        this.newConfigPath = PropertiesUtil.toString(getPropertyValue(PROP_NEW_CONFIG_PATH), DEFAULT_NEW_CONFIG_PATH);
        final boolean postSlash = newConfigPath.endsWith("/");
        if ( !postSlash ) {
            this.newConfigPath = newConfigPath.concat("/");
        }
        final boolean preSlash = newConfigPath.startsWith("/");
        if ( !preSlash ) {
            this.newConfigPath = this.folderNameFilter.getRootPaths()[0] + '/' + this.newConfigPath;
        }

        this.pauseScanNodePath = PropertiesUtil.toString(getPropertyValue(PROP_SCAN_PROP_PATH), PAUSE_SCAN_NODE_PATH);

        backgroundThread = new StoppableThread();
        backgroundThread.start();
    }

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

      final long timeout = 30000L;
        backgroundThread.active = false;
        logger.debug("Waiting for " + backgroundThread.getName() + " Thread to end...");
        backgroundThread.interrupt();
      try {
            backgroundThread.join(timeout);
      } catch(InterruptedException iex) {
          // ignore this as we want to shutdown
      }
        backgroundThread = null;

        folderNameFilter = null;
        watchedFolders = null;
        converters.clear();
        try {
            if (session != null) {
                for(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 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);
            }
        }
    }

    /** Get a property value from the old config, component context or bundle context */
    protected Object getPropertyValue(final String name) {
        final Dictionary<?, ?> oldConfig = this.oldConfiguration;
        Object result = null;
        if ( oldConfig != null ) {
            result = oldConfig.get(name);
            if ( result != null ) {
                logger.warn("Using configuration value from obsolete configuration with PID {} for property {}." +
                            " Please merge this configuration into the configuration with the PID {}.",
                            new Object[] {OLD_PID, name, this.componentContext.getProperties().get(Constants.SERVICE_PID)});
            }
        }
        if ( result == null ) {
            result = this.componentContext.getBundleContext().getProperty(name);
            if (result == null) {
                result = this.componentContext.getProperties().get(name);
            }
        }
        return result;
    }

    /** Find the paths to watch under rootPath, according to our folderNameFilter,
     *   and add them to result */
    private void findPathsToWatch(final String rootPath, final List<WatchedFolder> result) 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(n, result);
            }
        } 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 Node n, final List<WatchedFolder> result) throws RepositoryException {
        final String path = n.getPath();
        final int priority = folderNameFilter.getPriority(path);
        if (priority > 0) {
            result.add(new WatchedFolder(session, path, priority, converters));
        }
        final int depth = path.split("/").length;
        if(depth > maxWatchedFolderDepth) {
            logger.debug("Not recursing into {} due to maxWatchedFolderDepth={}", path, maxWatchedFolderDepth);
            return;
        }
        final NodeIterator it = n.getNodes();
        while (it.hasNext()) {
            findPathsUnderNode(it.nextNode(), result);
        }
    }

    /**
     * Add WatchedFolder to our list if it doesn't exist yet.
     */
    private void addWatchedFolder(final WatchedFolder toAdd)
    throws RepositoryException {
        WatchedFolder existing = null;
        for(WatchedFolder wf : watchedFolders) {
            if (wf.getPath().equals(toAdd.getPath())) {
                existing = wf;
                break;
            }
        }
        if (existing == null) {
            toAdd.start();
            watchedFolders.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() throws Exception {
        logger.debug("Updating folder list.");

        final List<String> result = new LinkedList<String>();

        final List<WatchedFolder> newFolders = new ArrayList<WatchedFolder>();
      for(String root : roots) {
          findPathsToWatch(root, newFolders);
      }
      for(WatchedFolder wf : newFolders) {
          addWatchedFolder(wf);
      }

        // Check all WatchedFolder, in case some were deleted
        final List<WatchedFolder> toRemove = new ArrayList<WatchedFolder>();
        for(WatchedFolder wf : watchedFolders) {
            logger.debug("Item {} exists? {}", wf.getPath(), session.itemExists(wf.getPath()));

            if(!session.itemExists(wf.getPath())) {
                result.addAll(wf.scan().toRemove);
                wf.stop();
                toRemove.add(wf);
            }
        }
        for(final WatchedFolder wf : toRemove) {
            logger.info("Deleting {}, path does not exist anymore", wf);
            watchedFolders.remove(wf);
        }

        return result;
    }

    /**
     * Check for changes in any of the root folders
     */
    private void checkChanges(final String path) {
        for(String root : roots) {
            if (path.startsWith(root)) {
                logger.info("Got event for root {}, scheduling scanning of new folders", root);
                updateFoldersListTimer.scheduleScan();
            }
        }
    }

    /**
     * @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(RepositoryException re) {
            logger.warn("RepositoryException in onEvent", re);
        }
    }

    /**
     * Run periodic scans of our watched folders, and watch for folders creations/deletions.
     */
    public void runOneCycle() {
        logger.debug("Running watch cycle.");

        try {
            boolean didRefresh = false;

            if (anyWatchFolderNeedsScan()) {
                session.refresh(false);
                didRefresh = true;
                if (scanningIsPaused()) {
                    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", pauseScanNodePath);
                        pauseMessageLogged = true;
                    }
                    return;
                } else if (pauseMessageLogged) {
                    pauseMessageLogged = false;
                }
            }

            // Rescan WatchedFolders if needed
            boolean scanWf = false;
            for(WatchedFolder wf : watchedFolders) {
                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();
                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);
        }

        try {
            Thread.sleep(RUN_LOOP_DELAY_MSEC);
        } catch (final InterruptedException ignore) {
            // ignore
        }
        counters[RUN_LOOP_COUNTER]++;
    }

    boolean scanningIsPaused() throws RepositoryException {
        if (session.nodeExists(pauseScanNodePath)) {
            Node node = session.getNode(pauseScanNodePath);
            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, pauseScanNodePath);
            }
            return result;
        }
        return false;
    }

    private boolean anyWatchFolderNeedsScan() {
        for (WatchedFolder wf : watchedFolders) {
            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) {
        if ( !this.writeBack ) {
            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 = this.folderNameFilter.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 ( this.folderNameFilter.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 String oldPath) {
        final String path;
        // check root path, we use the path with highest prio
        final String rootPath = this.folderNameFilter.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) {
        if ( !this.writeBack ) {
            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(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(this.newConfigPath + 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 = this.newConfigPath + 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(this.folderNameFilter.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();
            }
        }
    }

}
TOP

Related Classes of org.apache.sling.installer.provider.jcr.impl.JcrInstaller$StoppableThread

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.