You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2015/01/02 12:05:28 UTC
svn commit: r1648997 - in /sling/trunk/installer:
core/src/main/java/org/apache/sling/installer/api/
core/src/main/java/org/apache/sling/installer/core/impl/
factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/...
Author: cziegeler
Date: Fri Jan 2 11:05:28 2015
New Revision: 1648997
URL: http://svn.apache.org/r1648997
Log:
SLING-4272 : Issues in handling of configurations wrt update handling and write back
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/EntityResourceList.java
sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.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=1648997&r1=1648996&r2=1648997&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 Fri Jan 2 11:05:28 2015
@@ -38,6 +38,17 @@ import aQute.bnd.annotation.ProviderType
public interface ResourceChangeListener {
/**
+ * This attribute defines if a change of the resource should be persisted by the
+ * installer. This property is a boolean value defaulting to true.
+ *
+ * The property should be used, if a resource should not be updated/deleted if
+ * the resource is modified/deleted outside of the installer, e.g. if a configuration
+ * is changed or deleted through configuration admin.
+ * @since 3.2.0
+ */
+ public static final String RESOURCE_PERSIST = "org.apache.sling.installer.api.persist";
+
+ /**
* Inform the installer about an added or updated
* resource
* @param resourceType The resource type
Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java (original)
+++ sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java Fri Jan 2 11:05:28 2015
@@ -198,6 +198,15 @@ public class EntityResourceList implemen
}
/**
+ * Force the state to be set
+ */
+ public void setForceFinishState(final ResourceState state) {
+ // We first set the state of the resource to install to make setFinishState work in all cases
+ ((RegisteredResourceImpl)this.getFirstResource()).setState(ResourceState.INSTALL);
+ this.setFinishState(state);
+ }
+
+ /**
* @see org.apache.sling.installer.api.tasks.TaskResourceGroup#setFinishState(org.apache.sling.installer.api.tasks.ResourceState)
*/
public void setFinishState(ResourceState state) {
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=1648997&r1=1648996&r2=1648997&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 Fri Jan 2 11:05:28 2015
@@ -938,6 +938,7 @@ implements OsgiInstaller, ResourceChange
private final List<UpdateInfo> updateInfos = new ArrayList<OsgiInstallerImpl.UpdateInfo>();
/**
+ * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}.
* @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
*/
public void resourceAddedOrUpdated(final String resourceType,
@@ -971,7 +972,26 @@ implements OsgiInstaller, ResourceChange
}
}
+ /**
+ * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}.
+ * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
+ */
+ public void resourceRemoved(final String resourceType, String resourceId) {
+ final UpdateInfo ui = new UpdateInfo();
+ ui.resourceType = resourceType;
+ ui.entityId = resourceId;
+
+ synchronized ( this.resourcesLock ) {
+ updateInfos.add(ui);
+ this.wakeUp();
+ }
+ }
+ /**
+ * Process the internal queue of updates
+ * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
+ * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
+ */
private void processUpdateInfos() {
final List<UpdateInfo> infos = new ArrayList<OsgiInstallerImpl.UpdateInfo>();
synchronized ( this.resourcesLock ) {
@@ -986,33 +1006,77 @@ implements OsgiInstaller, ResourceChange
}
}
}
+
+ private boolean handleExternalUpdateWithoutWriteBack(final EntityResourceList erl) {
+ final TaskResource tr = erl.getFirstResource();
+
+ // if this is an update but the change should not be persisted, we have to change
+ // the state to IGNORED
+ // or to UNINSTALLED if state is UNINSTALL
+ if ( tr.getState() == ResourceState.UNINSTALLED || tr.getState() == ResourceState.IGNORED ) {
+ // ignore
+ return false;
+ } else if ( tr.getState() == ResourceState.UNINSTALL ) {
+ erl.setFinishState(ResourceState.UNINSTALLED);
+ return true;
+ } else {
+ erl.setForceFinishState(ResourceState.IGNORED);
+ return true;
+ }
+ }
+ /**
+ * Handle external addition or update of a resource
+ * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
+ */
private void internalResourceAddedOrUpdated(final String resourceType,
final String entityId,
final ResourceData data,
final Dictionary<String, Object> dict,
final Map<String, Object> attributes) {
final String key = resourceType + ':' + entityId;
+ final boolean persistChange = (attributes != null ? PropertiesUtil.toBoolean(attributes.get(ResourceChangeListener.RESOURCE_PERSIST), true) : true);
try {
+ boolean compactAndSave = false;
+ boolean done = false;
+
synchronized ( this.resourcesLock ) {
final EntityResourceList erl = this.persistentList.getEntityResourceList(key);
logger.debug("Added or updated {} : {}", key, erl);
// we first check for update
- boolean updated = false;
if ( erl != null && erl.getFirstResource() != null ) {
// check digest for dictionaries
final TaskResource tr = erl.getFirstResource();
if ( dict != null ) {
final String digest = FileDataStore.computeDigest(dict);
- if ( tr.getState() == ResourceState.INSTALLED && tr.getDigest().equals(digest) ) {
- logger.debug("Resource did not change {}", key);
- return;
+ if ( tr.getDigest().equals(digest) ) {
+ if ( tr.getState() == ResourceState.INSTALLED ) {
+ logger.debug("Resource did not change {}", key);
+ } else if ( tr.getState() == ResourceState.INSTALL
+ || tr.getState() == ResourceState.IGNORED ) {
+ erl.setForceFinishState(ResourceState.INSTALLED);
+ compactAndSave = true;
+ }
+ done = true;
}
}
- final UpdateHandler handler = this.findHandler(tr.getScheme());
- if ( handler == null ) {
- logger.debug("No handler found to handle update of resource with scheme {}", tr.getScheme());
+
+ final UpdateHandler handler;
+ if ( !done && persistChange ) {
+ handler = this.findHandler(tr.getScheme());
+ if ( handler == null ) {
+ logger.debug("No handler found to handle update of resource with scheme {}", tr.getScheme());
+ }
} else {
+ handler = null;
+ }
+
+ if ( !done && handler == null ) {
+ compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl);
+ done = true;
+ }
+
+ if ( !done ) {
final InputStream localIS = data.getInputStream();
try {
final UpdateResult result = (localIS == null ? handler.handleUpdate(resourceType, entityId, tr.getURL(), data.getDictionary(), attributes)
@@ -1051,29 +1115,26 @@ implements OsgiInstaller, ResourceChange
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();
+ erl.setForceFinishState(ResourceState.INSTALLED);
}
- updated = true;
+ compactAndSave = true;
+ } else {
+ // handler does not persist
+ compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl);
}
} finally {
if ( localIS != null ) {
// always close the input stream!
- try {
- localIS.close();
- } catch (final IOException ignore) {
+ try { localIS.close(); } catch (final IOException ignore) {
// ignore
}
}
}
+ done = true;
}
-
}
- boolean created = false;
- if ( !updated ) {
+ if ( !done ) {
// create
final List<UpdateHandler> handlerList = this.updateHandlerTracker.getSortedServices();
for(final UpdateHandler handler : handlerList) {
@@ -1104,7 +1165,8 @@ implements OsgiInstaller, ResourceChange
final EntityResourceList newGroup = this.persistentList.getEntityResourceList(key);
newGroup.setFinishState(ResourceState.INSTALLED);
newGroup.compact();
- created = true;
+ compactAndSave = true;
+ done = true;
break;
}
} finally {
@@ -1118,14 +1180,14 @@ implements OsgiInstaller, ResourceChange
}
}
}
- if ( !created ) {
+ if ( !done ) {
logger.debug("No handler found to handle creation of resource {}", key);
}
}
- if ( updated || created ) {
+ if ( compactAndSave ) {
+ erl.compact();
this.persistentList.save();
}
-
}
} catch (final IOException ioe) {
logger.error("Unable to handle resource add or update of " + key, ioe);
@@ -1133,35 +1195,29 @@ implements OsgiInstaller, ResourceChange
}
/**
+ * Handle external removal a resource
* @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
*/
- public void resourceRemoved(final String resourceType, String resourceId) {
- final UpdateInfo ui = new UpdateInfo();
- ui.resourceType = resourceType;
- ui.entityId = resourceId;
-
- synchronized ( this.resourcesLock ) {
- updateInfos.add(ui);
- this.wakeUp();
- }
- }
-
- private void internalResourceRemoved(final String resourceType, String resourceId) {
+ private void internalResourceRemoved(final String resourceType, final String entityId) {
- String key = resourceType + ':' + resourceId;
+ String key = resourceType + ':' + entityId;
synchronized ( this.resourcesLock ) {
final EntityResourceList erl = this.persistentList.getEntityResourceList(key);
logger.debug("Removed {} : {}", key, erl);
// if this is not registered at all, we can simply ignore this
if ( erl != null ) {
- resourceId = erl.getResourceId();
+ final String resourceId = erl.getResourceId();
key = resourceType + ':' + resourceId;
final TaskResource tr = erl.getFirstResource();
if ( tr != null ) {
if ( tr.getState() == ResourceState.IGNORED ) {
// if it has been ignored before, we activate it now again!
- ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
- this.persistentList.save();
+ // but only if it is not a template
+ if ( tr.getDictionary() == null
+ || tr.getDictionary().get(InstallableResource.RESOURCE_IS_TEMPLATE) == null ) {
+ ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
+ this.persistentList.save();
+ }
} else if ( tr.getState() == ResourceState.UNINSTALLED ) {
// it has already been removed - nothing do to
} else {
@@ -1173,9 +1229,7 @@ implements OsgiInstaller, ResourceChange
} else {
// we don't need to check the result, we just check if a result is returned
if ( handler.handleRemoval(resourceType, resourceId, tr.getURL()) != null ) {
- // We first set the state of the resource to uninstall to make setFinishState work in all cases
- ((RegisteredResourceImpl)tr).setState(ResourceState.UNINSTALL);
- erl.setFinishState(ResourceState.UNINSTALLED);
+ erl.setForceFinishState(ResourceState.UNINSTALLED);
erl.compact();
} else {
// set to ignored
Modified: sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java (original)
+++ sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java Fri Jan 2 11:05:28 2015
@@ -122,24 +122,27 @@ public class ConfigTaskCreator
final Coordinator.Operation op = Coordinator.SHARED.get(event.getPid(), event.getFactoryPid(), false);
if ( config != null && op == null ) {
final boolean persist = ConfigUtil.toBoolean(config.getProperties().get(ConfigurationConstants.PROPERTY_PERSISTENCE), true);
- if ( persist ) {
- final Dictionary<String, Object> dict = ConfigUtil.cleanConfiguration(config.getProperties());
- final Map<String, Object> attrs = new HashMap<String, Object>();
- attrs.put(Constants.SERVICE_PID, event.getPid());
- if ( event.getFactoryPid() == null ) {
- attrs.put(InstallableResource.RESOURCE_URI_HINT, pid);
- } else {
- attrs.put(InstallableResource.RESOURCE_URI_HINT, event.getFactoryPid() + '-' + pid);
- }
- if ( config.getBundleLocation() != null ) {
- attrs.put(InstallableResource.INSTALLATION_HINT, config.getBundleLocation());
- }
- // Factory?
- if (event.getFactoryPid() != null) {
- attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
- }
- this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, id, null, dict, attrs);
+
+ final Dictionary<String, Object> dict = ConfigUtil.cleanConfiguration(config.getProperties());
+ final Map<String, Object> attrs = new HashMap<String, Object>();
+ if ( !persist ) {
+ attrs.put(ResourceChangeListener.RESOURCE_PERSIST, Boolean.FALSE);
}
+ attrs.put(Constants.SERVICE_PID, event.getPid());
+ if ( event.getFactoryPid() == null ) {
+ attrs.put(InstallableResource.RESOURCE_URI_HINT, pid);
+ } else {
+ attrs.put(InstallableResource.RESOURCE_URI_HINT, event.getFactoryPid() + '-' + pid);
+ }
+ if ( config.getBundleLocation() != null ) {
+ attrs.put(InstallableResource.INSTALLATION_HINT, config.getBundleLocation());
+ }
+ // Factory?
+ if (event.getFactoryPid() != null) {
+ attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
+ }
+ this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, id, null, dict, attrs);
+
} else {
this.logger.debug("Ignoring configuration event for {}:{}", event.getPid(), event.getFactoryPid());
}
Modified: sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java (original)
+++ sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java Fri Jan 2 11:05:28 2015
@@ -307,6 +307,152 @@ public class ConfigInstallTest extends O
}
@Test
+ public void testInstallUpdateRemoveConfig() throws Exception {
+ final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+ cfgData.put("foo", "bar");
+ final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
+ assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
+
+ // install config
+ final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
+ null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+ installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+ // get config
+ final Configuration cfg = waitForConfiguration("After installing", cfgPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+ // update configuration
+ final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+ secondData.put("foo", "bla");
+ cfg.update(secondData);
+
+ sleep(200);
+
+ // get updated config
+ final Configuration secondCfg = waitForConfiguration("After updating", cfgPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+ // remove config
+ secondCfg.delete();
+
+ sleep(200);
+
+ final Configuration origCfg = waitForConfiguration("After deleting", cfgPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", origCfg.getProperties().get("foo"));
+ }
+
+ @Test
+ public void testInstallUpdateRemoveTemplateConfig() throws Exception {
+ final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+ cfgData.put("foo", "bar");
+ cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
+ final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
+ assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
+
+ // install config
+ final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
+ null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+ installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+ // get config
+ final Configuration cfg = waitForConfiguration("After installing", cfgPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+ // update configuration
+ final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+ secondData.put("foo", "bla");
+ cfg.update(secondData);
+
+ sleep(200);
+
+ // get updated config
+ final Configuration secondCfg = waitForConfiguration("After updating", cfgPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+ // remove config
+ secondCfg.delete();
+
+ sleep(200);
+
+ final Configuration noCfg = waitForConfiguration("After deleting", cfgPid, TIMEOUT, false);
+ assertNull("Configuration should be removed", noCfg);
+ }
+
+ @Test
+ public void testInstallUpdateRemoveConfigFactory() throws Exception {
+ final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+ cfgData.put("foo", "bar");
+ final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
+ final String alias = "alias" + uniqueID();
+ assertNull("Factory config " + cfgFactoryPid + " must not be found before test", findFactoryConfiguration(cfgFactoryPid));
+
+ // install factory config
+ final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
+ null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+ installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+ // get factory config
+ final Configuration cfg = waitForFactoryConfiguration("After installing", cfgFactoryPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+ // update configuration
+ final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+ secondData.put("foo", "bla");
+ cfg.update(secondData);
+
+ sleep(200);
+
+ // get updated factory config
+ final Configuration secondCfg = waitForFactoryConfiguration("After updating", cfgFactoryPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+ // remove factory config
+ secondCfg.delete();
+ sleep(200);
+
+ final Configuration origCfg = waitForFactoryConfiguration("After deleting", cfgFactoryPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", origCfg.getProperties().get("foo"));
+ }
+
+ @Test
+ public void testInstallUpdateRemoveTemplateConfigFactory() throws Exception {
+ final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+ cfgData.put("foo", "bar");
+ cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
+ final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
+ final String alias = "alias" + uniqueID();
+ assertNull("Factory config " + cfgFactoryPid + " must not be found before test", findFactoryConfiguration(cfgFactoryPid));
+
+ // install factory config
+ final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
+ null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+ installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+ // get factory config
+ final Configuration cfg = waitForFactoryConfiguration("After installing", cfgFactoryPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+ // update configuration
+ final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+ secondData.put("foo", "bla");
+ cfg.update(secondData);
+
+ sleep(200);
+
+ // get updated factory config
+ final Configuration secondCfg = waitForFactoryConfiguration("After updating", cfgFactoryPid, TIMEOUT, true);
+ assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+ // remove config
+ secondCfg.delete();
+ sleep(200);
+
+ final Configuration noCfg = waitForFactoryConfiguration("After deleting", cfgFactoryPid, TIMEOUT, false);
+ assertNull("Factory configuration should be removed", noCfg);
+ }
+
+ @Test
public void testDeferredConfigInstall() throws Exception {
// get config admin bundle and wait for service
final Bundle configAdmin = this.getConfigAdminBundle();