Merged DEV\EXTENSIONS to HEAD

svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4868 svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4869 .
   svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4904 svn://svn.alfresco.com:3691/alfresco/BRANCHES/DEV/EXTENSIONS@4938 .
   Module management support
   Modularization of Records Management


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4956 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-01-29 14:43:37 +00:00
parent 0e3da160a2
commit f047c6baaf
42 changed files with 3324 additions and 664 deletions

View File

@@ -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<ModuleComponent> dependsOn;
/** Defaults to <tt>true</tt> */
private boolean executeOnceOnly;
private boolean executed;
public AbstractModuleComponent()
{
sinceVersion = VersionNumber.VERSION_ZERO;
appliesFromVersion = VersionNumber.VERSION_ZERO;
appliesToVersion = VersionNumber.VERSION_BIG;
dependsOn = new ArrayList<ModuleComponent>(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 <b>0.0</b>.
*/
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 <b>999.0</b>.
*/
public void setAppliesToVersion(String version)
{
this.appliesToVersion = new VersionNumber(version);
}
/**
* @inheritDoc
*/
public List<ModuleComponent> getDependsOn()
{
return dependsOn;
}
/**
* @param dependsOn a list of modules that must be executed before this one
*/
public void setDependsOn(List<ModuleComponent> dependsOn)
{
this.dependsOn = dependsOn;
}
/**
* @inheritDoc
*
* @return Returns <tt>true</tt> always. Override as required.
*/
public boolean isExecuteOnceOnly()
{
return executeOnceOnly;
}
/**
* @param executeOnceOnly <tt>true</tt> to force execution of this component with
* each startup or <tt>false</tt> 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;
}
}
}

View File

@@ -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<ChildAssociationRef> 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);
}
}

View File

@@ -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<Properties> 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.<br/>
* 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.<br/>
* This is an alternative to {@link #setBootstrapView(Properties)}.
*
* @param bootstrapViews the bootstrap data locations
*
* @see ImporterBootstrap#setBootstrapViews(List)
*/
public void setBootstrapViews(List<Properties> 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<Properties> views = new ArrayList<Properties>(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
}
}

View File

@@ -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.
* <p/>
* 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.
* <p/>
* 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 <b>must</b> 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<ModuleComponent> getDependsOn();
/**
* @return Returns <tt>true</tt> if the component is to be successfully executed exactly once,
* or <tt>false</tt> 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.
* <p/>
* All failures should just be thrown out as runtime exceptions and will be dealt with by
* the associated module infrastructure.
*/
void execute();
}

View File

@@ -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<String, Map<String, ModuleComponent>> componentsByNameByModule;
/** Default constructor */
public ModuleComponentHelper()
{
componentsByNameByModule = new HashMap<String, Map<String, ModuleComponent>>(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<String, ModuleComponent> componentsByName = componentsByNameByModule.get(moduleId);
if (componentsByName == null)
{
componentsByName = new HashMap<String, ModuleComponent>(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 <tt>null</tt>.
*/
private synchronized Map<String, ModuleComponent> getComponents(String moduleId)
{
Map<String, ModuleComponent> 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<ModuleDetails> modules = moduleService.getAllModules();
loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size()));
// Process each module in turn. Ordering is not important.
final Set<ModuleComponent> executedComponents = new HashSet<ModuleComponent>(10);
for (final ModuleDetails module : modules)
{
TransactionWork<Object> startModuleWork = new TransactionWork<Object>()
{
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<ModuleComponent> 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<String, ModuleComponent> 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<ModuleComponent> 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<ModuleComponent> 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);
}
}
}

View File

@@ -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
* <p/>
* This test creates a bunch of dummy components and then simulates
* startups with different module current versions.
* <p/>
* 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<VersionNumber, Integer> EXECUTION_COUNT_BY_VERSION;
static
{
EXECUTION_COUNT_BY_VERSION = new HashMap<VersionNumber, Integer>(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.<ModuleComponent>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<ModuleDetails> getAllModules()
{
// Reset the execution count
executed = 0;
// Create some module details
List<ModuleDetails> details = new ArrayList<ModuleDetails>(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
{
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<String, String> modifications = new HashMap<String, String>(50);
for (Map.Entry<Object, Object> 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<String, String> modifications = readModificationsFromFile(warFileLocation + "/" + getModuleModificationFileName(installedExtensionProperties.getProperty(PROP_ID)));
for (Map.Entry<String, String> 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<String, String> copyToWar(String aepFileLocation, String warFileLocation, String sourceDir, String destinationDir)
throws IOException
{
Map<String, String> result = new HashMap<String, String>(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<String, String> 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<String, String> 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<String, String> mod : modifications.entrySet())
{
String output = mod.getValue() + DELIMITER + mod.getKey() + "\n";
os.write(output.getBytes());
}
}
finally
{
os.close();
}
}
private Map<String, String> readModificationsFromFile(String fileLocation)
{
Map<String, String> modifications = new HashMap<String, String>(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 ...");
}
}

View File

@@ -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<String> files = new ArrayList<String>(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<String> 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());
}
}
}

View File

@@ -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}.
* <p/>
* 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 <i>only</i>. The version numbering is not to be used
* for ordering purposes.
* <p/>
* 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<String, ModuleDetails> 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<ModuleDetails> getAllModules()
{
cacheModuleDetails();
Collection<ModuleDetails> moduleDetails = moduleDetailsById.values();
// Make a copy to avoid modification of cached data by clients (and to satisfy API)
List<ModuleDetails> result = new ArrayList<ModuleDetails>(moduleDetails);
// Done
return result;
}
/**
* Ensure that the {@link #moduleDetailsById module details} are populated.
* <p/>
* 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<String, ModuleDetails>(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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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<String> adds = new ArrayList<String>();
private Map<String, String> updates = new HashMap<String, String>();
private List<String> mkdirs = new ArrayList<String>();
/**
* 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<String, String> 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<String> 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<String, String> getUpdates()
{
return updates;
}
/**
* Gets a list of the dirs added during install
*
* @return list of directories added
*/
public List<String> 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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<Object, Object> 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<String, String> 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");
}
}

View File

@@ -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;

View File

@@ -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<String> files = new ArrayList<String>(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<String, String> 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<String> files2 = new ArrayList<String>(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<String> files3 = new ArrayList<String>(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<String> 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<String> 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());
}
}