blob: a6879f560cc35e8311fd69a874b1e1248fb96d1b [file] [log] [blame]
/*
* 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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongArray;
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.installer.api.serializer.ConfigurationSerializer;
import org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
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 AtomicLongArray counters = new AtomicLongArray(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";
private static final String CONFIG_FILE_EXTENSION = ".cfg.json";
/** Default regexp for watched folders */
public static final String DEFAULT_FOLDER_NAME_REGEXP = ".*/(install|config)$";
/**
* 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;
@Reference
private ServiceUserMapped serviceUserMapped;
/** 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 RootFolderMoveListener 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.loginService(/* subservice name */null, repository.getDefaultWorkspace());
for (final String path : cfg.getRoots()) {
listeners.add(new RootFolderListener(session, path, updateFoldersListTimer, cfg));
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
if(cfg.getRoots() != null && cfg.getRoots().length > 0) {
moveEventListener = new RootFolderMoveListener(session, cfg.getRoots(), updateFoldersListTimer);
}
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 = cfg.scanWatchedFolders();
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) {
moveEventListener.cleanup(session);
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.set(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.loginService(/* subservice name */null, 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) {
cfg.addWatchedFolder(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 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(final String root : cfg.getRoots()) {
findPathsToWatch(cfg, session, root);
}
// Check all WatchedFolder, in case some were deleted
final List<String> removedResources = cfg.checkForRemovedWatchedFolders(session);
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 (cfg.anyWatchFolderNeedsScan()) {
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;
}
try {
Thread.sleep(JcrInstaller.RUN_LOOP_DELAY_MSEC);
} catch(InterruptedException ignored) {
logger.debug("InterruptedException in scanningIsPaused block");
}
return;
} else if (pauseMessageLogged) {
pauseMessageLogged = false;
}
}
// Rescan WatchedFolders if needed
boolean scanWf = false;
for(final WatchedFolder wf : cfg.cloneWatchedFolders()) {
if (!wf.needsScan()) {
continue;
}
scanWf = true;
if ( !didRefresh ) {
session.refresh(false);
didRefresh = true;
}
counters.incrementAndGet(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.incrementAndGet(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.incrementAndGet(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 will be paused", nodeNames, cfg.getPauseScanNodePath());
}
return result;
}
return false;
}
long getCounterValue(int key) {
return counters.get(key);
}
/**
* @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 = repository.loginService(/* subservice name */null, repository.getDefaultWorkspace());
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 = repository.loginService(/* subservice name */null, repository.getDefaultWorkspace());
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_FILE_EXTENSION);
}
// ensure extension 'config'
if ( !nodePath.endsWith(CONFIG_FILE_EXTENSION) ) {
if ( session.itemExists(nodePath) ) {
session.getItem(nodePath).remove();
}
path = nodePath + CONFIG_FILE_EXTENSION;
} 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_FILE_EXTENSION;
logger.debug("Add of {} at {}", resourceType, path);
}
// write to a byte array stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("# Configuration created by Apache Sling JCR Installer\n".getBytes("UTF-8"));
ConfigurationSerializer serializer = ConfigurationSerializerFactory.create(ConfigurationSerializerFactory.Format.JSON);
serializer.serialize(dict, baos);
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();
}
}
}
}