diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 5ca124e1c2..0b7a896325 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -30,6 +30,13 @@ + + + - - - diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index fd3189c903..1d9f58fa79 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -270,6 +270,14 @@ + + + + + + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 767a32a0c5..3fccc42714 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -213,6 +213,7 @@ alfresco.messages.system-messages + alfresco.messages.module-messages alfresco.messages.dictionary-messages alfresco.messages.version-service alfresco.messages.permissions-service diff --git a/config/alfresco/messages/module-messages.properties b/config/alfresco/messages/module-messages.properties new file mode 100644 index 0000000000..d98dc8cab6 --- /dev/null +++ b/config/alfresco/messages/module-messages.properties @@ -0,0 +1,12 @@ +# Module messages + +module.msg.found_modules=Found {0} module(s). +module.msg.starting= Starting module ''{0}'' version {1}. +module.msg.installing= Installing module ''{0}'' version {1}. +module.msg.upgrading= Upgrading module ''{0}'' version {1} (was {2}). + +module.err.downgrading_not_supported=\nDowngrading of modules is not supported.\nModule ''{0}'' version {1} is currently installed and must be uninstalled before version {2} can be installed. +module.err.already_executed=The module component has already been executed: {0}.{1} +module.err.execution_failed=A module component ''{0}'' failed to execute: {1} +module.err.component_already_registered=A component named ''{0}'' has already been registered for module ''{1}''. +module.err.unable_to_open_module_properties=The module properties file ''{0}'' could not be read. \ No newline at end of file diff --git a/config/alfresco/module-context.xml b/config/alfresco/module-context.xml new file mode 100644 index 0000000000..db670a0ebb --- /dev/null +++ b/config/alfresco/module-context.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/module/test/module-context.xml.sample b/config/alfresco/module/test/module-context.xml.sample new file mode 100644 index 0000000000..586382e10e --- /dev/null +++ b/config/alfresco/module/test/module-context.xml.sample @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/module/test/module.properties.sample b/config/alfresco/module/test/module.properties.sample new file mode 100644 index 0000000000..6f07422d49 --- /dev/null +++ b/config/alfresco/module/test/module.properties.sample @@ -0,0 +1,5 @@ +# A test module +module.id=test.xyz +module.title=Test XYZ +module.description=A module for unit testing +module.version=3.0 \ No newline at end of file diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 2b15e9e115..a830294e78 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1320,4 +1320,32 @@ + + + + + org.alfresco.service.cmr.module.ModuleService + + + + + + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index b921a3e0f7..4c74cdd1ff 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -43,7 +43,7 @@ import org.apache.commons.logging.LogFactory; public abstract class AbstractPatch implements Patch { /** - * I18N message when properties nto set. + * I18N message when properties not set. *
    *
  • {0} = property name
  • *
  • {1} = patch instance
  • diff --git a/source/java/org/alfresco/repo/admin/registry/RegistryKey.java b/source/java/org/alfresco/repo/admin/registry/RegistryKey.java new file mode 100644 index 0000000000..81531c4a87 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/registry/RegistryKey.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.admin.registry; + +import java.io.Serializable; + +/** + * Key for looking up registry metadata. + * + * @author Derek Hulley + */ +public class RegistryKey implements Serializable +{ + private static final long serialVersionUID = 1137822242292626854L; + + static final String REGISTRY_1_0_URI = "http://www.alfresco.org/system/registry/1.0"; + + private String namespaceUri; + private String[] path; + private String property; + + /** + * Build a registry key from a given array of elements. + */ + private static String buildPathString(String... elements) + { + if (elements.length == 0) + { + return "/"; + } + StringBuilder sb = new StringBuilder(); + for (String element : elements) + { + if (element == null || element.length() == 0) + { + throw new IllegalArgumentException("Key elements may not be empty or null"); + } + sb.append("/").append(element); + } + return sb.toString(); + } + + /** + * For path /a/b/c and property 'x', put in
    "a", "b", "c", "x"
    + * The property can also be null as in
    "a", "b", "c", null
    + * + * @param namespaceUri the key namespace to use. If left null then the + * {@link #REGISTRY_1_0_URI default} will be used. + * @param key the path elements followed by the property name. + */ + public RegistryKey(String namespaceUri, String... key) + { + if (namespaceUri == null) + { + namespaceUri = REGISTRY_1_0_URI; + } + this.namespaceUri = namespaceUri; + // The last value is the property + int length = key.length; + if (length == 0) + { + throw new IllegalArgumentException("No value supplied for the RegistryKey property"); + } + this.property = key[length - 1]; + this.path = new String[length - 1]; + System.arraycopy(key, 0, path, 0, length - 1); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("RegistryKey") + .append("[ ").append(RegistryKey.buildPathString(path)).append("/").append(property) + .append(" ]"); + return sb.toString(); + } + + public String getNamespaceUri() + { + return namespaceUri; + } + + public String[] getPath() + { + return path; + } + + public String getProperty() + { + return property; + } + +} diff --git a/source/java/org/alfresco/repo/admin/registry/RegistryService.java b/source/java/org/alfresco/repo/admin/registry/RegistryService.java index 338d0a02c3..9000a28b14 100644 --- a/source/java/org/alfresco/repo/admin/registry/RegistryService.java +++ b/source/java/org/alfresco/repo/admin/registry/RegistryService.java @@ -29,16 +29,16 @@ public interface RegistryService /** * Assign a value to the registry key, which must be of the form /a/b/c. * - * @param key the registry key path delimited with '/'. + * @param key the registry key. * @param value any value that can be stored in the repository. */ - void addValue(String key, Serializable value); + void addValue(RegistryKey key, Serializable value); /** - * @param key the registry key path delimited with '/'. + * @param key the registry key. * @return Returns the value stored in the key. * * @see #addValue(String, Serializable) */ - Serializable getValue(String key); + Serializable getValue(RegistryKey key); } diff --git a/source/java/org/alfresco/repo/admin/registry/RegistryServiceImpl.java b/source/java/org/alfresco/repo/admin/registry/RegistryServiceImpl.java index a73cd97acc..1f77d22160 100644 --- a/source/java/org/alfresco/repo/admin/registry/RegistryServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/registry/RegistryServiceImpl.java @@ -18,7 +18,6 @@ package org.alfresco.repo.admin.registry; import java.io.Serializable; import java.util.List; -import java.util.StringTokenizer; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -33,6 +32,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; +import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -154,65 +154,44 @@ public class RegistryServiceImpl implements RegistryService return registryRootNodeRef; } - /** - * @return Returns a pair representing the node path and the property name - */ - private Pair splitKey(String key) - { - int index = key.lastIndexOf('/'); - Pair result = null; - if (index < 0) // It is just a property - { - result = new Pair("/", key); - } - else - { - String propertyName = key.substring(index + 1, key.length()); - if (propertyName.length() == 0) - { - throw new IllegalArgumentException("The registry key is invalid: " + key); - } - result = new Pair(key.substring(0, index), propertyName); - } - // done - return result; - } - /** * @return Returns the node and property name represented by the key or null * if it doesn't exist and was not allowed to be created */ - private Pair getPath(String key, boolean create) + private Pair getPath(RegistryKey key, boolean create) { // Get the root NodeRef currentNodeRef = getRegistryRootNodeRef(); - // Split the key - Pair keyPair = splitKey(key); - // Parse the key - StringTokenizer tokenizer = new StringTokenizer(keyPair.getFirst(), "/"); + // Get the key and property + String namespaceUri = key.getNamespaceUri(); + String[] pathElements = key.getPath(); + String property = key.getProperty(); // Find the node and property to put the value - while (tokenizer.hasMoreTokens()) + for (String pathElement : pathElements) { - String token = tokenizer.nextToken(); - String name = QName.createValidLocalName(token); - QName qname = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, name); + QName assocQName = QName.createQName( + namespaceUri, + QName.createValidLocalName(pathElement)); // Find the node List childAssocRefs = nodeService.getChildAssocs( currentNodeRef, ContentModel.ASSOC_CHILDREN, - qname); + assocQName); int size = childAssocRefs.size(); if (size == 0) // Found nothing with that path { if (create) // Must create the path { - // Create the node + // Create the node (with a name) + PropertyMap properties = new PropertyMap(); + properties.put(ContentModel.PROP_NAME, pathElement); currentNodeRef = nodeService.createNode( currentNodeRef, ContentModel.ASSOC_CHILDREN, - qname, - ContentModel.TYPE_CONTAINER).getChildRef(); + assocQName, + ContentModel.TYPE_CONTAINER, + properties).getChildRef(); } else { @@ -244,15 +223,15 @@ public class RegistryServiceImpl implements RegistryService } // Create the result QName propertyQName = QName.createQName( - NamespaceService.SYSTEM_MODEL_1_0_URI, - QName.createValidLocalName(keyPair.getSecond())); + namespaceUri, + QName.createValidLocalName(property)); Pair resultPair = new Pair(currentNodeRef, propertyQName); // done if (logger.isDebugEnabled()) { logger.debug("Converted registry key: \n" + - " key pair: " + keyPair + "\n" + - " result: " + resultPair); + " Key: " + key + "\n" + + " Result: " + resultPair); } if (resultPair.getFirst() == null) { @@ -267,7 +246,7 @@ public class RegistryServiceImpl implements RegistryService /** * @inheritDoc */ - public void addValue(String key, Serializable value) + public void addValue(RegistryKey key, Serializable value) { // Get the path, with creation support Pair keyPair = getPath(key, true); @@ -282,7 +261,7 @@ public class RegistryServiceImpl implements RegistryService } } - public Serializable getValue(String key) + public Serializable getValue(RegistryKey key) { // Get the path, without creating Pair keyPair = getPath(key, false); diff --git a/source/java/org/alfresco/repo/admin/registry/RegistryServiceImplTest.java b/source/java/org/alfresco/repo/admin/registry/RegistryServiceImplTest.java index 42a454f882..7d97d0f622 100644 --- a/source/java/org/alfresco/repo/admin/registry/RegistryServiceImplTest.java +++ b/source/java/org/alfresco/repo/admin/registry/RegistryServiceImplTest.java @@ -40,7 +40,7 @@ public class RegistryServiceImplTest extends TestCase authenticationComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); registryService = (RegistryService) ctx.getBean("RegistryService"); - // Run as admin + // Run as system user authenticationComponent.setSystemUserAsCurrentUser(); } @@ -65,13 +65,16 @@ public class RegistryServiceImplTest extends TestCase private static final Long VALUE_ONE = 1L; private static final Long VALUE_TWO = 2L; private static final Long VALUE_THREE = 3L; - private static final String KEY_A_B_C_1 = "/a/b/c/1"; - private static final String KEY_A_B_C_2 = "/a/b/c/2"; - private static final String KEY_A_B_C_3 = "/a/b/c/3"; - private static final String KEY_A_B_C_D_1 = "/a/b/c/d/1"; - private static final String KEY_A_B_C_D_2 = "/a/b/c/d/2"; - private static final String KEY_A_B_C_D_3 = "/a/b/c/d/3"; - private static final String KEY_SPECIAL = "/me & you/ whatever"; + private static final RegistryKey KEY_A_B_C_0 = new RegistryKey(null, "a", "b", "c", "0"); + private static final RegistryKey KEY_A_B_C_1 = new RegistryKey(null, "a", "b", "c", "1"); + private static final RegistryKey KEY_A_B_C_2 = new RegistryKey(null, "a", "b", "c", "2"); + private static final RegistryKey KEY_A_B_C_3 = new RegistryKey(null, "a", "b", "c", "3"); + private static final RegistryKey KEY_A_B_C_D_0 = new RegistryKey(null, "a", "b", "c", "d", "0"); + private static final RegistryKey KEY_A_B_C_D_1 = new RegistryKey(null, "a", "b", "c", "d", "1"); + private static final RegistryKey KEY_A_B_C_D_2 = new RegistryKey(null, "a", "b", "c", "d", "2"); + private static final RegistryKey KEY_A_B_C_D_3 = new RegistryKey(null, "a", "b", "c", "d", "3"); + private static final RegistryKey KEY_X_Y_Z_0 = new RegistryKey(null, "x", "y", "z", "0"); + private static final RegistryKey KEY_SPECIAL = new RegistryKey(null, "me & you", "whatever"); /** * General writing and reading back. */ @@ -91,9 +94,9 @@ public class RegistryServiceImplTest extends TestCase assertEquals("Incorrect value from service registry", VALUE_TWO, registryService.getValue(KEY_A_B_C_D_2)); assertEquals("Incorrect value from service registry", VALUE_THREE, registryService.getValue(KEY_A_B_C_D_3)); - assertNull("Missing key should return null value", registryService.getValue("/a/b/c/0")); - assertNull("Missing key should return null value", registryService.getValue("/a/b/c/d/0")); - assertNull("Missing key should return null value", registryService.getValue("/x/y/z/0")); + assertNull("Missing key should return null value", registryService.getValue(KEY_A_B_C_0)); + assertNull("Missing key should return null value", registryService.getValue(KEY_A_B_C_D_0)); + assertNull("Missing key should return null value", registryService.getValue(KEY_X_Y_Z_0)); } public void testSpecialCharacters() diff --git a/source/java/org/alfresco/repo/domain/PropertyValue.java b/source/java/org/alfresco/repo/domain/PropertyValue.java index 341dcc7724..4e3f517eb8 100644 --- a/source/java/org/alfresco/repo/domain/PropertyValue.java +++ b/source/java/org/alfresco/repo/domain/PropertyValue.java @@ -35,6 +35,7 @@ import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.VersionNumber; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -256,6 +257,20 @@ public class PropertyValue implements Cloneable, Serializable { return DefaultTypeConverter.INSTANCE.convert(Locale.class, value); } + }, + VERSION_NUMBER + { + @Override + protected ValueType getPersistedType(Serializable value) + { + return ValueType.STRING; + } + + @Override + Serializable convert(Serializable value) + { + return DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, value); + } }; /** @@ -362,6 +377,10 @@ public class PropertyValue implements Cloneable, Serializable { return ValueType.LOCALE; } + else if (value instanceof VersionNumber) + { + return ValueType.VERSION_NUMBER; + } else { // type is not recognised as belonging to any particular slot diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index 9b4e1b6eb3..9313b97307 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -30,10 +30,11 @@ import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; -import java.util.StringTokenizer; import javax.transaction.UserTransaction; +import net.sf.acegisecurity.Authentication; + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -249,21 +250,8 @@ public class ImporterBootstrap extends AbstractLifecycleBean public void setLocale(String locale) { // construct locale - StringTokenizer t = new StringTokenizer(locale, "_"); - int tokens = t.countTokens(); - if (tokens == 1) - { - this.locale = new Locale(locale); - } - else if (tokens == 2) - { - this.locale = new Locale(t.nextToken(), t.nextToken()); - } - else if (tokens == 3) - { - this.locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken()); - } - + this.locale = I18NUtil.parseLocale(locale); + // store original strLocale = locale; } @@ -333,6 +321,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean } UserTransaction userTransaction = transactionService.getUserTransaction(); + Authentication authentication = authenticationComponent.getCurrentAuthentication(); authenticationComponent.setSystemUserAsCurrentUser(); try @@ -448,7 +437,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean } finally { - try {authenticationComponent.clearCurrentSecurityContext(); } catch (Throwable ex) {} + try {authenticationComponent.setCurrentAuthentication(authentication); } catch (Throwable ex) {} } } diff --git a/source/java/org/alfresco/repo/module/AbstractModuleComponent.java b/source/java/org/alfresco/repo/module/AbstractModuleComponent.java new file mode 100644 index 0000000000..d7f930e87d --- /dev/null +++ b/source/java/org/alfresco/repo/module/AbstractModuleComponent.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VersionNumber; +import org.springframework.beans.factory.BeanNameAware; + +/** + * Implementation of a {@link org.alfresco.repo.module.ModuleComponent} to provide + * the basic necessities. + * + * @see #executeInternal() + * + * @author Roy Wetherall + * @author Derek Hulley + * @since 2.0 + */ +public abstract class AbstractModuleComponent implements ModuleComponent, BeanNameAware +{ + private static final String ERR_ALREADY_EXECUTED = "module.err.already_executed"; + private static final String ERR_EXECUTION_FAILED = "module.err.execution_failed"; + + // Supporting components + protected ServiceRegistry serviceRegistry; + protected AuthenticationComponent authenticationComponent; + protected ModuleService moduleService; + + private String moduleId; + private String name; + private String description; + private VersionNumber sinceVersion; + private VersionNumber appliesFromVersion; + private VersionNumber appliesToVersion; + private List dependsOn; + /** Defaults to true */ + private boolean executeOnceOnly; + private boolean executed; + + public AbstractModuleComponent() + { + sinceVersion = VersionNumber.VERSION_ZERO; + appliesFromVersion = VersionNumber.VERSION_ZERO; + appliesToVersion = VersionNumber.VERSION_BIG; + dependsOn = new ArrayList(0); + executeOnceOnly = true; + executed = false; + } + + /** + * Checks for the presence of all generally-required properties. + */ + protected void checkProperties() + { + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "authenticationComponent", authenticationComponent); + PropertyCheck.mandatory(this, "moduleId", moduleId); + PropertyCheck.mandatory(this, "name", name); + PropertyCheck.mandatory(this, "sinceVersion", sinceVersion); + PropertyCheck.mandatory(this, "appliesFromVersion", appliesFromVersion); + PropertyCheck.mandatory(this, "appliesToVersion", appliesToVersion); + } + + /** + * @see #getModuleId() + * @see #getName() + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("ModuleComponent") + .append("[ module=").append(moduleId) + .append(", name=").append(name) + .append(", since=").append(sinceVersion) + .append(", appliesFrom=").append(appliesFromVersion) + .append(", appliesTo=").append(appliesToVersion) + .append(", onceOnly=").append(executeOnceOnly) + .append("]"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (false == obj instanceof ModuleComponent) + { + return false; + } + ModuleComponent that = (ModuleComponent) obj; + return (EqualsHelper.nullSafeEquals(this.moduleId, that.getModuleId()) + && EqualsHelper.nullSafeEquals(this.name, that.getName())); + } + + @Override + public int hashCode() + { + return moduleId.hashCode() + 17 * name.hashCode(); + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + /** + * Set the module service to register with. If not set, the component will not be + * automatically started. + * + * @param moduleService the service to register against. This is optional. + */ + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @inheritDoc + */ + public String getModuleId() + { + return moduleId; + } + + /** + * @param moduleId the globally unique module name. + */ + public void setModuleId(String moduleId) + { + this.moduleId = moduleId; + } + + /** + * @inheritDoc + */ + public String getName() + { + return name; + } + + /** + * Set the component name, which must be unique within the context of the + * module. If the is not set, then the bean name will be used. + * + * @param name the name of the component within the module. + * + * @see #setBeanName(String) + */ + public void setName(String name) + { + this.name = name; + } + + /** + * Convenience method that will set the name of the component to + * match the bean name, unless the {@link #setName(String) name} has + * been explicitly set. + */ + public void setBeanName(String name) + { + setName(name); + } + + /** + * @inheritDoc + */ + public String getDescription() + { + return description; + } + + /** + * Set the component's description. This will automatically be I18N'ized, so it may just + * be a resource bundle key. + * + * @param description a description of the component. + */ + public void setDescription(String description) + { + this.description = description; + } + + /** + * @inheritDoc + */ + public VersionNumber getSinceVersionNumber() + { + return sinceVersion; + } + + /** + * Set the version number for which this component was added. + */ + public void setSinceVersion(String version) + { + this.sinceVersion = new VersionNumber(version); + } + + /** + * @inheritDoc + */ + public VersionNumber getAppliesFromVersionNumber() + { + return appliesFromVersion; + } + + /** + * Set the minimum module version number to which this component applies. + * Default 0.0. + */ + public void setAppliesFromVersion(String version) + { + this.appliesFromVersion = new VersionNumber(version); + } + + /** + * @inheritDoc + */ + public VersionNumber getAppliesToVersionNumber() + { + return appliesToVersion; + } + + /** + * Set the minimum module version number to which this component applies. + * Default 999.0. + */ + public void setAppliesToVersion(String version) + { + this.appliesToVersion = new VersionNumber(version); + } + + /** + * @inheritDoc + */ + public List getDependsOn() + { + return dependsOn; + } + + /** + * @param dependsOn a list of modules that must be executed before this one + */ + public void setDependsOn(List dependsOn) + { + this.dependsOn = dependsOn; + } + + /** + * @inheritDoc + * + * @return Returns true always. Override as required. + */ + public boolean isExecuteOnceOnly() + { + return executeOnceOnly; + } + + /** + * @param executeOnceOnly true to force execution of this component with + * each startup or false if it must only be executed once. + */ + public void setExecuteOnceOnly(boolean executeOnceOnly) + { + this.executeOnceOnly = executeOnceOnly; + } + + public void init() + { + // Ensure that the description gets I18N'ized + description = I18NUtil.getMessage(description); + // Register the component with the service + if (moduleService != null) // Allows optional registration of the component + { + moduleService.registerComponent(this); + } + } + + /** + * The method that performs the actual work. For the most part, derived classes will + * only have to override this method to be fully functional. + * + * @throws Throwable any problems, just throw them + */ + protected abstract void executeInternal() throws Throwable; + + /** + * @inheritDoc + * + * @see #executeInternal() the abstract method to be implemented by subclasses + */ + public final synchronized void execute() + { + // ensure that this has not been executed already + if (executed) + { + throw AlfrescoRuntimeException.create(ERR_ALREADY_EXECUTED, moduleId, name); + } + // Ensure properties have been set + checkProperties(); + // Execute + try + { + executeInternal(); + } + catch (Throwable e) + { + throw AlfrescoRuntimeException.create(e, ERR_EXECUTION_FAILED, name, e.getMessage()); + } + finally + { + // There are no second chances + executed = true; + } + } +} diff --git a/source/java/org/alfresco/repo/module/ComponentsTest.java b/source/java/org/alfresco/repo/module/ComponentsTest.java new file mode 100644 index 0000000000..98a2f382c2 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ComponentsTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.Collection; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Tests various module components. + * + * @see org.alfresco.repo.module.ImporterModuleComponent + * @see org.alfresco.repo.module.ModuleComponent + * + * @author Derek Hulley + */ +public class ComponentsTest extends TestCase +{ + private static ApplicationContext ctx = new ClassPathXmlApplicationContext("module/module-component-test-beans.xml"); + + private ServiceRegistry serviceRegistry; + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private NodeService nodeService; + private UserTransaction txn; + + @Override + protected void setUp() throws Exception + { + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + authenticationComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); + transactionService = serviceRegistry.getTransactionService(); + nodeService = serviceRegistry.getNodeService(); + + // Run as system user + authenticationComponent.setSystemUserAsCurrentUser(); + + // Start a transaction + txn = transactionService.getUserTransaction(); + } + + @Override + protected void tearDown() throws Exception + { + // Clear authentication + try + { + authenticationComponent.clearCurrentSecurityContext(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + // Rollback the transaction + try + { +// txn.rollback(); + txn.commit(); + } + catch (Throwable e) + { + // Ignore + } + } + + /** Ensure that the test starts and stops properly */ + public void testSetup() throws Exception + { + } + + private NodeRef getLoadedCategoryRoot() + { + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + + CategoryService categoryService = serviceRegistry.getCategoryService(); + // Check if the categories exist + Collection assocRefs = categoryService.getRootCategories( + storeRef, + ContentModel.ASPECT_GEN_CLASSIFIABLE); + // Find it + for (ChildAssociationRef assocRef : assocRefs) + { + NodeRef nodeRef = assocRef.getChildRef(); + if (nodeRef.getId().equals("test:xyz-root")) + { + // Found it + return nodeRef; + } + } + return null; + } + + public void testImporterModuleComponent() throws Exception + { + // Delete any pre-existing data + NodeRef nodeRef = getLoadedCategoryRoot(); + if (nodeRef != null) + { + CategoryService categoryService = serviceRegistry.getCategoryService(); + categoryService.deleteCategory(nodeRef); + } + // Double check to make sure it is gone + nodeRef = getLoadedCategoryRoot(); + assertNull("Category not deleted", nodeRef); + + ImporterModuleComponent component = (ImporterModuleComponent) ctx.getBean("module.test.importerComponent"); + // Execute it + component.execute(); + + // Now make sure the data exists + nodeRef = getLoadedCategoryRoot(); + assertNotNull("Loaded category root not found", nodeRef); + } +} diff --git a/source/java/org/alfresco/repo/module/ImporterModuleComponent.java b/source/java/org/alfresco/repo/module/ImporterModuleComponent.java new file mode 100644 index 0000000000..ed30973ba3 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ImporterModuleComponent.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.util.PropertyCheck; + + +/** + * Generic module component that can be wired up to import data into the system. + * + * @author Derek Hulley + * @since 2.0 + */ +public class ImporterModuleComponent extends AbstractModuleComponent +{ + private ImporterBootstrap importer; + private Properties bootstrapView; + private List bootstrapViews; + + /** + * Set the helper that has details of the store to load the data into. + * Alfresco has a set of predefined importers for all the common stores in use. + * + * @param importer the bootstrap bean that performs the store bootstrap. + */ + public void setImporter(ImporterBootstrap importer) + { + this.importer = importer; + } + + /** + * Set a list of bootstrap views to import.
    + * This is an alternative to {@link #setBootstrapViews(List)}. + * + * @param bootstrapView the bootstrap data location + * + * @see ImporterBootstrap#setBootstrapViews(List) + */ + public void setBootstrapView(Properties bootstrapView) + { + this.bootstrapView = bootstrapView; + } + + /** + * Set a list of bootstrap views to import.
    + * This is an alternative to {@link #setBootstrapView(Properties)}. + * + * @param bootstrapViews the bootstrap data locations + * + * @see ImporterBootstrap#setBootstrapViews(List) + */ + public void setBootstrapViews(List bootstrapViews) + { + this.bootstrapViews = bootstrapViews; + } + + @Override + protected void checkProperties() + { + PropertyCheck.mandatory(this, "importerBootstrap", importer); + if (bootstrapView == null && bootstrapViews == null) + { + PropertyCheck.mandatory(this, null, "bootstrapViews or bootstrapView"); + } + // fulfil contract of override + super.checkProperties(); + } + + @Override + protected void executeInternal() throws Throwable + { + // Construct the bootstrap views + List views = new ArrayList(1); + if (bootstrapViews != null) + { + views.addAll(bootstrapViews); + } + if (bootstrapView != null) + { + views.add(bootstrapView); + } + // modify the bootstrapper + importer.setBootstrapViews(views); + importer.setUseExistingStore(true); // allow import into existing store + + importer.bootstrap(); + // Done + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleComponent.java b/source/java/org/alfresco/repo/module/ModuleComponent.java new file mode 100644 index 0000000000..a8b5a2b7fc --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleComponent.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.List; + +import org.alfresco.util.VersionNumber; + +/** + * Interface for classes that control the startup and shutdown behaviour of modules. + *

    + * Note that the execution order of these components is on the basis of dependencies + * only. The version numbering determines only whether a component will be executed + * and doesn't imply any ordering. + *

    + * Equals and Hashcode method must be implemented. + * + * @author Derek Hulley + * @since 2.0 + */ +public interface ModuleComponent +{ + /** + * @return Returns the globally unique module ID. + */ + String getModuleId(); + + /** + * @return Returns the name of the component in the context of the module ID. It does not + * have to be globally unique. + */ + String getName(); + + /** + * + * @return Returns a description of the component. + */ + String getDescription(); + + /** + * @return Returns the version number of the module for which this component was introduced. + */ + VersionNumber getSinceVersionNumber(); + + /** + * @return Returns the smallest version number of the module to which this component applies. + */ + VersionNumber getAppliesFromVersionNumber(); + + /** + * @return Returns the largest version number of the module to which this component applies. + */ + VersionNumber getAppliesToVersionNumber(); + + /** + * A list of module components that must be executed prior to this instance. + * This is the only way to guarantee ordered execution. The dependencies may include + * components from other modules, guaranteeing an early failure if a module is missing. + * + * @return Returns a list of components that must be executed prior to this component. + */ + List getDependsOn(); + + /** + * @return Returns true if the component is to be successfully executed exactly once, + * or false if the component must be executed with each startup. + */ + boolean isExecuteOnceOnly(); + + /** + * Perform the actual component's work. Execution will be done within the context of a + * system account with an enclosing transaction. Long-running processes should be spawned + * from the calling thread, if required. + *

    + * All failures should just be thrown out as runtime exceptions and will be dealt with by + * the associated module infrastructure. + */ + void execute(); +} diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java new file mode 100644 index 0000000000..c26d9067c6 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sf.acegisecurity.Authentication; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.registry.RegistryKey; +import org.alfresco.repo.admin.registry.RegistryService; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VersionNumber; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Helper class to split up some of the code for managing module components. This class handles + * the execution of the module components. + * + * @author Derek Hulley + */ +public class ModuleComponentHelper +{ + public static final String URI_MODULES_1_0 = "http://www.alfresco.org/system/modules/1.0"; + private static final String REGISTRY_PATH_MODULES = "modules"; + private static final String REGISTRY_PROPERTY_INSTALLED_VERSION = "installedVersion"; + private static final String REGISTRY_PROPERTY_CURRENT_VERSION = "currentVersion"; + private static final String REGISTRY_PATH_COMPONENTS = "components"; + private static final String REGISTRY_PROPERTY_EXECUTION_DATE = "executionDate"; + + private static final String MSG_FOUND_MODULES = "module.msg.found_modules"; + private static final String MSG_STARTING = "module.msg.starting"; + private static final String MSG_INSTALLING = "module.msg.installing"; + private static final String MSG_UPGRADING = "module.msg.upgrading"; + private static final String ERR_NO_DOWNGRADE = "module.err.downgrading_not_supported"; + private static final String ERR_COMPONENT_ALREADY_REGISTERED = "module.err.component_already_registered"; + + private static Log logger = LogFactory.getLog(ModuleComponentHelper.class); + private static Log loggerService = LogFactory.getLog(ModuleServiceImpl.class); + + private ServiceRegistry serviceRegistry; + private AuthenticationComponent authenticationComponent; + private RegistryService registryService; + private ModuleService moduleService; + private Map> componentsByNameByModule; + + /** Default constructor */ + public ModuleComponentHelper() + { + componentsByNameByModule = new HashMap>(7); + } + + /** + * @param serviceRegistry provides access to the service APIs + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param authenticationComponent allows execution as system user. + */ + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + /** + * @param registryService the service used to persist component execution details. + */ + public void setRegistryService(RegistryService registryService) + { + this.registryService = registryService; + } + + /** + * @param moduleService the service from which to get the available modules. + */ + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + /** + * Add a managed module component to the registry of components. These will be controlled + * by the {@link #startModules()} method. + * + * @param component a module component to be executed + */ + public synchronized void registerComponent(ModuleComponent component) + { + String moduleId = component.getModuleId(); + String name = component.getName(); + // Get the map of components for the module + Map componentsByName = componentsByNameByModule.get(moduleId); + if (componentsByName == null) + { + componentsByName = new HashMap(11); + componentsByNameByModule.put(moduleId, componentsByName); + } + // Check if the component has already been registered + if (componentsByName.containsKey(name)) + { + throw AlfrescoRuntimeException.create(ERR_COMPONENT_ALREADY_REGISTERED, name, moduleId); + } + // Add it + componentsByName.put(name, component); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Registered component: " + component); + } + } + + /** + * @return Returns the map of components keyed by name. The map could be empty but + * will never be null. + */ + private synchronized Map getComponents(String moduleId) + { + Map componentsByName = componentsByNameByModule.get(moduleId); + if (componentsByName != null) + { + // Done + return componentsByName; + } + else + { + // Done + return Collections.emptyMap(); + } + } + + /** + * @inheritDoc + */ + public synchronized void startModules() + { + // Check properties + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "authenticationComponent", authenticationComponent); + PropertyCheck.mandatory(this, "registryService", registryService); + PropertyCheck.mandatory(this, "moduleService", moduleService); + /* + * Ensure transactionality and the correct authentication + */ + // Get the current authentication + Authentication authentication = authenticationComponent.getCurrentAuthentication(); + try + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + authenticationComponent.setSystemUserAsCurrentUser(); + // Get all the modules + List modules = moduleService.getAllModules(); + loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); + // Process each module in turn. Ordering is not important. + final Set executedComponents = new HashSet(10); + for (final ModuleDetails module : modules) + { + TransactionWork startModuleWork = new TransactionWork() + { + public Object doWork() throws Exception + { + startModule(module, executedComponents); + return null; + } + }; + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, startModuleWork); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Executed " + executedComponents.size() + " components"); + } + } + finally + { + // Restore the original authentication + authenticationComponent.setCurrentAuthentication(authentication); + } + } + + /** + * Does the actual work without fussing about transactions and authentication. + */ + private void startModule(ModuleDetails module, Set executedComponents) + { + String moduleId = module.getId(); + VersionNumber moduleVersion = module.getVersionNumber(); + // Get the module details from the registry + RegistryKey moduleKeyInstalledVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_INSTALLED_VERSION); + RegistryKey moduleKeyCurrentVersion = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PROPERTY_CURRENT_VERSION); + VersionNumber versionCurrent = (VersionNumber) registryService.getValue(moduleKeyCurrentVersion); + String msg = null; + if (versionCurrent == null) // There is no current version + { + msg = I18NUtil.getMessage(MSG_INSTALLING, moduleId, moduleVersion); + // Record the install version + registryService.addValue(moduleKeyInstalledVersion, moduleVersion); + } + else // It is an upgrade or is the same + { + if (versionCurrent.compareTo(moduleVersion) == 0) // The current version is the same + { + msg = I18NUtil.getMessage(MSG_STARTING, moduleId, moduleVersion); + } + else if (versionCurrent.compareTo(moduleVersion) > 0) // Downgrading not supported + { + throw AlfrescoRuntimeException.create(ERR_NO_DOWNGRADE, moduleId, versionCurrent, moduleVersion); + } + else // This is an upgrade + { + msg = I18NUtil.getMessage(MSG_UPGRADING, moduleId, moduleVersion, versionCurrent); + } + } + loggerService.info(msg); + // Record the current version + registryService.addValue(moduleKeyCurrentVersion, moduleVersion); + + Map componentsByName = getComponents(moduleId); + for (ModuleComponent component : componentsByName.values()) + { + executeComponent(module, component, executedComponents); + } + + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Started module: " + module); + } + } + + /** + * Execute the component, respecting dependencies. + */ + private void executeComponent(ModuleDetails module, ModuleComponent component, Set executedComponents) + { + // Ignore if it has been executed in this run already + if (executedComponents.contains(component)) + { + // Already done + if (logger.isDebugEnabled()) + { + logger.debug("Skipping component already executed in this run: \n" + + " Component: " + component); + } + return; + } + + // Check the version applicability + VersionNumber moduleVersion = module.getVersionNumber(); + VersionNumber minVersion = component.getAppliesFromVersionNumber(); + VersionNumber maxVersion = component.getAppliesToVersionNumber(); + if (moduleVersion.compareTo(minVersion) < 0 || moduleVersion.compareTo(maxVersion) > 0) + { + // It is out of the allowable range for execution so we just ignore it + if (logger.isDebugEnabled()) + { + logger.debug("Skipping component that doesn't apply to the current version: \n" + + " Component: " + component + "\n" + + " Module: " + module + "\n" + + " Version: " + moduleVersion + "\n" + + " Applies From : " + minVersion + "\n" + + " Applies To : " + maxVersion); + } + return; + } + + // Construct the registry key to store the execution date + String moduleId = component.getModuleId(); + String name = component.getName(); + RegistryKey executionDateKey = new RegistryKey( + ModuleComponentHelper.URI_MODULES_1_0, + REGISTRY_PATH_MODULES, moduleId, REGISTRY_PATH_COMPONENTS, name, REGISTRY_PROPERTY_EXECUTION_DATE); + + // Check if the component has been executed + Date executionDate = (Date) registryService.getValue(executionDateKey); + if (executionDate != null && component.isExecuteOnceOnly()) + { + // It has been executed and is scheduled for a single execution - leave it + if (logger.isDebugEnabled()) + { + logger.debug("Skipping already-executed module component: \n" + + " Component: " + component + "\n" + + " Execution Time: " + executionDate); + } + return; + } + // It may have been executed, but not in this run and it is allowed to be repeated + // Check for dependencies + List dependencies = component.getDependsOn(); + for (ModuleComponent dependency : dependencies) + { + executeComponent(module, dependency, executedComponents); + } + // Execute the component itself + component.execute(); + // Keep track of it in the registry and in this run + executedComponents.add(component); + registryService.addValue(executionDateKey, new Date()); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Executed module component: \n" + + " Component: " + component); + } + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java new file mode 100644 index 0000000000..00de91f456 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.admin.registry.RegistryService; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.util.BaseAlfrescoTestCase; +import org.alfresco.util.VersionNumber; + +/** + * @see org.alfresco.repo.module.ModuleComponentHelper + *

    + * This test creates a bunch of dummy components and then simulates + * startups with different module current versions. + *

    + * There are 3 modules. There are 3 components. + * + * @author Derek Hulley + */ +public class ModuleComponentHelperTest extends BaseAlfrescoTestCase +{ + private final String CURRENT_TIME = "" + System.currentTimeMillis() + "-" + System.nanoTime(); + private final String[] MODULE_IDS = + { + "M0 @ " + CURRENT_TIME, + "M1 @ " + CURRENT_TIME, + "M2 @ " + CURRENT_TIME + }; + private final String[] COMPONENT_NAMES = + { + "C0 @ " + CURRENT_TIME, + "C1 @ " + CURRENT_TIME, + "C2 @ " + CURRENT_TIME + }; + private final VersionNumber[] VERSIONS = + { + new VersionNumber("0"), + new VersionNumber("1"), + new VersionNumber("2"), + new VersionNumber("3") + }; + private static final Map EXECUTION_COUNT_BY_VERSION; + static + { + EXECUTION_COUNT_BY_VERSION = new HashMap(13); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("0.0"), 3); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("0.5"), 3); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("1.0"), 6); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("1.5"), 3); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("2.0"), 6); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("2.5"), 3); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("3.0"), 3); + EXECUTION_COUNT_BY_VERSION.put(new VersionNumber("3.5"), 0); + }; + + private RegistryService registryService; + private DummyModuleService moduleService; + private ModuleComponentHelper helper; + + private DummyModuleComponent[][] components; + + public void setUp() throws Exception + { + super.setUp(); + + registryService = (RegistryService) ctx.getBean("RegistryService"); + + moduleService = new DummyModuleService(); + helper = new ModuleComponentHelper(); + helper.setAuthenticationComponent(super.authenticationComponent); + helper.setModuleService(moduleService); + helper.setRegistryService(registryService); + helper.setServiceRegistry(serviceRegistry); + + // Register the components + components = new DummyModuleComponent[3][3]; // i,j + for (int i = 0; i < 3; i++) // i = module number + { + for (int j = 0; j < 3; j++) // j = component number + { + DummyModuleComponent component = new DummyModuleComponent( + MODULE_IDS[i], + COMPONENT_NAMES[j], + VERSIONS[j], + VERSIONS[j+1]); + component.setServiceRegistry(serviceRegistry); + component.setAuthenticationComponent(authenticationComponent); + component.setModuleService(moduleService); + // Don't initialize the component as that will do the registration. We do it manually. + helper.registerComponent(component); + // Add to array + components[i][j] = component; + } + } + // M1-C1 depends on M0-C1 + components[1][1].setDependsOn(Collections.singletonList(components[0][1])); + } + + public void testSetup() throws Exception + { + // See that it all starts OK + } + + private void startComponents(VersionNumber moduleVersion) + { + int expectedCount = (Integer) EXECUTION_COUNT_BY_VERSION.get(moduleVersion); + // Set the current version number for all modules + moduleService.setCurrentVersion(moduleVersion); + // Start them + helper.startModules(); + // Check + assertEquals("Incorrent number of executions (version " + moduleVersion + ")", expectedCount, executed); + } + + public void testStartComponentsV00() + { + VersionNumber moduleVersion = new VersionNumber("0.0"); + startComponents(moduleVersion); + } + + public void testStartComponentsV05() + { + VersionNumber moduleVersion = new VersionNumber("0.5"); + startComponents(moduleVersion); + } + + public void testStartComponentsV10() + { + VersionNumber moduleVersion = new VersionNumber("1.0"); + startComponents(moduleVersion); + } + + public void testStartComponentsV15() + { + VersionNumber moduleVersion = new VersionNumber("1.5"); + startComponents(moduleVersion); + } + + public void testStartComponentsV30() + { + VersionNumber moduleVersion = new VersionNumber("3.0"); + startComponents(moduleVersion); + } + + public void testStartComponentsV35() + { + VersionNumber moduleVersion = new VersionNumber("3.5"); + startComponents(moduleVersion); + } + + /** + * Helper bean to simulate module presences under controlled conditions. + */ + private class DummyModuleService implements ModuleService + { + private VersionNumber currentVersion; + /** Set the current version of all the modules */ + public void setCurrentVersion(VersionNumber currentVersion) + { + this.currentVersion = currentVersion; + } + + public void registerComponent(ModuleComponent component) + { + throw new UnsupportedOperationException(); + } + + public List getAllModules() + { + // Reset the execution count + executed = 0; + // Create some module details + List details = new ArrayList(3); + for (int i = 0; i < 3; i++) + { + ModuleDetails moduleDetails = new ModuleDetailsImpl( + MODULE_IDS[i], + currentVersion, + "Module-" + i, + "Description-" + i); + details.add(moduleDetails); + } + // Done + return details; + } + + public ModuleDetails getModule(String moduleId) + { + throw new UnsupportedOperationException(); + } + + public void startModules() + { + throw new UnsupportedOperationException(); + } + } + + /** Keep track of the execution count */ + static int executed = 0; + + /** + * A dummy + * @author Derek Hulley + */ + private class DummyModuleComponent extends AbstractModuleComponent + { + private DummyModuleComponent(String moduleId, String name, VersionNumber from, VersionNumber to) + { + super.setServiceRegistry(serviceRegistry); + super.setAuthenticationComponent(authenticationComponent); + super.setModuleService(moduleService); + + super.setModuleId(moduleId); + super.setName(name); + super.setAppliesFromVersion(from.toString()); + super.setAppliesToVersion(to.toString()); + super.setSinceVersion("10.1.2"); + + super.setDescription("A dummy module component"); + } + + @Override + protected void executeInternal() throws Throwable + { + // Record execution + executed++; + } + } + + /** No-operation tester class */ + public static class NoopModuleComponent extends AbstractModuleComponent + { + @Override + protected void executeInternal() throws Throwable + { + } + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java b/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java new file mode 100644 index 0000000000..de59b6b3d5 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleDetailsImpl.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import org.alfresco.repo.module.tool.ModuleManagementToolException; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.util.VersionNumber; + +/** + * Module details implementation. + * + * Loads details from the serialized properties file provided. + * + * @author Roy Wetherall + */ +public class ModuleDetailsImpl implements ModuleDetails +{ + /** Property names */ + protected static final String PROP_ID = "module.id"; + protected static final String PROP_TITLE = "module.title"; + protected static final String PROP_DESCRIPTION = "module.description"; + protected static final String PROP_VERSION = "module.version"; + protected static final String PROP_INSTALL_DATE = "module.installDate"; + + /** Properties object */ + protected Properties properties; + + /** + * Constructor + * + * @param is input stream, which will be closed + */ + public ModuleDetailsImpl(InputStream is) + { + try + { + this.properties = new Properties(); + this.properties.load(is); + } + catch (IOException exception) + { + throw new ModuleManagementToolException("Unable to load module details from property file.", exception); + } + finally + { + try { is.close(); } catch (IOException e) { e.printStackTrace(); } + } + } + + /** + * Constructor + * + * @param id module id + * @param versionNumber version number + * @param title title + * @param description description + */ + public ModuleDetailsImpl(String id, VersionNumber versionNumber, String title, String description) + { + this.properties = new Properties(); + this.properties.setProperty(PROP_ID, id); + this.properties.setProperty(PROP_VERSION, versionNumber.toString()); + this.properties.setProperty(PROP_TITLE, title); + this.properties.setProperty(PROP_DESCRIPTION, description); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#exists() + */ + public boolean exists() + { + return (this.properties != null); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#getId() + */ + public String getId() + { + return this.properties.getProperty(PROP_ID); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#getVersionNumber() + */ + public VersionNumber getVersionNumber() + { + return new VersionNumber(this.properties.getProperty(PROP_VERSION)); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#getTitle() + */ + public String getTitle() + { + return this.properties.getProperty(PROP_TITLE); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#getDescription() + */ + public String getDescription() + { + return this.properties.getProperty(PROP_DESCRIPTION); + } + + /** + * @see org.alfresco.service.cmr.module.ModuleDetails#getInstalledDate() + */ + public String getInstalledDate() + { + return this.properties.getProperty(PROP_INSTALL_DATE); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return getId(); + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/ModuleManagementTool.java deleted file mode 100644 index c1d8134330..0000000000 --- a/source/java/org/alfresco/repo/module/ModuleManagementTool.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * 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.alfresco.repo.module; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.util.VersionNumber; -import org.apache.log4j.Logger; - -import de.schlichtherle.io.DefaultRaesZipDetector; -import de.schlichtherle.io.File; -import de.schlichtherle.io.FileInputStream; -import de.schlichtherle.io.FileOutputStream; -import de.schlichtherle.io.ZipControllerException; -import de.schlichtherle.io.ZipDetector; -import de.schlichtherle.io.ZipWarningException; - -/** - * @author Roy Wetherall - */ -public class ModuleManagementTool -{ - public static Logger logger = Logger.getLogger("org.alfresco.repo.extension.ModuleManagementTool"); - - private static final String DEFAULT_FILE_MAPPING_PROPERTIES = "org/alfresco/repo/module/default-file-mapping.properties"; - private static final String MODULE_DIR = "/WEB-INF/classes/alfresco/module"; - - private static final String DELIMITER = ":"; - - private static final String PROP_ID = "module.id"; - private static final String PROP_TITLE = "module.title"; - private static final String PROP_DESCRIPTION = "module.description"; - private static final String PROP_VERSION = "module.version"; - - private static final String MOD_ADD_FILE = "add"; - private static final String MOD_UPDATE_FILE = "update"; - private static final String MOD_MK_DIR = "mkdir"; - - private static final String OP_INSTALL = "install"; - - private ZipDetector defaultDetector; - - private Properties fileMappingProperties; - - private boolean verbose = false; - - public ModuleManagementTool() - { - // Create the default zip detector - this.defaultDetector = new DefaultRaesZipDetector("amp|war"); - - // Load the default file mapping properties - this.fileMappingProperties = new Properties(); - InputStream is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_FILE_MAPPING_PROPERTIES); - try - { - this.fileMappingProperties.load(is); - } - catch (IOException exception) - { - throw new ModuleManagementToolException("Unable to load default extension file mapping properties.", exception); - } - } - - public boolean isVerbose() - { - return verbose; - } - - public void setVerbose(boolean verbose) - { - this.verbose = verbose; - } - - public void installModule(String ampFileLocation, String warFileLocation) - { - try - { - // Load the extension properties - File installingPropertiesFile = new File(ampFileLocation + "/module.properties", this.defaultDetector); - if (installingPropertiesFile.exists() == false) - { - throw new ModuleManagementToolException("Extension properties are not present in the AMP. Check that a valid module.properties file is present."); - } - Properties installingProperties = new Properties(); - installingProperties.load(new FileInputStream(installingPropertiesFile)); - - // Get the intalling extension version - String installingVersionString = installingProperties.getProperty(PROP_VERSION); - if (installingVersionString == null || installingVersionString.length() == 0) - { - throw new ModuleManagementToolException("The version number has not been specified in the module properties found in the AMP."); - } - VersionNumber installingVersion = new VersionNumber(installingVersionString); - - // Get the installed directory - File installDir = getInstalledDir(warFileLocation); - - // Look for a previously installed version of this extension - File installedExtensionPropertiesFile = new File(installDir.getPath() + "/" + getModuleDetailsFileName(installingProperties.getProperty(PROP_ID)), this.defaultDetector); - if (installedExtensionPropertiesFile.exists() == true) - { - Properties installedExtensionProperties = new Properties(); - InputStream is = new FileInputStream(installedExtensionPropertiesFile); - installedExtensionProperties.load(is); - - // Get the installed version - VersionNumber installedVersion = new VersionNumber(installedExtensionProperties.getProperty(PROP_VERSION)); - int compareValue = installedVersion.compareTo(installingVersion); - if (compareValue == -1) - { - // Trying to update the extension, old files need to cleaned before we proceed - cleanWAR(warFileLocation, installedExtensionProperties); - } - else if (compareValue == 0) - { - // Trying to install the same extension version again - verboseMessage("WARNING: This version of this module is already installed in the WAR"); - throw new ModuleManagementToolException("This version of this module is alreay installed. Use the 'force' parameter if you want to overwrite the current installation."); - } - else if (compareValue == 1) - { - // Trying to install an earlier version of the extension - verboseMessage("WARNING: A later version of this module is already installed in the WAR"); - throw new ModuleManagementToolException("An earlier version of this module is already installed. You must first unistall the current version before installing this version of the module."); - } - - } - - // TODO check for any additional file mapping propeties supplied in the AEP file - - // Copy the files from the AEP file into the WAR file - Map modifications = new HashMap(50); - for (Map.Entry entry : this.fileMappingProperties.entrySet()) - { - modifications.putAll(copyToWar(ampFileLocation, warFileLocation, (String)entry.getKey(), (String)entry.getValue())); - } - - // Copy the properties file into the war - if (installedExtensionPropertiesFile.exists() == false) - { - installedExtensionPropertiesFile.createNewFile(); - } - InputStream is = new FileInputStream(installingPropertiesFile); - try - { - installedExtensionPropertiesFile.catFrom(is); - } - finally - { - is.close(); - } - - // Create and add the modifications file to the war - writeModificationToFile(installDir.getPath() + "/" + getModuleModificationFileName(installingProperties.getProperty(PROP_ID)), modifications); - - // Update the zip file's - File.update(); - } - catch (ZipWarningException ignore) - { - // Only instances of the class ZipWarningException exist in the chain of - // exceptions. We choose to ignore this. - } - catch (ZipControllerException exception) - { - // At least one exception occured which is not just a ZipWarningException. - // This is a severe situation that needs to be handled. - throw new ModuleManagementToolException("A Zip error was encountered during deployment of the AEP into the WAR", exception); - } - catch (IOException exception) - { - throw new ModuleManagementToolException("An IO error was encountered during deployment of the AEP into the WAR", exception); - } - } - - private void cleanWAR(String warFileLocation, Properties installedExtensionProperties) - { - // Get the currently installed modifications - Map modifications = readModificationsFromFile(warFileLocation + "/" + getModuleModificationFileName(installedExtensionProperties.getProperty(PROP_ID))); - - for (Map.Entry modification : modifications.entrySet()) - { - String modType = modification.getValue(); - if (MOD_ADD_FILE.equals(modType) == true) - { - // Remove file - } - else if (MOD_UPDATE_FILE.equals(modType) == true) - { - // Remove file - // Replace with back-up - } - else if (MOD_MK_DIR.equals(modType) == true) - { - // Add to list of dir's to remove at the end - } - } - } - - private Map copyToWar(String aepFileLocation, String warFileLocation, String sourceDir, String destinationDir) - throws IOException - { - Map result = new HashMap(10); - - String sourceLocation = aepFileLocation + sourceDir; - File aepConfig = new File(sourceLocation, this.defaultDetector); - - for (java.io.File sourceChild : aepConfig.listFiles()) - { - String destinationFileLocation = warFileLocation + destinationDir + "/" + sourceChild.getName(); - File destinationChild = new File(destinationFileLocation, this.defaultDetector); - if (sourceChild.isFile() == true) - { - boolean createFile = false; - if (destinationChild.exists() == false) - { - destinationChild.createNewFile(); - createFile = true; - } - FileInputStream fis = new FileInputStream(sourceChild); - try - { - destinationChild.catFrom(fis); - } - finally - { - fis.close(); - } - - if (createFile == true) - { - result.put(destinationDir + "/" + sourceChild.getName(), MOD_ADD_FILE); - this.verboseMessage("File added: " + destinationDir + "/" + sourceChild.getName()); - } - else - { - result.put(destinationDir + "/" + sourceChild.getName(), MOD_UPDATE_FILE); - this.verboseMessage("File updated:" + destinationDir + "/" + sourceChild.getName()); - } - } - else - { - boolean mkdir = false; - if (destinationChild.exists() == false) - { - destinationChild.mkdir(); - mkdir = true; - } - - Map subResult = copyToWar(aepFileLocation, warFileLocation, sourceDir + "/" + sourceChild.getName(), - destinationDir + "/" + sourceChild.getName()); - result.putAll(subResult); - - if (mkdir == true) - { - result.put(destinationDir + "/" + sourceChild.getName(), MOD_MK_DIR); - this.verboseMessage("Directory added: " + destinationDir + "/" + sourceChild.getName()); - } - } - } - - return result; - } - - private File getInstalledDir(String warFileLocation) - { - // Check for the installed directory in the WAR file - File installedDir = new File(warFileLocation + MODULE_DIR, this.defaultDetector); - if (installedDir.exists() == false) - { - installedDir.mkdir(); - } - return installedDir; - } - - public void disableModule(String moduleId, String warLocation) - { - System.out.println("Currently unsupported ..."); - } - - public void enableModule(String moduleId, String warLocation) - { - System.out.println("Currently unsupported ..."); - } - - public void uninstallModule(String moduleId, String warLocation) - { - System.out.println("Currently unsupported ..."); - } - - public void listModules(String warLocation) - { - System.out.println("Currently unsupported ..."); - } - - private void verboseMessage(String message) - { - if (this.verbose == true) - { - System.out.println(message); - } - } - - private void writeModificationToFile(String fileLocation, Map modifications) - throws IOException - { - File file = new File(fileLocation, this.defaultDetector); - if (file.exists() == false) - { - file.createNewFile(); - } - FileOutputStream os = new FileOutputStream(file); - try - { - for (Map.Entry mod : modifications.entrySet()) - { - String output = mod.getValue() + DELIMITER + mod.getKey() + "\n"; - os.write(output.getBytes()); - } - } - finally - { - os.close(); - } - } - - private Map readModificationsFromFile(String fileLocation) - { - Map modifications = new HashMap(50); - - File file = new File(fileLocation, this.defaultDetector); - try - { - BufferedReader reader = new BufferedReader(new FileReader(file)); - try - { - String line = reader.readLine(); - while (line != null) - { - line = reader.readLine(); - String[] modification = line.split(DELIMITER); - modifications.put(modification[1], modification[0]); - } - } - finally - { - reader.close(); - } - } - catch(FileNotFoundException exception) - { - throw new ModuleManagementToolException("The module file install file '" + fileLocation + "' does not exist"); - } - catch(IOException exception) - { - throw new ModuleManagementToolException("Error whilst reading file '" + fileLocation); - } - - return modifications; - } - - private String getModuleDetailsFileName(String moduleId) - { - return "module-" + moduleId + ".install"; - } - - private String getModuleModificationFileName(String moduleId) - { - return "module-" + moduleId + "-modifications.install"; - } - - /** - * @param args - */ - public static void main(String[] args) - { - if (args.length >= 1) - { - ModuleManagementTool manager = new ModuleManagementTool(); - - String operation = args[0]; - if (operation.equals(OP_INSTALL) == true && args.length >= 3) - { - String aepFileLocation = args[1]; - String warFileLocation = args[2]; - - manager.installModule(aepFileLocation, warFileLocation); - } - else - { - outputUsage(); - } - } - else - { - outputUsage(); - } - } - - private static void outputUsage() - { - System.out.println("output useage ..."); - } - -} diff --git a/source/java/org/alfresco/repo/module/ModuleManagementToolTest.java b/source/java/org/alfresco/repo/module/ModuleManagementToolTest.java deleted file mode 100644 index f942bfc6f5..0000000000 --- a/source/java/org/alfresco/repo/module/ModuleManagementToolTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * 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.alfresco.repo.module; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -import junit.framework.TestCase; - -import org.springframework.util.FileCopyUtils; - -import de.schlichtherle.io.DefaultRaesZipDetector; -import de.schlichtherle.io.FileOutputStream; -import de.schlichtherle.io.ZipDetector; - -/** - * @author Roy Wetherall - */ -public class ModuleManagementToolTest extends TestCase -{ - private ModuleManagementTool manager = new ModuleManagementTool(); - - ZipDetector defaultDetector = new DefaultRaesZipDetector("amp|war"); - - public void testBasicInstall() - throws Exception - { - manager.setVerbose(true); - - String warLocation = getFileLocation(".war", "module/test.war"); - String ampLocation = getFileLocation(".amp", "module/test.amp"); - - System.out.println(warLocation); - - // Initial install of module - this.manager.installModule(ampLocation, warLocation); - - // Check that the war has been modified correctly - List files = new ArrayList(10); - files.add("/WEB-INF/classes/alfresco/module/module-test.install"); - files.add("/WEB-INF/classes/alfresco/module/module-test-modifications.install"); - files.add("/WEB-INF/lib/test.jar"); - files.add("/WEB-INF/classes/alfresco/module/test/module-context.xml"); - files.add("/WEB-INF/classes/alfresco/module/test"); - files.add("/WEB-INF/licenses/license.txt"); - files.add("/scripts/test.js"); - files.add("/images/test.jpg"); - files.add("/jsp/test.jsp"); - files.add("/css/test.css"); - checkForFileExistance(warLocation, files); - - // Try and install same version - try - { - this.manager.installModule(ampLocation, warLocation); - fail("The module is already installed so an exception should have been raised since we are not forcing an overwite"); - } - catch(ModuleManagementToolException exception) - { - // Pass - } - - // Install a later version - // TODO - - // Try and install and earlier version - // TODO - - - } - - private String getFileLocation(String extension, String location) - throws IOException - { - File file = File.createTempFile("moduleManagementToolTest-", extension); - InputStream is = this.getClass().getClassLoader().getResourceAsStream(location); - OutputStream os = new FileOutputStream(file); - FileCopyUtils.copy(is, os); - return file.getPath(); - } - - private void checkForFileExistance(String warLocation, List files) - { - for (String file : files) - { - File file0 = new de.schlichtherle.io.File(warLocation + file, this.defaultDetector); - assertTrue("The file/dir " + file + " does not exist in the WAR.", file0.exists()); - } - } -} diff --git a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java new file mode 100644 index 0000000000..c41b32cfcb --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.registry.RegistryService; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.module.ModuleDetails; +import org.alfresco.service.cmr.module.ModuleService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +/** + * This component controls the execution of + * {@link org.alfresco.repo.module.runtime.ModuleComponent module startup components}. + *

    + * All required startup executions are performed in a single transaction, so this + * component guarantees that the module initialization is consistent. Module components are + * executed in dependency order only. The version numbering is not to be used + * for ordering purposes. + *

    + * Afterwards, execution details are persisted in the + * {@link org.alfresco.repo.admin.registry.RegistryService service registry} to be used when the + * server starts up again. + * + * @author Roy Wetherall + * @author Derek Hulley + * @since 2.0 + */ +public class ModuleServiceImpl implements ModuleService +{ + /** Error messages **/ + private static final String ERR_UNABLE_TO_OPEN_MODULE_PROPETIES = "module.err.unable_to_open_module_properties"; + + /** The classpath search path for module properties */ + private static final String MODULE_CONFIG_SEARCH_ALL = "classpath*:alfresco/module/*/module.properties"; + + private static Log logger = LogFactory.getLog(ModuleServiceImpl.class); + + private ServiceRegistry serviceRegistry; + private AuthenticationComponent authenticationComponent; + private ModuleComponentHelper moduleComponentHelper; + /** A cache of module details by module ID */ + private Map moduleDetailsById; + + /** Default constructor */ + public ModuleServiceImpl() + { + moduleComponentHelper = new ModuleComponentHelper(); + moduleComponentHelper.setModuleService(this); + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + this.moduleComponentHelper.setServiceRegistry(this.serviceRegistry); + } + + /** + * @param authenticationComponent allows execution as system user. + */ + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + this.moduleComponentHelper.setAuthenticationComponent(this.authenticationComponent); + } + + /** + * @param registryService the service used to persist component execution details. + */ + public void setRegistryService(RegistryService registryService) + { + this.moduleComponentHelper.setRegistryService(registryService); + } + + /** + * @see ModuleComponentHelper#registerComponent(ModuleComponent) + */ + public void registerComponent(ModuleComponent component) + { + this.moduleComponentHelper.registerComponent(component); + } + + /** + * @inheritDoc + * + * @see ModuleComponentHelper#startModules() + */ + public void startModules() + { + moduleComponentHelper.startModules(); + } + + /** + * @inheritDoc + */ + public ModuleDetails getModule(String moduleId) + { + cacheModuleDetails(); + // Get the details of the specific module + ModuleDetails details = moduleDetailsById.get(moduleId); + // Done + return details; + } + + /** + * @inheritDoc + */ + public List getAllModules() + { + cacheModuleDetails(); + Collection moduleDetails = moduleDetailsById.values(); + // Make a copy to avoid modification of cached data by clients (and to satisfy API) + List result = new ArrayList(moduleDetails); + // Done + return result; + } + + /** + * Ensure that the {@link #moduleDetailsById module details} are populated. + *

    + * TODO: We will have to avoid caching or add context listening if we support reloading + * of beans one day. + */ + private synchronized void cacheModuleDetails() + { + if (moduleDetailsById != null) + { + // There is nothing to do + return; + } + try + { + moduleDetailsById = new HashMap(13); + + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources(MODULE_CONFIG_SEARCH_ALL); + + // Read each resource + for (Resource resource : resources) + { + try + { + InputStream is = new BufferedInputStream(resource.getInputStream()); + ModuleDetails details = new ModuleDetailsImpl(is); + moduleDetailsById.put(details.getId(), details); + } + catch (Throwable e) + { + throw AlfrescoRuntimeException.create(e, ERR_UNABLE_TO_OPEN_MODULE_PROPETIES, resource); + } + } + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to retrieve module information", e); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Found " + moduleDetailsById.size() + " modules: \n" + + " Modules: " + moduleDetailsById); + } + } +} diff --git a/source/java/org/alfresco/repo/module/ModuleStarter.java b/source/java/org/alfresco/repo/module/ModuleStarter.java new file mode 100644 index 0000000000..78ef38e1c1 --- /dev/null +++ b/source/java/org/alfresco/repo/module/ModuleStarter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module; + +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.util.AbstractLifecycleBean; +import org.alfresco.util.PropertyCheck; +import org.springframework.context.ApplicationEvent; + +/** + * This component is responsible for ensuring that patches are applied + * at the appropriate time. + * + * @author Derek Hulley + */ +public class ModuleStarter extends AbstractLifecycleBean +{ + private ModuleService moduleService; + + /** + * @param moduleService the service that will do the actual work. + */ + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + PropertyCheck.mandatory(this, "moduleService", moduleService); + moduleService.startModules(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } +} diff --git a/source/java/org/alfresco/repo/module/tool/InstalledFiles.java b/source/java/org/alfresco/repo/module/tool/InstalledFiles.java new file mode 100644 index 0000000000..43c52210c2 --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/InstalledFiles.java @@ -0,0 +1,217 @@ +package org.alfresco.repo.module.tool; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.schlichtherle.io.File; +import de.schlichtherle.io.FileInputStream; +import de.schlichtherle.io.FileOutputStream; + +/** + * Details of the files installed during a module installation into a WAR + * + * @author Roy Wetherall + */ +public class InstalledFiles +{ + /** Modification types */ + private static final String MOD_ADD_FILE = "add"; + private static final String MOD_UPDATE_FILE = "update"; + private static final String MOD_MK_DIR = "mkdir"; + + /** Delimieter used in the file */ + private static final String DELIMITER = "|"; + + /** War location **/ + private String warLocation; + + /** Module id **/ + private String moduleId; + + /** Lists containing the modifications made */ + private List adds = new ArrayList(); + private Map updates = new HashMap(); + private List mkdirs = new ArrayList(); + + /** + * Constructor + * + * @param warLocation the war location + * @param moduleId the module id + */ + public InstalledFiles(String warLocation, String moduleId) + { + this.warLocation = warLocation; + this.moduleId = moduleId; + } + + /** + * Loads the exisiting information about the installed files from the WAR + */ + public void load() + { + File file = new File(getFileLocation(), ModuleManagementTool.defaultDetector); + if (file.exists() == true) + { + try + { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + try + { + String line = reader.readLine(); + while (line != null) + { + String[] modification = line.split("\\" + DELIMITER); + String mod = modification[0]; + String location = modification[1]; + if (mod.equals(MOD_ADD_FILE) == true) + { + this.adds.add(location); + } + else if (mod.equals(MOD_MK_DIR) == true) + { + this.mkdirs.add(location); + } + else if (mod.equals(MOD_UPDATE_FILE) == true) + { + this.updates.put(location, modification[2]); + } + line = reader.readLine(); + } + } + finally + { + reader.close(); + } + } + catch(FileNotFoundException exception) + { + throw new ModuleManagementToolException("The module file install file '" + getFileLocation() + "' does not exist", exception); + } + catch(IOException exception) + { + throw new ModuleManagementToolException("Error whilst reading file '" + getFileLocation(), exception); + } + } + } + + /** + * Saves the current modification details into the WAR + */ + public void save() + { + try + { + File file = new File(getFileLocation(), ModuleManagementTool.defaultDetector); + if (file.exists() == false) + { + file.createNewFile(); + } + FileOutputStream os = new FileOutputStream(file); + try + { + for (String add : this.adds) + { + String output = MOD_ADD_FILE + DELIMITER + add + "\n"; + os.write(output.getBytes()); + } + for (Map.Entry update : this.updates.entrySet()) + { + String output = MOD_UPDATE_FILE + DELIMITER + update.getKey() + DELIMITER + update.getValue() + "\n"; + os.write(output.getBytes()); + } + for (String mkdir : this.mkdirs) + { + String output = MOD_MK_DIR + DELIMITER + mkdir + "\n"; + os.write(output.getBytes()); + } + } + finally + { + os.close(); + } + } + catch(IOException exception) + { + throw new ModuleManagementToolException("Error whilst saving modifications file.", exception); + } + } + + /** + * Returns the location of the modifications file based on the module id + * + * @return the file location + */ + private String getFileLocation() + { + return this.warLocation + ModuleManagementTool.MODULE_DIR + "/" + this.moduleId + "/modifications.install"; + } + + /** + * Get all the added files + * + * @return list of files added to war + */ + public List getAdds() + { + return adds; + } + + /** + * Get all the updated files, key is the file that has been updated and the value is the + * location of the backup made before modification took place. + * + * @return map of file locaiton and backup + */ + public Map getUpdates() + { + return updates; + } + + /** + * Gets a list of the dirs added during install + * + * @return list of directories added + */ + public List getMkdirs() + { + return mkdirs; + } + + /** + * Add a file addition + * + * @param location the file added + */ + public void addAdd(String location) + { + this.adds.add(location); + } + + /** + * Add a file update + * + * @param location the file updated + * @param backup the backup location + */ + public void addUpdate(String location, String backup) + { + this.updates.put(location, backup); + } + + /** + * Add a directory + * + * @param location the directory location + */ + public void addMkdir(String location) + { + this.mkdirs.add(location); + } +} diff --git a/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java new file mode 100644 index 0000000000..352e94e92a --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/ModuleDetailsHelper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module.tool; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; + +import org.alfresco.repo.module.ModuleDetailsImpl; + +import de.schlichtherle.io.File; +import de.schlichtherle.io.FileInputStream; +import de.schlichtherle.io.FileOutputStream; + +/** + * Module details helper used by the module mangement tool + * + * @author Roy Wetherall + */ +public class ModuleDetailsHelper extends ModuleDetailsImpl +{ + /** + * Constructor + * + * @param is input stream + */ + public ModuleDetailsHelper(InputStream is) + { + super(is); + } + + /** + * Creates a module details helper object based on a file location. + * + * @param location file location + * @return module details helper object + */ + public static ModuleDetailsHelper create(String location) + { + ModuleDetailsHelper result = null; + try + { + File file = new File(location, ModuleManagementTool.defaultDetector); + if (file.exists() == true) + { + result = new ModuleDetailsHelper(new FileInputStream(file)); + } + } + catch (IOException exception) + { + throw new ModuleManagementToolException("Unable to load module details from property file.", exception); + } + return result; + } + + /** + * Creates a module details helper object based on a war location and the module id + * + * @param warLocation the war location + * @param moduleId the module id + * @return the module details helper + */ + public static ModuleDetailsHelper create(String warLocation, String moduleId) + { + return ModuleDetailsHelper.create(ModuleDetailsHelper.getFileLocation(warLocation, moduleId)); + } + + /** + * Gets the file location + * + * @param warLocation the war location + * @param moduleId the module id + * @return the file location + */ + private static String getFileLocation(String warLocation, String moduleId) + { + return warLocation + ModuleManagementTool.MODULE_DIR + "/" + moduleId + "/" + "module.properties"; + } + + /** + * Saves the module detailsin to the war in the correct location based on the module id + * + * @param warLocation the war location + * @param moduleId the module id + */ + public void save(String warLocation, String moduleId) + { + try + { + File file = new File(getFileLocation(warLocation, moduleId), ModuleManagementTool.defaultDetector); + if (file.exists() == false) + { + file.createNewFile(); + } + + OutputStream os = new FileOutputStream(file); + try + { + Date now = new Date(); + this.properties.setProperty(PROP_INSTALL_DATE, now.toString()); + this.properties.store(os, null); + } + finally + { + os.close(); + } + } + catch (IOException exception) + { + throw new ModuleManagementToolException("Unable to save module details into WAR file.", exception); + } + } + +} diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java new file mode 100644 index 0000000000..5a001310f2 --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementTool.java @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module.tool; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +import org.alfresco.util.GUID; +import org.apache.log4j.Logger; +import org.springframework.util.FileCopyUtils; + +import de.schlichtherle.io.DefaultRaesZipDetector; +import de.schlichtherle.io.File; +import de.schlichtherle.io.FileInputStream; +import de.schlichtherle.io.ZipControllerException; +import de.schlichtherle.io.ZipDetector; +import de.schlichtherle.io.ZipWarningException; + +/** + * Module management tool. + * + * Manages the modules installed in a war file. Allows modules to be installed, updated, enabled, disabled and + * uninstalled. Information about the module installed is also available. + * + * @author Roy Wetherall + */ +public class ModuleManagementTool +{ + /** Logger */ + public static Logger logger = Logger.getLogger("org.alfresco.repo.extension.ModuleManagementTool"); + + /** Location of the default mapping properties file */ + private static final String DEFAULT_FILE_MAPPING_PROPERTIES = "org/alfresco/repo/module/tool/default-file-mapping.properties"; + + /** Standard directories found in the alfresco war */ + public static final String MODULE_DIR = "/WEB-INF/classes/alfresco/module"; + public static final String BACKUP_DIR = MODULE_DIR + "/backup"; + + /** Operations and options supperted via the command line interface to this class */ + private static final String OP_INSTALL = "install"; + private static final String OP_LIST = "list"; + private static final String OPTION_VERBOSE = "-verbose"; + private static final String OPTION_FORCE = "-force"; + private static final String OPTION_PREVIEW = "-preview"; + private static final String OPTION_NOBACKUP = "-nobackup"; + + /** Default zip detector */ + public static ZipDetector defaultDetector = new DefaultRaesZipDetector("amp|war"); + + /** File mapping properties */ + private Properties fileMappingProperties; + + /** Indicates the current verbose setting */ + private boolean verbose = false; + + /** + * Constructor + */ + public ModuleManagementTool() + { + // Load the default file mapping properties + this.fileMappingProperties = new Properties(); + InputStream is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_FILE_MAPPING_PROPERTIES); + try + { + this.fileMappingProperties.load(is); + } + catch (IOException exception) + { + throw new ModuleManagementToolException("Unable to load default extension file mapping properties.", exception); + } + } + + /** + * Indicates whether the management tool is currently in verbose reporting mode. + * + * @return true if verbose, false otherwise + */ + public boolean isVerbose() + { + return verbose; + } + + /** + * Sets the verbose setting for the mangement tool + * + * @param verbose true if verbose, false otherwise + */ + public void setVerbose(boolean verbose) + { + this.verbose = verbose; + } + + /** + * Installs a given AMP file into a given WAR file. + * + * @see ModuleManagementTool.installModule(String, String, boolean, boolean, boolean) + * + * @param ampFileLocation the location of the AMP file to be installed + * @param warFileLocation the location of the WAR file into which the AMP file is to be installed + */ + public void installModule(String ampFileLocation, String warFileLocation) + { + installModule(ampFileLocation, warFileLocation, false, false, true); + } + + /** + * Installs a given AMP file into a given WAR file. + * + * @param ampFileLocation the location of the AMP file to be installed + * @param warFileLocation the location of the WAR file into which the AMP file is to be installed. + * @param preview indicates whether this should be a preview install. This means that the process of + * installation will be followed and reported, but the WAR file will not be modified. + * @param forceInstall indicates whether the installed files will be replaces reguarless of the currently installed + * version of the AMP. Generally used during development of the AMP. + * @param backupWAR indicates whether we should backup the war we are modifying or not + */ + public void installModule(String ampFileLocation, String warFileLocation, boolean preview, boolean forceInstall, boolean backupWAR) + { + try + { + if (preview == false) + { + // Make sure the module and backup directory exisits in the WAR file + File moduleDir = new File(warFileLocation + MODULE_DIR, defaultDetector); + if (moduleDir.exists() == false) + { + moduleDir.mkdir(); + } + File backUpDir = new File(warFileLocation + BACKUP_DIR, defaultDetector); + if (backUpDir.exists() == false) + { + backUpDir.mkdir(); + } + + // Make a backup of the war we are oging to modify + if (backupWAR == true) + { + java.io.File warFile = new java.io.File(warFileLocation); + if (warFile.exists() == false) + { + throw new ModuleManagementToolException("The war file '" + warFileLocation + "' does not exist."); + } + String backupLocation = warFileLocation + "-" + System.currentTimeMillis() + ".bak"; + java.io.File backup = new java.io.File(backupLocation); + FileCopyUtils.copy(warFile, backup); + + outputMessage("WAR has been backed up to '" + backupLocation + "'"); + } + } + + // Get the details of the installing module + ModuleDetailsHelper installingModuleDetails = ModuleDetailsHelper.create(ampFileLocation + "/module.properties"); + if (installingModuleDetails.exists() == false) + { + throw new ModuleManagementToolException("No module.properties file has been found in the installing .amp file '" + ampFileLocation + "'"); + } + + // Get the detail of the installed module + ModuleDetailsHelper installedModuleDetails = ModuleDetailsHelper.create(warFileLocation, installingModuleDetails.getId()); + if (installedModuleDetails != null) + { + int compareValue = installedModuleDetails.getVersionNumber().compareTo(installingModuleDetails.getVersionNumber()); + if (forceInstall == true || compareValue == -1) + { + if (forceInstall == true) + { + // Warn of forced install + outputMessage("WARNING: The installation of this module is being forced. All files will be removed and replaced reguarless of exiting versions present."); + } + + // Trying to update the extension, old files need to cleaned before we proceed + outputMessage("Clearing out files relating to version '" + installedModuleDetails.getVersionNumber().toString() + "' of module '" + installedModuleDetails.getId() + "'"); + cleanWAR(warFileLocation, installedModuleDetails.getId(), preview); + } + else if (compareValue == 0) + { + // Trying to install the same extension version again + outputMessage("WARNING: This version of this module is already installed in the WAR"); + throw new ModuleManagementToolException("This version of this module is alreay installed. Use the 'force' parameter if you want to overwrite the current installation."); + } + else if (compareValue == 1) + { + // Trying to install an earlier version of the extension + outputMessage("WARNING: A later version of this module is already installed in the WAR"); + throw new ModuleManagementToolException("An earlier version of this module is already installed. You must first unistall the current version before installing this version of the module."); + } + + } + + // TODO check for any additional file mapping propeties supplied in the AEP file + + // Copy the files from the AEP file into the WAR file + outputMessage("Adding files relating to version '" + installingModuleDetails.getVersionNumber().toString() + "' of module '" + installingModuleDetails.getId() + "'"); + InstalledFiles installedFiles = new InstalledFiles(warFileLocation, installingModuleDetails.getId()); + for (Map.Entry entry : this.fileMappingProperties.entrySet()) + { + // Run throught the files one by one figuring out what we are going to do during the copy + copyToWar(ampFileLocation, warFileLocation, (String)entry.getKey(), (String)entry.getValue(), installedFiles, preview); + + if (preview == false) + { + // Get a reference to the source folder (if it isn't present dont do anything + File source = new File(ampFileLocation + "/" + entry.getKey(), defaultDetector); + if (source != null && source.list() != null) + { + // Get a reference to the destination folder + File destination = new File(warFileLocation + "/" + entry.getValue(), defaultDetector); + if (destination == null) + { + throw new ModuleManagementToolException("The destination folder '" + entry.getValue() + "' as specified in mapping properties does not exist in the war"); + } + // Do the bulk copy since this is quicker than copying file's one by one + destination.copyAllFrom(source); + } + } + } + + if (preview == false) + { + // Save the installed file list + installedFiles.save(); + + // Update the installed module details + installingModuleDetails.save(warFileLocation, installingModuleDetails.getId()); + + // Update the zip file's + File.update(); + } + } + catch (ZipWarningException ignore) + { + // Only instances of the class ZipWarningException exist in the chain of + // exceptions. We choose to ignore this. + } + catch (ZipControllerException exception) + { + // At least one exception occured which is not just a ZipWarningException. + // This is a severe situation that needs to be handled. + throw new ModuleManagementToolException("A Zip error was encountered during deployment of the AEP into the WAR", exception); + } + catch (IOException exception) + { + throw new ModuleManagementToolException("An IO error was encountered during deployment of the AEP into the WAR", exception); + } + } + + /** + * Cleans the WAR file of all files relating to the currently installed version of the the AMP. + * + * @param warFileLocatio the war file location + * @param moduleId the module id + * @param preview indicates whether this is a preview installation + */ + private void cleanWAR(String warFileLocation, String moduleId, boolean preview) + { + InstalledFiles installedFiles = new InstalledFiles(warFileLocation, moduleId); + installedFiles.load(); + + for (String add : installedFiles.getAdds()) + { + // Remove file + removeFile(warFileLocation, add, preview); + } + for (String mkdir : installedFiles.getMkdirs()) + { + // Remove folder + removeFile(warFileLocation, mkdir, preview); + } + for (Map.Entry update : installedFiles.getUpdates().entrySet()) + { + if (preview == false) + { + // Recover updated file and delete backups + File modified = new File(warFileLocation + update.getKey(), defaultDetector); + File backup = new File(warFileLocation + update.getValue(), defaultDetector); + modified.copyFrom(backup); + backup.delete(); + } + + outputMessage("Recovering file '" + update.getKey() + "' from backup '" + update.getValue() + "'", true); + } + } + + /** + * Removes a file from the given location in the war file. + * + * @param warLocation the war file location + * @param filePath the path to the file that is to be deleted + * @param preview indicates whether this is a preview install + */ + private void removeFile(String warLocation, String filePath, boolean preview) + { + File removeFile = new File(warLocation + filePath, defaultDetector); + if (removeFile.exists() == true) + { + outputMessage("Removing file '" + filePath + "' from war", true); + if (preview == false) + { + removeFile.delete(); + } + } + else + { + outputMessage("The file '" + filePath + "' was expected for removal but was not present in the war", true); + } + } + + /** + * Copies a file from the AMP location to the correct location in the WAR, interating on directories where appropraite. + * + * @param ampFileLocation the AMP file location + * @param warFileLocation the WAR file location + * @param sourceDir the directory in the AMP to copy from + * @param destinationDir the directory in the WAR to copy to + * @param installedFiles a list of the currently installed files + * @param preview indicates whether this is a preview install or not + * @throws IOException throws any IOExpceptions thar are raised + */ + private void copyToWar(String ampFileLocation, String warFileLocation, String sourceDir, String destinationDir, InstalledFiles installedFiles, boolean preview) + throws IOException + { + String sourceLocation = ampFileLocation + sourceDir; + File ampConfig = new File(sourceLocation, defaultDetector); + + java.io.File[] files = ampConfig.listFiles(); + if (files != null) + { + for (java.io.File sourceChild : files) + { + String destinationFileLocation = warFileLocation + destinationDir + "/" + sourceChild.getName(); + File destinationChild = new File(destinationFileLocation, defaultDetector); + if (sourceChild.isFile() == true) + { + String backupLocation = null; + boolean createFile = false; + if (destinationChild.exists() == false) + { + createFile = true; + } + else + { + // Backup file about to be updated + backupLocation = BACKUP_DIR + "/" + GUID.generate() + ".bin"; + if (preview == false) + { + File backupFile = new File(warFileLocation + backupLocation, defaultDetector); + backupFile.copyFrom(destinationChild); + } + } + + if (createFile == true) + { + installedFiles.addAdd(destinationDir + "/" + sourceChild.getName()); + this.outputMessage("File '" + destinationDir + "/" + sourceChild.getName() + "' added to war from amp", true); + } + else + { + installedFiles.addUpdate(destinationDir + "/" + sourceChild.getName(), backupLocation); + this.outputMessage("WARNING: The file '" + destinationDir + "/" + sourceChild.getName() + "' is being updated by this module and has been backed-up to '" + backupLocation + "'", true); + } + } + else + { + boolean mkdir = false; + if (destinationChild.exists() == false) + { + mkdir = true; + } + + copyToWar(ampFileLocation, warFileLocation, sourceDir + "/" + sourceChild.getName(), + destinationDir + "/" + sourceChild.getName(), installedFiles, preview); + if (mkdir == true) + { + installedFiles.addMkdir(destinationDir + "/" + sourceChild.getName()); + this.outputMessage("Directory '" + destinationDir + "/" + sourceChild.getName() + "' added to war", true); + } + } + } + } + } + + /** + * @throws UnsupportedOperationException + */ + public void disableModule(String moduleId, String warLocation) + { + throw new UnsupportedOperationException("Disable module is not currently supported"); + } + + /** + * @throws UnsupportedOperationException + */ + public void enableModule(String moduleId, String warLocation) + { + throw new UnsupportedOperationException("Enable module is not currently supported"); + } + + /** + * @throws UnsupportedOperationException + */ + public void uninstallModule(String moduleId, String warLocation) + { + throw new UnsupportedOperationException("Uninstall module is not currently supported"); + } + + /** + * Lists all the currently installed modules in the WAR + * + * @param warLocation the war location + */ + public void listModules(String warLocation) + { + ModuleDetailsHelper moduleDetails = null; + boolean previous = this.verbose; + this.verbose = true; + try + { + File moduleDir = new File(warLocation + MODULE_DIR, defaultDetector); + if (moduleDir.exists() == false) + { + outputMessage("No modules are installed in this WAR file"); + } + + java.io.File[] dirs = moduleDir.listFiles(); + if (dirs != null && dirs.length != 0) + { + for (java.io.File dir : dirs) + { + if (dir.isDirectory() == true) + { + File moduleProperties = new File(dir.getPath() + "/module.properties", defaultDetector); + if (moduleProperties.exists() == true) + { + try + { + moduleDetails = new ModuleDetailsHelper(new FileInputStream(moduleProperties)); + } + catch (FileNotFoundException exception) + { + throw new ModuleManagementToolException("Unable to open module properties file '" + moduleProperties.getPath() + "'"); + } + + outputMessage("Module '" + moduleDetails.getId() + "' installed in '" + warLocation + "'"); + outputMessage("Title: " + moduleDetails.getTitle(), true); + outputMessage("Version: " + moduleDetails.getVersionNumber(), true); + outputMessage("Install Date: " + moduleDetails.getInstalledDate(), true); + outputMessage("Desription: " + moduleDetails.getDescription(), true); + } + } + } + } + else + { + outputMessage("No modules are installed in this WAR file"); + } + } + finally + { + this.verbose = previous; + } + } + + /** + * Outputs a message the console (in verbose mode) and the logger. + * + * @param message the message to output + */ + private void outputMessage(String message) + { + outputMessage(message, false); + } + + /** + * Outputs a message the console (in verbose mode) and the logger. + * + * @param message the message to output + * @prarm indent indicates that the message should be formated with an indent + */ + private void outputMessage(String message, boolean indent) + { + if (indent == true) + { + message = " - " + message; + } + if (this.verbose == true) + { + System.out.println(message); + } + if (logger.isDebugEnabled() == true) + { + logger.debug(message); + } + } + + /** + * Main + * + * @param args command line interface arguments + */ + public static void main(String[] args) + { + if (args.length >= 1) + { + ModuleManagementTool manager = new ModuleManagementTool(); + + String operation = args[0]; + if (operation.equals(OP_INSTALL) == true && args.length >= 3) + { + String aepFileLocation = args[1]; + String warFileLocation = args[2]; + boolean forceInstall = false; + boolean previewInstall = false; + boolean backup = true; + + if (args.length > 3) + { + for (int i = 3; i < args.length; i++) + { + String option = args[i]; + if (OPTION_VERBOSE.equals(option) == true) + { + manager.setVerbose(true); + } + else if (OPTION_FORCE.equals(option) == true) + { + forceInstall = true; + } + else if (OPTION_PREVIEW.equals(option) == true) + { + previewInstall = true; + } + else if (OPTION_NOBACKUP.equals(option) == true) + { + backup = false; + } + } + } + + // Install the module + manager.installModule(aepFileLocation, warFileLocation, previewInstall, forceInstall, backup); + } + else if (OP_LIST.equals(operation) == true && args.length == 2) + { + // List the installed modules + String warFileLocation = args[1]; + manager.listModules(warFileLocation); + } + else + { + outputUsage(); + } + } + else + { + outputUsage(); + } + } + + /** + * Outputs the module management tool usage + */ + private static void outputUsage() + { + System.out.println("Module managment tool available commands:"); + System.out.println("-----------------------------------------------------------\n"); + System.out.println("install: Installs a AMP file into an Alfresco WAR file, updates if an older version is already installed."); + System.out.println("usage: install AMPFile WARFile options"); + System.out.println("valid options: "); + System.out.println(" -verbose : enable verbose output"); + System.out.println(" -force : forces installation of AMP regardless of currently installed module version"); + System.out.println(" -preview : previews installation of AMP without modifying WAR file"); + System.out.println(" -nobackup : indicates that no backup should be made of the WAR\n"); + System.out.println("-----------------------------------------------------------\n"); + System.out.println("list: Lists all the modules currently installed in an Alfresco WAR file."); + System.out.println("usage: list WARFile\n"); + System.out.println("-----------------------------------------------------------\n"); + } + + +} diff --git a/source/java/org/alfresco/repo/module/ModuleManagementToolException.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolException.java similarity index 93% rename from source/java/org/alfresco/repo/module/ModuleManagementToolException.java rename to source/java/org/alfresco/repo/module/tool/ModuleManagementToolException.java index 59edd6b7cb..47453a1020 100644 --- a/source/java/org/alfresco/repo/module/ModuleManagementToolException.java +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolException.java @@ -14,7 +14,7 @@ * language governing permissions and limitations under the * License. */ -package org.alfresco.repo.module; +package org.alfresco.repo.module.tool; import org.alfresco.error.AlfrescoRuntimeException; diff --git a/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java new file mode 100644 index 0000000000..0d904d1ac6 --- /dev/null +++ b/source/java/org/alfresco/repo/module/tool/ModuleManagementToolTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.module.tool; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.springframework.util.FileCopyUtils; + +import de.schlichtherle.io.DefaultRaesZipDetector; +import de.schlichtherle.io.FileInputStream; +import de.schlichtherle.io.FileOutputStream; +import de.schlichtherle.io.ZipDetector; + +/** + * Module management tool unit test + * + * @author Roy Wetherall + */ +public class ModuleManagementToolTest extends TestCase +{ + private ModuleManagementTool manager = new ModuleManagementTool(); + + ZipDetector defaultDetector = new DefaultRaesZipDetector("amp|war"); + + public void testBasicInstall() + throws Exception + { + manager.setVerbose(true); + + String warLocation = getFileLocation(".war", "module/test.war"); + String ampLocation = getFileLocation(".amp", "module/test.amp"); + String ampV2Location = getFileLocation(".amp", "module/test_v2.amp"); + + System.out.println(warLocation); + + // Initial install of module + this.manager.installModule(ampLocation, warLocation); + + // Check that the war has been modified correctly + List files = new ArrayList(10); + files.add("/WEB-INF/classes/alfresco/module/test/module.properties"); + files.add("/WEB-INF/classes/alfresco/module/test/modifications.install"); + files.add("/WEB-INF/lib/test.jar"); + files.add("/WEB-INF/classes/alfresco/module/test/module-context.xml"); + files.add("/WEB-INF/classes/alfresco/module/test"); + files.add("/WEB-INF/licenses/license.txt"); + files.add("/scripts/test.js"); + files.add("/images/test.jpg"); + files.add("/jsp/test.jsp"); + files.add("/css/test.css"); + checkForFileExistance(warLocation, files); + + // Check the intstalled files + InstalledFiles installed0 = new InstalledFiles(warLocation, "test"); + installed0.load(); + assertNotNull(installed0); + assertEquals(7, installed0.getAdds().size()); + assertEquals(1, installed0.getMkdirs().size()); + assertEquals(1, installed0.getUpdates().size()); + String backup = null; + String orig = null; + for (Map.Entry update : installed0.getUpdates().entrySet()) + { + checkContentsOfFile(warLocation + update.getKey(), "VERSIONONE"); + checkContentsOfFile(warLocation + update.getValue(), "ORIGIONAL"); + backup = update.getValue(); + orig = update.getKey(); + } + + // Try and install same version + try + { + this.manager.installModule(ampLocation, warLocation); + fail("The module is already installed so an exception should have been raised since we are not forcing an overwite"); + } + catch(ModuleManagementToolException exception) + { + // Pass + } + + // Install a later version + this.manager.installModule(ampV2Location, warLocation); + + // Check that the war has been modified correctly + List files2 = new ArrayList(12); + files.add("/WEB-INF/classes/alfresco/module/test/module.properties"); + files.add("/WEB-INF/classes/alfresco/module/test/modifications.install"); + files2.add("/WEB-INF/lib/test.jar"); + files2.add("/WEB-INF/classes/alfresco/module/test/module-context.xml"); + files2.add("/WEB-INF/classes/alfresco/module/test"); + files2.add("/WEB-INF/licenses/license.txt"); + files2.add("/scripts/test2.js"); + files2.add("/scripts/test3.js"); + files2.add("/images/test.jpg"); + files2.add("/css/test.css"); + files2.add("/WEB-INF/classes/alfresco/module/test/version2"); + files2.add("/WEB-INF/classes/alfresco/module/test/version2/version2-context.xml"); + checkForFileExistance(warLocation, files2); + + List files3 = new ArrayList(2); + files3.add("/scripts/test.js"); + files3.add("/jsp/test.jsp"); + files3.add(backup); + checkForFileNonExistance(warLocation, files3); + + // Check the intstalled files + InstalledFiles installed1 = new InstalledFiles(warLocation, "test"); + installed1.load(); + assertNotNull(installed1); + assertEquals(8, installed1.getAdds().size()); + assertEquals(1, installed1.getMkdirs().size()); + assertEquals(0, installed1.getUpdates().size()); + + // Ensure the file has been reverted as it isnt updated in the v2.0 + checkContentsOfFile(warLocation + orig, "ORIGIONAL"); + + // Try and install and earlier version + try + { + this.manager.installModule(ampLocation, warLocation); + fail("An earlier version of this module is already installed so an exception should have been raised since we are not forcing an overwite"); + } + catch(ModuleManagementToolException exception) + { + // Pass + } + } + + public void testPreviewInstall() + throws Exception + { + manager.setVerbose(true); + + String warLocation = getFileLocation(".war", "module/test.war"); + String ampLocation = getFileLocation(".amp", "module/test.amp"); + + System.out.println(warLocation); + + // Initial install of module + this.manager.installModule(ampLocation, warLocation, true, false, true); + + // TODO need to prove that the war file has not been updated in any way + } + + public void testForcedInstall() + throws Exception + { + manager.setVerbose(true); + + String warLocation = getFileLocation(".war", "module/test.war"); + String ampLocation = getFileLocation(".amp", "module/test.amp"); + + System.out.println(warLocation); + + // Initial install of module + this.manager.installModule(ampLocation, warLocation, false, false, false); + this.manager.installModule(ampLocation, warLocation, false, true, false); + } + + public void testList() + throws Exception + { + String warLocation = getFileLocation(".war", "module/test.war"); + String ampLocation = getFileLocation(".amp", "module/test.amp"); + + this.manager.listModules(warLocation); + + this.manager.installModule(ampLocation, warLocation); + + this.manager.listModules(warLocation); + } + + private String getFileLocation(String extension, String location) + throws IOException + { + File file = File.createTempFile("moduleManagementToolTest-", extension); + InputStream is = this.getClass().getClassLoader().getResourceAsStream(location); + OutputStream os = new FileOutputStream(file); + FileCopyUtils.copy(is, os); + return file.getPath(); + } + + private void checkForFileExistance(String warLocation, List files) + { + for (String file : files) + { + File file0 = new de.schlichtherle.io.File(warLocation + file, this.defaultDetector); + assertTrue("The file/dir " + file + " does not exist in the WAR.", file0.exists()); + } + } + + private void checkForFileNonExistance(String warLocation, List files) + { + for (String file : files) + { + File file0 = new de.schlichtherle.io.File(warLocation + file, this.defaultDetector); + assertFalse("The file/dir " + file + " does exist in the WAR.", file0.exists()); + } + } + + private void checkContentsOfFile(String location, String expectedContents) + throws IOException + { + File file = new de.schlichtherle.io.File(location, this.defaultDetector); + assertTrue(file.exists()); + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + String line = reader.readLine(); + assertNotNull(line); + assertEquals(expectedContents, line.trim()); + } +} diff --git a/source/java/org/alfresco/repo/module/default-file-mapping.properties b/source/java/org/alfresco/repo/module/tool/default-file-mapping.properties similarity index 100% rename from source/java/org/alfresco/repo/module/default-file-mapping.properties rename to source/java/org/alfresco/repo/module/tool/default-file-mapping.properties diff --git a/source/java/org/alfresco/service/cmr/module/ModuleDetails.java b/source/java/org/alfresco/service/cmr/module/ModuleDetails.java index 363c55c8b0..6afb0609e3 100644 --- a/source/java/org/alfresco/service/cmr/module/ModuleDetails.java +++ b/source/java/org/alfresco/service/cmr/module/ModuleDetails.java @@ -19,42 +19,52 @@ package org.alfresco.service.cmr.module; import org.alfresco.util.VersionNumber; /** - * Module details, contains the details of an installed alfresco - * module. + * Module details, contains the details of an installed alfresco module. * * @author Roy Wetherall + * @since 2.0 */ -public class ModuleDetails +public interface ModuleDetails { - private String id; - private VersionNumber version; - private String title; - private String description; + /** + * Indicates whether the details exists or not + * + * @return true if it exists, false otherwise + */ + boolean exists(); - public ModuleDetails(String id, VersionNumber version, String title, String description) - { - this.id = id; - this.version = version; - this.title = title; - } + /** + * Get the id of the module + * + * @return module id + */ + String getId(); - public String getId() - { - return id; - } + /** + * Get the version number of the module + * + * @return module version number + */ + VersionNumber getVersionNumber(); - public VersionNumber getVersion() - { - return version; - } + /** + * Get the title of the module + * + * @return module title + */ + String getTitle(); - public String getTitle() - { - return title; - } + /** + * Get the description of the module + * + * @return module description + */ + String getDescription(); - public String getDescription() - { - return description; - } + /** + * Get the modules install date + * + * @return module install date + */ + String getInstalledDate(); } diff --git a/source/java/org/alfresco/service/cmr/module/ModuleService.java b/source/java/org/alfresco/service/cmr/module/ModuleService.java index c7d3b177dc..153aa8f1c5 100644 --- a/source/java/org/alfresco/service/cmr/module/ModuleService.java +++ b/source/java/org/alfresco/service/cmr/module/ModuleService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2007 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -18,15 +18,45 @@ package org.alfresco.service.cmr.module; import java.util.List; +import org.alfresco.repo.module.ModuleComponent; + /** - * Module service. Provides information about the currently installed alfresco - * modules. + * A service to control and provide information about the currently-installed modules. * * @author Roy Wetherall + * @author Derek Hulley + * @since 2.0 */ public interface ModuleService { - public ModuleDetails getModule(String moduleId); + /** + * Gets the module details for a given module id. If the module does not exist or is not installed + * then null is returned. + * + * @param moduleId a module id + * @return the module details + */ + ModuleDetails getModule(String moduleId); - public List getAllModules(); + /** + * Gets a list of all the modules currently installed. + * + * @return module details of the currently installed modules. + */ + List getAllModules(); + + /** + * Register a component of a module for execution. + * + * @param component the module component. + */ + void registerComponent(ModuleComponent component); + + /** + * Start all the modules. For transaction purposes, each module should be + * regarded as a self-contained unit and started in its own transaction. + * Where inter-module dependencies exist, these will be pulled into the + * transaction. + */ + void startModules(); } diff --git a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java index 09e97e7466..664870247d 100644 --- a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java +++ b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverter.java @@ -42,6 +42,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.VersionNumber; /** * Support for generic conversion between types. @@ -278,6 +279,14 @@ public class DefaultTypeConverter } }); + INSTANCE.addConverter(String.class, VersionNumber.class, new TypeConverter.Converter() + { + public VersionNumber convert(String source) + { + return new VersionNumber(source); + } + }); + // // From Locale @@ -296,7 +305,20 @@ public class DefaultTypeConverter } }); + + // + // From VersionNumber + // + INSTANCE.addConverter(VersionNumber.class, String.class, new TypeConverter.Converter() + { + public String convert(VersionNumber source) + { + return source.toString(); + } + }); + + // // From MLText // diff --git a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverterTest.java b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverterTest.java index 4b3dbad33f..39a06c1bab 100644 --- a/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverterTest.java +++ b/source/java/org/alfresco/service/cmr/repository/datatype/DefaultTypeConverterTest.java @@ -28,6 +28,7 @@ import junit.framework.TestCase; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.VersionNumber; public class DefaultTypeConverterTest extends TestCase { @@ -98,6 +99,8 @@ public class DefaultTypeConverterTest extends TestCase assertEquals("woof", DefaultTypeConverter.INSTANCE.convert(String.class, mlText)); // Locale assertEquals("fr_FR_", DefaultTypeConverter.INSTANCE.convert(String.class, Locale.FRANCE)); + // VersionNumber + assertEquals("1.2.3", DefaultTypeConverter.INSTANCE.convert(String.class, new VersionNumber("1.2.3"))); } public void testFromString() @@ -131,6 +134,8 @@ public class DefaultTypeConverterTest extends TestCase assertEquals(Locale.FRANCE, DefaultTypeConverter.INSTANCE.convert(Locale.class, "fr_FR")); assertEquals(Locale.FRANCE, DefaultTypeConverter.INSTANCE.convert(Locale.class, "fr_FR_")); + + assertEquals(new VersionNumber("1.2.3"), DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, "1.2.3")); } public void testPrimativeAccessors() diff --git a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java index 250e673de8..d8a41562af 100644 --- a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java +++ b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java @@ -20,14 +20,12 @@ package org.alfresco.util; import junit.framework.TestCase; import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.transaction.TransactionService; import org.springframework.context.ApplicationContext; @@ -52,8 +50,8 @@ public abstract class BaseAlfrescoTestCase extends TestCase /** The content service */ protected ContentService contentService; - /** The authentication service */ - protected AuthenticationService authenticationService; + /** The authentication component */ + protected AuthenticationComponent authenticationComponent; /** The store reference */ protected StoreRef storeRef; @@ -73,14 +71,13 @@ public abstract class BaseAlfrescoTestCase extends TestCase // get the service register this.serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); //Get a reference to the node service - this.nodeService = (NodeService)ctx.getBean("NodeService"); - this.contentService = (ContentService)ctx.getBean("ContentService"); - this.authenticationService = (AuthenticationService)ctx.getBean("authenticationService"); + this.nodeService = serviceRegistry.getNodeService(); + this.contentService = serviceRegistry.getContentService(); + this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); this.actionService = (ActionService)ctx.getBean("actionService"); - this.transactionService = (TransactionService)ctx.getBean("transactionComponent"); + this.transactionService = serviceRegistry.getTransactionService(); // Authenticate as the system user - this must be done before we create the store - AuthenticationComponent authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); authenticationComponent.setSystemUserAsCurrentUser(); // Create the store and get the root node @@ -94,7 +91,15 @@ public abstract class BaseAlfrescoTestCase extends TestCase @Override protected void tearDown() throws Exception { - authenticationService.clearCurrentSecurityContext(); + try + { + authenticationComponent.clearCurrentSecurityContext(); + } + catch (Throwable e) + { + e.printStackTrace(); + // Don't let this mask any previous exceptions + } super.tearDown(); } diff --git a/source/test-resources/module/module-component-test-beans.xml b/source/test-resources/module/module-component-test-beans.xml new file mode 100644 index 0000000000..3e21801530 --- /dev/null +++ b/source/test-resources/module/module-component-test-beans.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + /cm:categoryRoot/cm:generalclassifiable + module/module-importer-test-categories.xml + + + + + diff --git a/source/test-resources/module/module-importer-test-categories.xml b/source/test-resources/module/module-importer-test-categories.xml new file mode 100644 index 0000000000..49f289fbcf --- /dev/null +++ b/source/test-resources/module/module-importer-test-categories.xml @@ -0,0 +1,26 @@ + + + + module.importer.test.categories + test:xyz-root + + + X + test:x + + + Y + test:y + + + Z + test:z + + + + + + \ No newline at end of file diff --git a/source/test-resources/module/test.amp b/source/test-resources/module/test.amp index d9915a2691..5865987b45 100644 Binary files a/source/test-resources/module/test.amp and b/source/test-resources/module/test.amp differ diff --git a/source/test-resources/module/test.war b/source/test-resources/module/test.war index 5549ecc3a5..efb5ef2718 100644 Binary files a/source/test-resources/module/test.war and b/source/test-resources/module/test.war differ diff --git a/source/test-resources/module/test_v2.amp b/source/test-resources/module/test_v2.amp new file mode 100644 index 0000000000..ea9a5fcbb9 Binary files /dev/null and b/source/test-resources/module/test_v2.amp differ