Author: cziegeler
Date: Wed Mar 2 10:53:34 2011
New Revision: 1076166
URL: http://svn.apache.org/viewvc?rev=1076166&view=rev
Log:
SLING-1971 : Persist configuration (and bundle) changes not made through the
installer
Added:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
(with props)
Modified:
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/FileDataStore.java
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/RegisteredResourceImpl.java
sling/trunk/installer/providers/jcr/pom.xml
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilter.java
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
sling/trunk/installer/providers/jcr/src/main/resources/OSGI-INF/metatype/metatype.properties
sling/trunk/installer/providers/jcr/src/test/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilterTest.java
Modified:
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
(original)
+++
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
Wed Mar 2 10:53:34 2011
@@ -37,20 +37,20 @@ public interface ResourceChangeListener
* Inform the installer about an added or updated
* resource
* @param resourceType The resource type
- * @param resourceId The resource id (symbolic name etc.)
+ * @param entityId The entity id (symbolic name etc.)
* @param is Input stream or
* @param dict Dictionary
*/
void resourceAddedOrUpdated(final String resourceType,
- final String resourceId,
+ final String entityId,
final InputStream is,
final Dictionary<String, Object> dict);
/**
* Inform the installer about a removed resource
* @param resourceType The resource type
- * @param resourceId The resource id (symbolic name etc.)
+ * @param entityId The entity id (symbolic name etc.)
*/
void resourceRemoved(final String resourceType,
- final String resourceId);
+ final String entityId);
}
\ No newline at end of file
Modified:
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/FileDataStore.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/FileDataStore.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/FileDataStore.java
(original)
+++
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/FileDataStore.java
Wed Mar 2 10:53:34 2011
@@ -256,7 +256,7 @@ public class FileDataStore {
}
}
- bos.flush();
+ oos.flush();
d.update(bos.toByteArray());
return digestToString(d);
} catch (Exception ignore) {
Modified:
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
(original)
+++
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
Wed Mar 2 10:53:34 2011
@@ -175,16 +175,22 @@ public class OsgiInstallerImpl
this.mergeNewlyRegisteredResources();
- // invoke transformers
- this.transformResources();
+ // invoke transformers - sync as we change state
+ synchronized ( this.resourcesLock ) {
+ this.transformResources();
+ }
// execute tasks
final SortedSet<InstallTask> tasks = this.computeTasks();
final boolean tasksCreated = !tasks.isEmpty();
- this.executeTasks(tasks);
- // clean up and save
- this.cleanupInstallableResources();
+ // sync as we might change state
+ synchronized ( this.resourcesLock ) {
+ this.executeTasks(tasks);
+
+ // clean up and save
+ this.cleanupInstallableResources();
+ }
// if we don't have any tasks, we go to sleep
if (!tasksCreated) {
@@ -663,19 +669,19 @@ public class OsgiInstallerImpl
* @see
org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String,
java.lang.String, java.io.InputStream, java.util.Dictionary)
*/
public void resourceAddedOrUpdated(final String resourceType,
- String resourceId,
+ String entityId,
final InputStream is,
final Dictionary<String, Object> dict) {
- String key = resourceType + ':' + resourceId;
+ String key = resourceType + ':' + entityId;
try {
final ResourceData data = ResourceData.create(is, dict);
synchronized ( this.resourcesLock ) {
final EntityResourceList erl =
this.persistentList.getEntityResourceList(key);
if ( erl != null ) {
- resourceId = erl.getResourceId();
- key = resourceType + ':' + resourceId;
+ entityId = erl.getResourceId();
+ key = resourceType + ':' + entityId;
}
- logger.info("Added or updated {}:{}: {}", new Object[]
{resourceType, resourceId, erl});
+ logger.debug("Added or updated {} : {}", key, erl);
// we first check for update
boolean updated = false;
@@ -685,28 +691,51 @@ public class OsgiInstallerImpl
if ( dict != null ) {
final String digest =
FileDataStore.computeDigest(dict);
if ( tr.getState() == ResourceState.INSTALLED &&
tr.getDigest().equals(digest) ) {
- logger.debug("Resource did not change {}:{}",
resourceType, resourceId);
+ logger.debug("Resource did not change {}", key);
return;
}
}
final UpdateHandler handler =
this.findHandler(tr.getScheme());
if ( handler == null ) {
- logger.info("No handler found to handle update of
resource with scheme {}", tr.getScheme());
+ logger.debug("No handler found to handle update of
resource with scheme {}", tr.getScheme());
} else {
final InputStream localIS = data.getInputStream();
try {
- final UpdateResult result =
handler.handleUpdate(resourceType, resourceId, tr.getURL(), localIS,
data.getDictionary());
+ final UpdateResult result =
handler.handleUpdate(resourceType, entityId, tr.getURL(), localIS,
data.getDictionary());
if ( result != null ) {
- ((RegisteredResourceImpl)tr).update(
- data.getDataFile(),
data.getDictionary(),
- data.getDigest(result.getURL(),
result.getDigest()),
- result.getPriority());
- // TODO : Handle move and add
+ if ( !result.getURL().equals(tr.getURL()) &&
!result.getResourceIsMoved() ) {
+ // resource has been added!
+ final InternalResource internalResource =
new InternalResource(result.getScheme(),
+ result.getResourceId(),
+ null,
+ data.getDictionary(),
+ (data.getDictionary() != null ?
InstallableResource.TYPE_PROPERTIES : InstallableResource.TYPE_FILE),
+ data.getDigest(result.getURL(),
result.getDigest()),
+ result.getPriority(),
+ data.getDataFile());
+ final RegisteredResource rr =
this.persistentList.addOrUpdate(internalResource);
+ final TransformationResult transRes = new
TransformationResult();
+ transRes.setId(entityId);
+ transRes.setResourceType(resourceType);
+ this.persistentList.transform(rr, new
TransformationResult[] {
+ transRes
+ });
+ final EntityResourceList newGroup =
this.persistentList.getEntityResourceList(key);
+
newGroup.setFinishState(ResourceState.INSTALLED);
+ newGroup.compact();
+ } else {
+ // resource has been updated or moved
+ ((RegisteredResourceImpl)tr).update(
+ data.getDataFile(),
data.getDictionary(),
+ data.getDigest(result.getURL(),
result.getDigest()),
+ result.getPriority(),
+ result.getURL());
+ // We first set the state of the resource
to install to make setFinishState work in all cases
+
((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
+
erl.setFinishState(ResourceState.INSTALLED);
+ erl.compact();
+ }
updated = true;
- // We first set the state of the resource to
install to make setFinishState work in all cases
-
((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
- erl.setFinishState(ResourceState.INSTALLED);
- erl.compact();
this.persistentList.save();
this.wakeUp();
}
@@ -731,7 +760,7 @@ public class OsgiInstallerImpl
for(final UpdateHandler handler : handlerList) {
final InputStream localIS = data.getInputStream();
try {
- final UpdateResult result =
handler.handleUpdate(resourceType, resourceId, null, localIS,
data.getDictionary());
+ final UpdateResult result =
handler.handleUpdate(resourceType, entityId, null, localIS,
data.getDictionary());
if ( result != null ) {
final InternalResource internalResource = new
InternalResource(result.getScheme(),
result.getResourceId(),
@@ -743,11 +772,14 @@ public class OsgiInstallerImpl
data.getDataFile());
final RegisteredResource rr =
this.persistentList.addOrUpdate(internalResource);
final TransformationResult transRes = new
TransformationResult();
- transRes.setId(resourceId);
+ transRes.setId(entityId);
transRes.setResourceType(resourceType);
this.persistentList.transform(rr, new
TransformationResult[] {
transRes
});
+ final EntityResourceList newGroup =
this.persistentList.getEntityResourceList(key);
+
newGroup.setFinishState(ResourceState.INSTALLED);
+ newGroup.compact();
this.persistentList.save();
created = true;
this.wakeUp();
@@ -765,7 +797,7 @@ public class OsgiInstallerImpl
}
}
if ( !created ) {
- logger.info("No handler found to handle creation of
resource {}:{}", resourceType, resourceId);
+ logger.debug("No handler found to handle creation of
resource {}", key);
}
}
@@ -791,7 +823,7 @@ public class OsgiInstallerImpl
String key = resourceType + ':' + resourceId;
synchronized ( this.resourcesLock ) {
final EntityResourceList erl =
this.persistentList.getEntityResourceList(key);
- logger.info("Removed {}:{}: {}", new Object[] {resourceType,
resourceId, erl});
+ logger.debug("Removed {} : {}", key, erl);
// if this is not registered at all, we can simply ignore this
if ( erl != null ) {
resourceId = erl.getResourceId();
@@ -801,7 +833,7 @@ public class OsgiInstallerImpl
if ( tr.getState() != ResourceState.IGNORED ) {
final UpdateHandler handler =
this.findHandler(tr.getScheme());
if ( handler == null ) {
- logger.info("No handler found to handle remove of
resource with scheme {}", tr.getScheme());
+ logger.debug("No handler found to handle remove of
resource with scheme {}", tr.getScheme());
} else {
// we don't need to check the result, we just
check if a result is returned
if ( handler.handleUpdate(resourceType,
resourceId, tr.getURL(), null, null) != null ) {
@@ -812,7 +844,7 @@ public class OsgiInstallerImpl
this.persistentList.save();
this.wakeUp();
} else {
- logger.info("No handler found to handle remove
of resource with scheme {}", tr.getScheme());
+ logger.debug("No handler found to handle
remove of resource with scheme {}", tr.getScheme());
}
}
} else {
Modified:
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/RegisteredResourceImpl.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/RegisteredResourceImpl.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/RegisteredResourceImpl.java
(original)
+++
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/RegisteredResourceImpl.java
Wed Mar 2 10:53:34 2011
@@ -50,10 +50,10 @@ public class RegisteredResourceImpl
private static final int VERSION = 2;
/** The resource url. */
- private final String url;
+ private String url;
/** The installer scheme. */
- private final String urlScheme;
+ private String urlScheme;
/** The digest for the resource. */
private String digest;
@@ -507,7 +507,8 @@ public class RegisteredResourceImpl
public void update(final File file,
final Dictionary<String, Object> dict,
final String digest,
- final int priority) {
+ final int priority,
+ final String url) {
this.removeDataFile();
if ( file != null ) {
this.dataFile = file;
@@ -523,5 +524,8 @@ public class RegisteredResourceImpl
}
this.digest = digest;
this.priority = priority;
+ this.url = url;
+ final int pos = url.indexOf(':');
+ this.urlScheme = url.substring(0, pos);
}
}
\ No newline at end of file
Modified: sling/trunk/installer/providers/jcr/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/pom.xml?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
--- sling/trunk/installer/providers/jcr/pom.xml (original)
+++ sling/trunk/installer/providers/jcr/pom.xml Wed Mar 2 10:53:34 2011
@@ -72,6 +72,10 @@
<dependencies>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.settings</artifactId>
<version>1.0.0</version>
@@ -80,7 +84,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.installer.core</artifactId>
- <version>3.0.0</version>
+ <version>3.1.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
Modified:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilter.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilter.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilter.java
(original)
+++
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilter.java
Wed Mar 2 10:53:34 2011
@@ -18,6 +18,8 @@
*/
package org.apache.sling.installer.provider.jcr.impl;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -51,6 +53,7 @@ class FolderNameFilter {
public static final int DEFAULT_ROOT_PRIORITY = 99;
FolderNameFilter(final String [] rootsConfig, final String regexp, final
Set<String> runModes) {
+ final List<RootPathInfo> rootPathInfos = new ArrayList<RootPathInfo>();
this.regexp = regexp;
this.pattern = Pattern.compile(regexp);
this.runModes = runModes;
@@ -58,10 +61,10 @@ class FolderNameFilter {
// Each entry in rootsConfig is like /libs:100, where 100
// is the priority.
// Break that up into paths and priorities
- rootPaths = new String[rootsConfig.length];
+ this.rootPaths = new String[rootsConfig.length];
for(int i = 0; i < rootsConfig.length; i++) {
final String [] parts = rootsConfig[i].split(":");
- rootPaths[i] = cleanupRootPath(parts[0]);
+ this.rootPaths[i] = cleanupRootPath(parts[0]);
Integer priority = new Integer(DEFAULT_ROOT_PRIORITY);
if(parts.length > 1) {
try {
@@ -70,14 +73,41 @@ class FolderNameFilter {
log.warn("Invalid priority in path definition
'{}'", rootsConfig[i]);
}
}
- rootPriorities.put(rootPaths[i], priority);
- log.debug("Root path {} has priority {}", rootPaths[i],
priority);
+ rootPriorities.put(this.rootPaths[i], priority);
+ rootPathInfos.add(new RootPathInfo(this.rootPaths[i],
priority));
+ log.debug("Root path {} has priority {}", this.rootPaths[i],
priority);
+ }
+ // sort root paths by priority
+ Collections.sort(rootPathInfos);
+ int index = 0;
+ for(final RootPathInfo rpi : rootPathInfos) {
+ this.rootPaths[index++] = rpi.path;
+ }
+ }
+
+ private static final class RootPathInfo implements
Comparable<RootPathInfo> {
+
+ public final String path;
+ private final Integer priority;
+
+ public RootPathInfo(final String path, final Integer prio) {
+ this.path = path;
+ this.priority = prio;
+ }
+
+ public int compareTo(RootPathInfo o) {
+ int result = -this.priority.compareTo(o.priority);
+ if ( result == 0 ) {
+ result = this.path.compareTo(o.path);
+ }
+ return result;
}
}
/**
* Return the list of root paths.
- * Every entry in the list starts with a slash
+ * Every entry in the list starts with a slash and the entries are
+ * sorted by priority - highest priority first
*/
String [] getRootPaths() {
return rootPaths;
Modified:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
(original)
+++
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrInstaller.java
Wed Mar 2 10:53:34 2011
@@ -18,8 +18,10 @@
*/
package org.apache.sling.installer.provider.jcr.impl;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Dictionary;
import java.util.LinkedList;
import java.util.List;
@@ -31,9 +33,17 @@ import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
+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.OsgiUtil;
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.service.component.ComponentConstants;
@@ -41,27 +51,26 @@ import org.osgi.service.component.Compon
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.
- *
- * @scr.component
- * label="%jcrinstall.name"
- * description="%jcrinstall.description"
- * immediate="true"
- * @scr.property
- * name="service.description"
- * value="Sling Jcrinstall Service"
- * @scr.property
- * name="service.vendor"
- * value="The Apache Software Foundation"
+/**
+ * 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.
*/
-public class JcrInstaller implements EventListener {
+@Component(label="%jcrinstall.name", description="%jcrinstall.description",
immediate=true, metatype=true)
+@Properties({
+ @Property(name="service.description", value="Sling Jcrinstall Service"),
+ @Property(name="service.vendor", value="The Apache Software Foundation"),
+ @Property(name=UpdateHandler.PROPERTY_SCHEMES,
value=JcrInstaller.URL_SCHEME),
+ @Property(name="service.ranking", intValue=100)
+})
+@Service(value=UpdateHandler.class)
+public class JcrInstaller implements EventListener, UpdateHandler {
+
public static final long RUN_LOOP_DELAY_MSEC = 500L;
public static final String URL_SCHEME = "jcrinstall";
- private final Logger log = LoggerFactory.getLogger(getClass());
+ private final Logger logger = LoggerFactory.getLogger(getClass());
/** Counters, used for statistics and testing */
private final long [] counters = new long[COUNTERS_COUNT];
@@ -70,46 +79,52 @@ public class JcrInstaller implements Eve
public static final int RUN_LOOP_COUNTER = 2;
public static final int COUNTERS_COUNT = 3;
- /** This class watches the repository for installable resources
- * @scr.reference
+ /**
+ * 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".
- * @scr.reference
+ /**
+ * 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.
- * @scr.reference
+ /**
+ * 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
- * @scr.property valueRef="DEFAULT_FOLDER_NAME_REGEXP"
+ /**
+ * 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";
- /** Configurable max. path depth for watched folders
- * @scr.property valueRef="DEFAULT_FOLDER_MAX_DEPTH" type="Integer"
+ 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.
- *
- * @scr.property values.1="/libs:100" values.2="/apps:200"
+ /**
+ * 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 int DEFAULT_FOLDER_MAX_DEPTH = 4;
private int maxWatchedFolderDepth;
/** Filter for folder names */
@@ -126,6 +141,20 @@ public class JcrInstaller implements Eve
private ComponentContext componentContext;
+ private static final String DEFAULT_NEW_CONFIG_PATH = "/apps/sling/config";
+ @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;
+
+ 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;
+
/** Convert Nodes to InstallableResources */
static interface NodeConverter {
InstallableResource convertNode(Node n, int priority)
@@ -154,14 +183,14 @@ public class JcrInstaller implements Eve
@Override
public final void run() {
- log.info("Background thread {} starting",
Thread.currentThread().getName());
+ 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));
- log.debug("Configured root folder: {}", path);
+ logger.debug("Configured root folder: {}", path);
}
// Watch for events on the root - that might be one of our
root folders
@@ -172,7 +201,7 @@ public class JcrInstaller implements Eve
null,
null,
true); // noLocal
- log.debug("Watching for node events on / to detect removal/add
of our root folders");
+ 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
@@ -185,14 +214,14 @@ public class JcrInstaller implements Eve
final List<InstallableResource> resources = new
LinkedList<InstallableResource>();
for(WatchedFolder f : watchedFolders) {
final WatchedFolder.ScanResult r = f.scan();
- log.debug("Startup: {} provides resources {}", f, r.toAdd);
+ logger.debug("Startup: {} provides resources {}", f,
r.toAdd);
resources.addAll(r.toAdd);
}
- log.debug("Registering {} resources with OSGi installer: {}",
resources.size(), resources);
+ logger.debug("Registering {} resources with OSGi installer:
{}", resources.size(), resources);
installer.registerResources(URL_SCHEME, resources.toArray(new
InstallableResource[resources.size()]));
} catch (final RepositoryException re) {
- log.error("Repository exception during startup - deactivating
installer!", re);
+ logger.error("Repository exception during startup -
deactivating installer!", re);
active = false;
final ComponentContext ctx = componentContext;
if ( ctx != null ) {
@@ -204,7 +233,7 @@ public class JcrInstaller implements Eve
while (active) {
runOneCycle();
}
- log.info("Background thread {} done",
Thread.currentThread().getName());
+ logger.info("Background thread {} done",
Thread.currentThread().getName());
counters[RUN_LOOP_COUNTER] = -1;
}
};
@@ -218,7 +247,13 @@ public class JcrInstaller implements Eve
throw new IllegalStateException("Expected backgroundThread to be
null in activate()");
}
this.componentContext = context;
- log.info("Activating Apache Sling JCR Installer");
+ logger.info("Activating Apache Sling JCR Installer");
+
+ this.newConfigPath =
OsgiUtil.toString(context.getProperties().get(PROP_NEW_CONFIG_PATH),
DEFAULT_NEW_CONFIG_PATH);
+ if ( !newConfigPath.endsWith("/") ) {
+ this.newConfigPath = this.newConfigPath.concat("/");
+ }
+ this.writeBack =
OsgiUtil.toBoolean(context.getProperties().get(PROP_ENABLE_WRITEBACK),
DEFAULT_ENABLE_WRITEBACK);
// Setup converters
converters.add(new FileNodeConverter());
@@ -229,20 +264,20 @@ public class JcrInstaller implements Eve
if (obj != null) {
// depending on where it's coming from, obj might be a string
or integer
maxWatchedFolderDepth =
Integer.valueOf(String.valueOf(obj)).intValue();
- log.debug("Using configured ({}) folder name max depth '{}'",
PROP_INSTALL_FOLDER_MAX_DEPTH, maxWatchedFolderDepth);
+ logger.debug("Using configured ({}) folder name max depth '{}'",
PROP_INSTALL_FOLDER_MAX_DEPTH, maxWatchedFolderDepth);
} else {
maxWatchedFolderDepth = DEFAULT_FOLDER_MAX_DEPTH;
- log.debug("Using default folder max depth {}, not provided by {}",
maxWatchedFolderDepth, PROP_INSTALL_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(context,
FOLDER_NAME_REGEXP_PROPERTY);
if(folderNameRegexp != null) {
folderNameRegexp = folderNameRegexp.trim();
- log.debug("Using configured ({}) folder name regexp '{}'",
FOLDER_NAME_REGEXP_PROPERTY, folderNameRegexp);
+ logger.debug("Using configured ({}) folder name regexp '{}'",
FOLDER_NAME_REGEXP_PROPERTY, folderNameRegexp);
} else {
folderNameRegexp = DEFAULT_FOLDER_NAME_REGEXP;
- log.debug("Using default folder name regexp '{}', not provided by
{}", folderNameRegexp, FOLDER_NAME_REGEXP_PROPERTY);
+ logger.debug("Using default folder name regexp '{}', not provided
by {}", folderNameRegexp, FOLDER_NAME_REGEXP_PROPERTY);
}
// Setup folder filtering and watching
@@ -257,11 +292,11 @@ public class JcrInstaller implements Eve
* Deactivate this component
*/
protected void deactivate(final ComponentContext context) {
- log.info("Deactivating Apache Sling JCR Installer");
+ logger.info("Deactivating Apache Sling JCR Installer");
final long timeout = 30000L;
backgroundThread.active = false;
- log.debug("Waiting for " + backgroundThread.getName() + " Thread to
end...");
+ logger.debug("Waiting for " + backgroundThread.getName() + " Thread to
end...");
backgroundThread.interrupt();
try {
backgroundThread.join(timeout);
@@ -281,7 +316,7 @@ public class JcrInstaller implements Eve
session.getWorkspace().getObservationManager().removeEventListener(this);
}
} catch (final RepositoryException e) {
- log.warn("Exception in deactivate()", e);
+ logger.warn("Exception in deactivate()", e);
}
if ( session != null ) {
session.logout();
@@ -308,9 +343,9 @@ public class JcrInstaller implements Eve
try {
s =
repository.loginAdministrative(repository.getDefaultWorkspace());
if (!s.itemExists(rootPath) || !s.getItem(rootPath).isNode() ) {
- log.info("Bundles root node {} not found, ignored", rootPath);
+ logger.info("Bundles root node {} not found, ignored",
rootPath);
} else {
- log.debug("Bundles root node {} found, looking for bundle
folders inside it", rootPath);
+ logger.debug("Bundles root node {} found, looking for bundle
folders inside it", rootPath);
final Node n = (Node)s.getItem(rootPath);
findPathsUnderNode(n, result);
}
@@ -333,7 +368,7 @@ public class JcrInstaller implements Eve
}
final int depth = path.split("/").length;
if(depth > maxWatchedFolderDepth) {
- log.debug("Not recursing into {} due to maxWatchedFolderDepth={}",
path, maxWatchedFolderDepth);
+ logger.debug("Not recursing into {} due to
maxWatchedFolderDepth={}", path, maxWatchedFolderDepth);
return;
}
final NodeIterator it = n.getNodes();
@@ -364,7 +399,7 @@ public class JcrInstaller implements Eve
* for folders that have been removed
*/
private List<String> updateFoldersList() throws Exception {
- log.debug("Updating folder list.");
+ logger.debug("Updating folder list.");
final List<String> result = new LinkedList<String>();
@@ -379,7 +414,7 @@ public class JcrInstaller implements Eve
// Check all WatchedFolder, in case some were deleted
final List<WatchedFolder> toRemove = new ArrayList<WatchedFolder>();
for(WatchedFolder wf : watchedFolders) {
- log.debug("Item {} exists? {}", wf.getPath(),
session.itemExists(wf.getPath()));
+ logger.debug("Item {} exists? {}", wf.getPath(),
session.itemExists(wf.getPath()));
if(!session.itemExists(wf.getPath())) {
result.addAll(wf.scan().toRemove);
@@ -388,7 +423,7 @@ public class JcrInstaller implements Eve
}
}
for(WatchedFolder wf : toRemove) {
- log.info("Deleting {}, path does not exist anymore", wf);
+ logger.info("Deleting {}, path does not exist anymore", wf);
watchedFolders.remove(wf);
}
@@ -401,23 +436,23 @@ public class JcrInstaller implements Eve
try {
while(it.hasNext()) {
final Event e = it.nextEvent();
- log.debug("Got event {}", e);
+ logger.debug("Got event {}", e);
for(String root : roots) {
if (e.getPath().startsWith(root)) {
- log.info("Got event for root {}, scheduling scanning
of new folders", root);
+ logger.info("Got event for root {}, scheduling
scanning of new folders", root);
updateFoldersListTimer.scheduleScan();
}
}
}
} catch(RepositoryException re) {
- log.warn("RepositoryException in onEvent", re);
+ logger.warn("RepositoryException in onEvent", re);
}
}
/** Run periodic scans of our watched folders, and watch for folders
creations/deletions */
public void runOneCycle() {
- log.debug("Running watch cycle.");
+ logger.debug("Running watch cycle.");
try {
boolean didRefresh = true;
@@ -434,8 +469,8 @@ public class JcrInstaller implements Eve
WatchedFolder.getRescanTimer().reset();
counters[SCAN_FOLDERS_COUNTER]++;
final WatchedFolder.ScanResult sr = wf.scan();
- log.info("Registering resource with OSGi installer:
{}",sr.toAdd);
- log.info("Removing resource from OSGi installer: {}",
sr.toRemove);
+ logger.info("Registering resource with OSGi installer:
{}",sr.toAdd);
+ logger.info("Removing resource from OSGi installer: {}",
sr.toRemove);
installer.updateResources(URL_SCHEME, sr.toAdd.toArray(new
InstallableResource[sr.toAdd.size()]),
sr.toRemove.toArray(new
String[sr.toRemove.size()]));
}
@@ -451,7 +486,7 @@ public class JcrInstaller implements Eve
updateFoldersListTimer.reset();
counters[UPDATE_FOLDERS_LIST_COUNTER]++;
final List<String> toRemove = updateFoldersList();
- log.info("Removing resource from OSGi installer (folder
deleted): {}", toRemove);
+ logger.info("Removing resource from OSGi installer (folder
deleted): {}", toRemove);
installer.updateResources(URL_SCHEME, null,
toRemove.toArray(new String[toRemove.size()]));
}
@@ -462,7 +497,7 @@ public class JcrInstaller implements Eve
}
} catch(Exception e) {
- log.warn("Exception in run()", e);
+ logger.warn("Exception in run()", e);
try {
Thread.sleep(RUN_LOOP_DELAY_MSEC);
} catch(InterruptedException ignore) {
@@ -475,4 +510,87 @@ public class JcrInstaller implements Eve
return counters;
}
+ /**
+ * @see
org.apache.sling.installer.api.UpdateHandler#handleUpdate(java.lang.String,
java.lang.String, String, java.io.InputStream, java.util.Dictionary)
+ */
+ public UpdateResult handleUpdate(final String resourceType,
+ final String id,
+ final String url,
+ final InputStream is,
+ final Dictionary<String, Object> dict) {
+ if ( !this.writeBack ) {
+ return null;
+ }
+
+ if ( is == null && dict == null ) {
+ final int pos = url.indexOf(':');
+ final String path = url.substring(pos + 1);
+ // remove
+ logger.debug("Removal of {}", path);
+ Session session = null;
+ try {
+ session = this.repository.loginAdministrative(null);
+ if ( session.nodeExists(path) ) {
+ session.getNode(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);
+ }
+ // we only handle add and remove for configs for now
+ if ( !resourceType.equals(InstallableResource.TYPE_CONFIG) ) {
+ return null;
+ }
+
+ final String path;
+ boolean resourceIsMoved = true;
+ if ( url != null ) {
+ // update
+ final int pos = url.indexOf(':');
+ final String oldPath = url.substring(pos + 1);
+ // 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);
+ resourceIsMoved = false;
+ } else {
+ path = oldPath;
+ }
+ logger.debug("Update of {} at {}", resourceType, path);
+ } else {
+ // add
+ path = this.newConfigPath + id;
+ logger.debug("Add of {} at {}", resourceType, path);
+ }
+
+ Session session = null;
+ try {
+ session = this.repository.loginAdministrative(null);
+ final Node configNode = JcrUtil.createPath(session, path,
ConfigNodeConverter.CONFIG_NODE_TYPE);
+ JcrUtil.removeAllProperties(configNode);
+ JcrUtil.saveProperties(configNode, dict);
+ session.save();
+
+ final UpdateResult result = new
UpdateResult(JcrInstaller.URL_SCHEME + ':' + path);
+ // priority
+ result.setPriority(this.folderNameFilter.getPriority(path));
+ result.setResourceIsMoved(resourceIsMoved);
+ return result;
+ } catch (final RepositoryException re) {
+ logger.error("Unable to remove resource from " + path, re);
+ return null;
+ } finally {
+ if ( session != null ) {
+ session.logout();
+ }
+ }
+ }
}
\ No newline at end of file
Added:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java?rev=1076166&view=auto
==============================================================================
---
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
(added)
+++
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
Wed Mar 2 10:53:34 2011
@@ -0,0 +1,174 @@
+/*
+ * 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.InputStream;
+import java.util.Calendar;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+/**
+ * The <code>JcrUtil</code> class provides helper methods used
+ * throughout this bundle.
+ */
+public abstract class JcrUtil {
+
+ private static final String FOLDER_NODE_TYPE = "sling:Folder";
+
+ /**
+ * Creates a {@link javax.jcr.Value JCR Value} for the given object with
+ * the given Session.
+ * Selects the the {@link javax.jcr.PropertyType PropertyType} according
+ * the instance of the object's Class
+ *
+ * @param value object
+ * @param session to create value for
+ * @return the value or null if not convertible to a valid PropertyType
+ * @throws RepositoryException in case of error, accessing the Repository
+ */
+ public static Value createValue(Object value, Session session)
+ throws RepositoryException {
+ Value val;
+ ValueFactory fac = session.getValueFactory();
+ if(value instanceof Calendar) {
+ val = fac.createValue((Calendar)value);
+ } else if (value instanceof InputStream) {
+ val = fac.createValue((InputStream)value);
+ } else if (value instanceof Node) {
+ val = fac.createValue((Node)value);
+ } else if (value instanceof Long) {
+ val = fac.createValue((Long)value);
+ } else if (value instanceof Integer) {
+ val = fac.createValue(((Integer)value));
+ } else if (value instanceof Number) {
+ val = fac.createValue(((Number)value).doubleValue());
+ } else if (value instanceof Boolean) {
+ val = fac.createValue((Boolean) value);
+ } else if ( value instanceof String ){
+ val = fac.createValue((String)value);
+ } else {
+ val = null;
+ }
+ return val;
+ }
+
+ /**
+ * Sets the value of the property.
+ * Selects the {@link javax.jcr.PropertyType PropertyType} according
+ * to the instance of the object's class.
+ * @param node The node where the property will be set on.
+ * @param propertyName The name of the property.
+ * @param propertyValue The value for the property.
+ */
+ public static void setProperty(final Node node,
+ final String propertyName,
+ final Object propertyValue)
+ throws RepositoryException {
+ if ( propertyValue == null ) {
+ node.setProperty(propertyName, (String)null);
+ } else if ( propertyValue.getClass().isArray() ) {
+ final Object[] values = (Object[])propertyValue;
+ final Value[] setValues = new Value[values.length];
+ for(int i=0; i<values.length; i++) {
+ setValues[i] = createValue(values[i], node.getSession());
+ }
+ node.setProperty(propertyName, setValues);
+ } else {
+ node.setProperty(propertyName, createValue(propertyValue,
node.getSession()));
+ }
+ }
+
+ /**
+ * Creates or gets the {@link javax.jcr.Node Node} at the given Path.
+ *
+ * @param session The session to use for node creation
+ * @param absolutePath absolute node path
+ * @param nodeType to use for creation of the final node
+ * @return the Node at path
+ * @throws RepositoryException in case of exception accessing the
Repository
+ */
+ public static Node createPath(final Session session,
+ final String absolutePath,
+ final String nodeType)
+ throws RepositoryException {
+ final Node parentNode = session.getRootNode();
+ String relativePath = absolutePath.substring(1);
+ if (!parentNode.hasNode(relativePath)) {
+ Node node = parentNode;
+ int pos = relativePath.lastIndexOf('/');
+ if ( pos != -1 ) {
+ final StringTokenizer st = new
StringTokenizer(relativePath.substring(0, pos), "/");
+ while ( st.hasMoreTokens() ) {
+ final String token = st.nextToken();
+ if ( !node.hasNode(token) ) {
+ try {
+ node.addNode(token, FOLDER_NODE_TYPE);
+ } catch (RepositoryException re) {
+ // we ignore this as this folder might be created
from a different task
+ node.refresh(false);
+ }
+ }
+ node = node.getNode(token);
+ }
+ relativePath = relativePath.substring(pos + 1);
+ }
+ if ( !node.hasNode(relativePath) ) {
+ node.addNode(relativePath, nodeType);
+ }
+ return node.getNode(relativePath);
+ }
+ return parentNode.getNode(relativePath);
+ }
+
+ /**
+ * Remove all properties from the node.
+ * All properties without a namespace are removed.
+ * @param node The node
+ * @throws RepositoryException
+ */
+ public static void removeAllProperties(final Node node)
+ throws RepositoryException {
+ final PropertyIterator pI = node.getProperties();
+ while ( pI.hasNext() ) {
+ final javax.jcr.Property prop = pI.nextProperty();
+ if ( !prop.getName().contains(":") ) {
+ prop.remove();
+ }
+ }
+ }
+
+ public static void saveProperties(final Node configNode,
+ final Dictionary<String, Object> dict)
+ throws RepositoryException {
+ final Enumeration<String> keys = dict.keys();
+ while ( keys.hasMoreElements() ) {
+ final String key = keys.nextElement();
+
+ JcrUtil.setProperty(configNode, key, dict.get(key));
+ }
+ }
+}
Propchange:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
------------------------------------------------------------------------------
svn:keywords = author date id revision rev url
Propchange:
sling/trunk/installer/providers/jcr/src/main/java/org/apache/sling/installer/provider/jcr/impl/JcrUtil.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified:
sling/trunk/installer/providers/jcr/src/main/resources/OSGI-INF/metatype/metatype.properties
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/src/main/resources/OSGI-INF/metatype/metatype.properties?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/providers/jcr/src/main/resources/OSGI-INF/metatype/metatype.properties
(original)
+++
sling/trunk/installer/providers/jcr/src/main/resources/OSGI-INF/metatype/metatype.properties
Wed Mar 2 10:53:34 2011
@@ -43,4 +43,11 @@ sling.jcrinstall.search.path.description
for installable resources. Combined with the installations folders name
regexp \
to select folders for scanning. Each path is followed by a colon and the
priority \
of resources found under that path, resources with higher values override
resources \
- with lower values which represent the same OSGi entity (configuration,
bundle, etc).
\ No newline at end of file
+ with lower values which represent the same OSGi entity (configuration,
bundle, etc).
+
+sling.jcrinstall.new.config.path.name = New Config Path
+sling.jcrinstall.new.config.path.description = New configurations are stored
at this location.
+
+sling.jcrinstall.enable.writeback.name = Enable Write Back
+sling.jcrinstall.enable.writeback.description = Enable writing back of changes
done through other \
+ tools like writing back configurations changed in the web console etc.
\ No newline at end of file
Modified:
sling/trunk/installer/providers/jcr/src/test/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilterTest.java
URL:
http://svn.apache.org/viewvc/sling/trunk/installer/providers/jcr/src/test/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilterTest.java?rev=1076166&r1=1076165&r2=1076166&view=diff
==============================================================================
---
sling/trunk/installer/providers/jcr/src/test/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilterTest.java
(original)
+++
sling/trunk/installer/providers/jcr/src/test/java/org/apache/sling/installer/provider/jcr/impl/FolderNameFilterTest.java
Wed Mar 2 10:53:34 2011
@@ -25,8 +25,6 @@ import static org.junit.Assert.assertTru
import java.util.HashSet;
import java.util.Set;
-import org.apache.sling.installer.provider.jcr.impl.FolderNameFilter;
-import org.apache.sling.installer.provider.jcr.impl.JcrInstaller;
import org.junit.Test;
public class FolderNameFilterTest {
@@ -47,16 +45,16 @@ public class FolderNameFilterTest {
{
final String [] paths = { "a:100", "/b/: 200 " };
final FolderNameFilter f = new FolderNameFilter(paths,
DEFAULT_REGEXP, new HashSet<String>());
- assertEquals("/a", f.getRootPaths()[0]);
- assertEquals("/b", f.getRootPaths()[1]);
+ assertEquals("/b", f.getRootPaths()[0]);
+ assertEquals("/a", f.getRootPaths()[1]);
assertEquals(100, f.getRootPriority("/a/foo"));
assertEquals(200, f.getRootPriority("/b/foo"));
}
{
final String [] paths = { "a/:NOT_AN_INTEGER", "/b/: 200 " };
final FolderNameFilter f = new FolderNameFilter(paths,
DEFAULT_REGEXP, new HashSet<String>());
- assertEquals("/a", f.getRootPaths()[0]);
- assertEquals("/b", f.getRootPaths()[1]);
+ assertEquals("/b", f.getRootPaths()[0]);
+ assertEquals("/a", f.getRootPaths()[1]);
assertEquals(FolderNameFilter.DEFAULT_ROOT_PRIORITY,
f.getRootPriority("/a/foo"));
assertEquals(200, f.getRootPriority("/b/foo"));
}