mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
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:
@@ -43,7 +43,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
public abstract class AbstractPatch implements Patch
|
||||
{
|
||||
/**
|
||||
* I18N message when properties nto set.
|
||||
* I18N message when properties not set.
|
||||
* <ul>
|
||||
* <li>{0} = property name</li>
|
||||
* <li>{1} = patch instance</li>
|
||||
|
108
source/java/org/alfresco/repo/admin/registry/RegistryKey.java
Normal file
108
source/java/org/alfresco/repo/admin/registry/RegistryKey.java
Normal 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.admin.registry;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Key for looking up registry metadata.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class RegistryKey implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1137822242292626854L;
|
||||
|
||||
static final String REGISTRY_1_0_URI = "http://www.alfresco.org/system/registry/1.0";
|
||||
|
||||
private String namespaceUri;
|
||||
private String[] path;
|
||||
private String property;
|
||||
|
||||
/**
|
||||
* Build a registry key from a given array of elements.
|
||||
*/
|
||||
private static String buildPathString(String... elements)
|
||||
{
|
||||
if (elements.length == 0)
|
||||
{
|
||||
return "/";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String element : elements)
|
||||
{
|
||||
if (element == null || element.length() == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Key elements may not be empty or null");
|
||||
}
|
||||
sb.append("/").append(element);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For path /a/b/c and property 'x', put in <pre>"a", "b", "c", "x"</pre>
|
||||
* The property can also be <tt>null</tt> as in <pre>"a", "b", "c", null</pre>
|
||||
*
|
||||
* @param namespaceUri the key namespace to use. If left <tt>null</tt> then the
|
||||
* {@link #REGISTRY_1_0_URI default} will be used.
|
||||
* @param key the path elements followed by the property name.
|
||||
*/
|
||||
public RegistryKey(String namespaceUri, String... key)
|
||||
{
|
||||
if (namespaceUri == null)
|
||||
{
|
||||
namespaceUri = REGISTRY_1_0_URI;
|
||||
}
|
||||
this.namespaceUri = namespaceUri;
|
||||
// The last value is the property
|
||||
int length = key.length;
|
||||
if (length == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("No value supplied for the RegistryKey property");
|
||||
}
|
||||
this.property = key[length - 1];
|
||||
this.path = new String[length - 1];
|
||||
System.arraycopy(key, 0, path, 0, length - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append("RegistryKey")
|
||||
.append("[ ").append(RegistryKey.buildPathString(path)).append("/").append(property)
|
||||
.append(" ]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getNamespaceUri()
|
||||
{
|
||||
return namespaceUri;
|
||||
}
|
||||
|
||||
public String[] getPath()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getProperty()
|
||||
{
|
||||
return property;
|
||||
}
|
||||
|
||||
}
|
@@ -29,16 +29,16 @@ public interface RegistryService
|
||||
/**
|
||||
* Assign a value to the registry key, which must be of the form <b>/a/b/c</b>.
|
||||
*
|
||||
* @param key the registry key path delimited with '/'.
|
||||
* @param key the registry key.
|
||||
* @param value any value that can be stored in the repository.
|
||||
*/
|
||||
void addValue(String key, Serializable value);
|
||||
void addValue(RegistryKey key, Serializable value);
|
||||
|
||||
/**
|
||||
* @param key the registry key path delimited with '/'.
|
||||
* @param key the registry key.
|
||||
* @return Returns the value stored in the key.
|
||||
*
|
||||
* @see #addValue(String, Serializable)
|
||||
*/
|
||||
Serializable getValue(String key);
|
||||
Serializable getValue(RegistryKey key);
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ package org.alfresco.repo.admin.registry;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
@@ -33,6 +32,7 @@ import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.alfresco.util.PropertyMap;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -154,65 +154,44 @@ public class RegistryServiceImpl implements RegistryService
|
||||
return registryRootNodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a pair representing the node path and the property name
|
||||
*/
|
||||
private Pair<String, String> splitKey(String key)
|
||||
{
|
||||
int index = key.lastIndexOf('/');
|
||||
Pair<String, String> result = null;
|
||||
if (index < 0) // It is just a property
|
||||
{
|
||||
result = new Pair<String, String>("/", key);
|
||||
}
|
||||
else
|
||||
{
|
||||
String propertyName = key.substring(index + 1, key.length());
|
||||
if (propertyName.length() == 0)
|
||||
{
|
||||
throw new IllegalArgumentException("The registry key is invalid: " + key);
|
||||
}
|
||||
result = new Pair<String, String>(key.substring(0, index), propertyName);
|
||||
}
|
||||
// done
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the node and property name represented by the key or <tt>null</tt>
|
||||
* if it doesn't exist and was not allowed to be created
|
||||
*/
|
||||
private Pair<NodeRef, QName> getPath(String key, boolean create)
|
||||
private Pair<NodeRef, QName> getPath(RegistryKey key, boolean create)
|
||||
{
|
||||
// Get the root
|
||||
NodeRef currentNodeRef = getRegistryRootNodeRef();
|
||||
// Split the key
|
||||
Pair<String, String> keyPair = splitKey(key);
|
||||
// Parse the key
|
||||
StringTokenizer tokenizer = new StringTokenizer(keyPair.getFirst(), "/");
|
||||
// Get the key and property
|
||||
String namespaceUri = key.getNamespaceUri();
|
||||
String[] pathElements = key.getPath();
|
||||
String property = key.getProperty();
|
||||
// Find the node and property to put the value
|
||||
while (tokenizer.hasMoreTokens())
|
||||
for (String pathElement : pathElements)
|
||||
{
|
||||
String token = tokenizer.nextToken();
|
||||
String name = QName.createValidLocalName(token);
|
||||
QName qname = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, name);
|
||||
QName assocQName = QName.createQName(
|
||||
namespaceUri,
|
||||
QName.createValidLocalName(pathElement));
|
||||
|
||||
// Find the node
|
||||
List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(
|
||||
currentNodeRef,
|
||||
ContentModel.ASSOC_CHILDREN,
|
||||
qname);
|
||||
assocQName);
|
||||
int size = childAssocRefs.size();
|
||||
if (size == 0) // Found nothing with that path
|
||||
{
|
||||
if (create) // Must create the path
|
||||
{
|
||||
// Create the node
|
||||
// Create the node (with a name)
|
||||
PropertyMap properties = new PropertyMap();
|
||||
properties.put(ContentModel.PROP_NAME, pathElement);
|
||||
currentNodeRef = nodeService.createNode(
|
||||
currentNodeRef,
|
||||
ContentModel.ASSOC_CHILDREN,
|
||||
qname,
|
||||
ContentModel.TYPE_CONTAINER).getChildRef();
|
||||
assocQName,
|
||||
ContentModel.TYPE_CONTAINER,
|
||||
properties).getChildRef();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -244,15 +223,15 @@ public class RegistryServiceImpl implements RegistryService
|
||||
}
|
||||
// Create the result
|
||||
QName propertyQName = QName.createQName(
|
||||
NamespaceService.SYSTEM_MODEL_1_0_URI,
|
||||
QName.createValidLocalName(keyPair.getSecond()));
|
||||
namespaceUri,
|
||||
QName.createValidLocalName(property));
|
||||
Pair<NodeRef, QName> resultPair = new Pair<NodeRef, QName>(currentNodeRef, propertyQName);
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Converted registry key: \n" +
|
||||
" key pair: " + keyPair + "\n" +
|
||||
" result: " + resultPair);
|
||||
" Key: " + key + "\n" +
|
||||
" Result: " + resultPair);
|
||||
}
|
||||
if (resultPair.getFirst() == null)
|
||||
{
|
||||
@@ -267,7 +246,7 @@ public class RegistryServiceImpl implements RegistryService
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public void addValue(String key, Serializable value)
|
||||
public void addValue(RegistryKey key, Serializable value)
|
||||
{
|
||||
// Get the path, with creation support
|
||||
Pair<NodeRef, QName> keyPair = getPath(key, true);
|
||||
@@ -282,7 +261,7 @@ public class RegistryServiceImpl implements RegistryService
|
||||
}
|
||||
}
|
||||
|
||||
public Serializable getValue(String key)
|
||||
public Serializable getValue(RegistryKey key)
|
||||
{
|
||||
// Get the path, without creating
|
||||
Pair<NodeRef, QName> keyPair = getPath(key, false);
|
||||
|
@@ -40,7 +40,7 @@ public class RegistryServiceImplTest extends TestCase
|
||||
authenticationComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent");
|
||||
registryService = (RegistryService) ctx.getBean("RegistryService");
|
||||
|
||||
// Run as admin
|
||||
// Run as system user
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
}
|
||||
|
||||
@@ -65,13 +65,16 @@ public class RegistryServiceImplTest extends TestCase
|
||||
private static final Long VALUE_ONE = 1L;
|
||||
private static final Long VALUE_TWO = 2L;
|
||||
private static final Long VALUE_THREE = 3L;
|
||||
private static final String KEY_A_B_C_1 = "/a/b/c/1";
|
||||
private static final String KEY_A_B_C_2 = "/a/b/c/2";
|
||||
private static final String KEY_A_B_C_3 = "/a/b/c/3";
|
||||
private static final String KEY_A_B_C_D_1 = "/a/b/c/d/1";
|
||||
private static final String KEY_A_B_C_D_2 = "/a/b/c/d/2";
|
||||
private static final String KEY_A_B_C_D_3 = "/a/b/c/d/3";
|
||||
private static final String KEY_SPECIAL = "/me & you/ whatever";
|
||||
private static final RegistryKey KEY_A_B_C_0 = new RegistryKey(null, "a", "b", "c", "0");
|
||||
private static final RegistryKey KEY_A_B_C_1 = new RegistryKey(null, "a", "b", "c", "1");
|
||||
private static final RegistryKey KEY_A_B_C_2 = new RegistryKey(null, "a", "b", "c", "2");
|
||||
private static final RegistryKey KEY_A_B_C_3 = new RegistryKey(null, "a", "b", "c", "3");
|
||||
private static final RegistryKey KEY_A_B_C_D_0 = new RegistryKey(null, "a", "b", "c", "d", "0");
|
||||
private static final RegistryKey KEY_A_B_C_D_1 = new RegistryKey(null, "a", "b", "c", "d", "1");
|
||||
private static final RegistryKey KEY_A_B_C_D_2 = new RegistryKey(null, "a", "b", "c", "d", "2");
|
||||
private static final RegistryKey KEY_A_B_C_D_3 = new RegistryKey(null, "a", "b", "c", "d", "3");
|
||||
private static final RegistryKey KEY_X_Y_Z_0 = new RegistryKey(null, "x", "y", "z", "0");
|
||||
private static final RegistryKey KEY_SPECIAL = new RegistryKey(null, "me & you", "whatever");
|
||||
/**
|
||||
* General writing and reading back.
|
||||
*/
|
||||
@@ -91,9 +94,9 @@ public class RegistryServiceImplTest extends TestCase
|
||||
assertEquals("Incorrect value from service registry", VALUE_TWO, registryService.getValue(KEY_A_B_C_D_2));
|
||||
assertEquals("Incorrect value from service registry", VALUE_THREE, registryService.getValue(KEY_A_B_C_D_3));
|
||||
|
||||
assertNull("Missing key should return null value", registryService.getValue("/a/b/c/0"));
|
||||
assertNull("Missing key should return null value", registryService.getValue("/a/b/c/d/0"));
|
||||
assertNull("Missing key should return null value", registryService.getValue("/x/y/z/0"));
|
||||
assertNull("Missing key should return null value", registryService.getValue(KEY_A_B_C_0));
|
||||
assertNull("Missing key should return null value", registryService.getValue(KEY_A_B_C_D_0));
|
||||
assertNull("Missing key should return null value", registryService.getValue(KEY_X_Y_Z_0));
|
||||
}
|
||||
|
||||
public void testSpecialCharacters()
|
||||
|
@@ -35,6 +35,7 @@ import org.alfresco.service.cmr.repository.Path;
|
||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.alfresco.util.VersionNumber;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -256,6 +257,20 @@ public class PropertyValue implements Cloneable, Serializable
|
||||
{
|
||||
return DefaultTypeConverter.INSTANCE.convert(Locale.class, value);
|
||||
}
|
||||
},
|
||||
VERSION_NUMBER
|
||||
{
|
||||
@Override
|
||||
protected ValueType getPersistedType(Serializable value)
|
||||
{
|
||||
return ValueType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
Serializable convert(Serializable value)
|
||||
{
|
||||
return DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -362,6 +377,10 @@ public class PropertyValue implements Cloneable, Serializable
|
||||
{
|
||||
return ValueType.LOCALE;
|
||||
}
|
||||
else if (value instanceof VersionNumber)
|
||||
{
|
||||
return ValueType.VERSION_NUMBER;
|
||||
}
|
||||
else
|
||||
{
|
||||
// type is not recognised as belonging to any particular slot
|
||||
|
@@ -30,10 +30,11 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import net.sf.acegisecurity.Authentication;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.i18n.I18NUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
@@ -249,21 +250,8 @@ public class ImporterBootstrap extends AbstractLifecycleBean
|
||||
public void setLocale(String locale)
|
||||
{
|
||||
// construct locale
|
||||
StringTokenizer t = new StringTokenizer(locale, "_");
|
||||
int tokens = t.countTokens();
|
||||
if (tokens == 1)
|
||||
{
|
||||
this.locale = new Locale(locale);
|
||||
}
|
||||
else if (tokens == 2)
|
||||
{
|
||||
this.locale = new Locale(t.nextToken(), t.nextToken());
|
||||
}
|
||||
else if (tokens == 3)
|
||||
{
|
||||
this.locale = new Locale(t.nextToken(), t.nextToken(), t.nextToken());
|
||||
}
|
||||
|
||||
this.locale = I18NUtil.parseLocale(locale);
|
||||
|
||||
// store original
|
||||
strLocale = locale;
|
||||
}
|
||||
@@ -333,6 +321,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean
|
||||
}
|
||||
|
||||
UserTransaction userTransaction = transactionService.getUserTransaction();
|
||||
Authentication authentication = authenticationComponent.getCurrentAuthentication();
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
try
|
||||
@@ -448,7 +437,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean
|
||||
}
|
||||
finally
|
||||
{
|
||||
try {authenticationComponent.clearCurrentSecurityContext(); } catch (Throwable ex) {}
|
||||
try {authenticationComponent.setCurrentAuthentication(authentication); } catch (Throwable ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
142
source/java/org/alfresco/repo/module/ComponentsTest.java
Normal file
142
source/java/org/alfresco/repo/module/ComponentsTest.java
Normal 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
93
source/java/org/alfresco/repo/module/ModuleComponent.java
Normal file
93
source/java/org/alfresco/repo/module/ModuleComponent.java
Normal 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();
|
||||
}
|
342
source/java/org/alfresco/repo/module/ModuleComponentHelper.java
Normal file
342
source/java/org/alfresco/repo/module/ModuleComponentHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
141
source/java/org/alfresco/repo/module/ModuleDetailsImpl.java
Normal file
141
source/java/org/alfresco/repo/module/ModuleDetailsImpl.java
Normal 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();
|
||||
}
|
||||
}
|
@@ -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 ...");
|
||||
}
|
||||
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
192
source/java/org/alfresco/repo/module/ModuleServiceImpl.java
Normal file
192
source/java/org/alfresco/repo/module/ModuleServiceImpl.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
54
source/java/org/alfresco/repo/module/ModuleStarter.java
Normal file
54
source/java/org/alfresco/repo/module/ModuleStarter.java
Normal 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
|
||||
}
|
||||
}
|
217
source/java/org/alfresco/repo/module/tool/InstalledFiles.java
Normal file
217
source/java/org/alfresco/repo/module/tool/InstalledFiles.java
Normal 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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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;
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -19,42 +19,52 @@ package org.alfresco.service.cmr.module;
|
||||
import org.alfresco.util.VersionNumber;
|
||||
|
||||
/**
|
||||
* Module details, contains the details of an installed alfresco
|
||||
* module.
|
||||
* Module details, contains the details of an installed alfresco module.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ModuleDetails
|
||||
public interface ModuleDetails
|
||||
{
|
||||
private String id;
|
||||
private VersionNumber version;
|
||||
private String title;
|
||||
private String description;
|
||||
/**
|
||||
* Indicates whether the details exists or not
|
||||
*
|
||||
* @return true if it exists, false otherwise
|
||||
*/
|
||||
boolean exists();
|
||||
|
||||
public ModuleDetails(String id, VersionNumber version, String title, String description)
|
||||
{
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.title = title;
|
||||
}
|
||||
/**
|
||||
* Get the id of the module
|
||||
*
|
||||
* @return module id
|
||||
*/
|
||||
String getId();
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
/**
|
||||
* Get the version number of the module
|
||||
*
|
||||
* @return module version number
|
||||
*/
|
||||
VersionNumber getVersionNumber();
|
||||
|
||||
public VersionNumber getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
/**
|
||||
* Get the title of the module
|
||||
*
|
||||
* @return module title
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
public String getTitle()
|
||||
{
|
||||
return title;
|
||||
}
|
||||
/**
|
||||
* Get the description of the module
|
||||
*
|
||||
* @return module description
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
/**
|
||||
* Get the modules install date
|
||||
*
|
||||
* @return module install date
|
||||
*/
|
||||
String getInstalledDate();
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
* Copyright (C) 2007 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
@@ -18,15 +18,45 @@ package org.alfresco.service.cmr.module;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.module.ModuleComponent;
|
||||
|
||||
/**
|
||||
* Module service. Provides information about the currently installed alfresco
|
||||
* modules.
|
||||
* A service to control and provide information about the currently-installed modules.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
* @author Derek Hulley
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ModuleService
|
||||
{
|
||||
public ModuleDetails getModule(String moduleId);
|
||||
/**
|
||||
* Gets the module details for a given module id. If the module does not exist or is not installed
|
||||
* then null is returned.
|
||||
*
|
||||
* @param moduleId a module id
|
||||
* @return the module details
|
||||
*/
|
||||
ModuleDetails getModule(String moduleId);
|
||||
|
||||
public List<ModuleDetails> getAllModules();
|
||||
/**
|
||||
* Gets a list of all the modules currently installed.
|
||||
*
|
||||
* @return module details of the currently installed modules.
|
||||
*/
|
||||
List<ModuleDetails> getAllModules();
|
||||
|
||||
/**
|
||||
* Register a component of a module for execution.
|
||||
*
|
||||
* @param component the module component.
|
||||
*/
|
||||
void registerComponent(ModuleComponent component);
|
||||
|
||||
/**
|
||||
* Start all the modules. For transaction purposes, each module should be
|
||||
* regarded as a self-contained unit and started in its own transaction.
|
||||
* Where inter-module dependencies exist, these will be pulled into the
|
||||
* transaction.
|
||||
*/
|
||||
void startModules();
|
||||
}
|
||||
|
@@ -42,6 +42,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.Path;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.util.ISO8601DateFormat;
|
||||
import org.alfresco.util.VersionNumber;
|
||||
|
||||
/**
|
||||
* Support for generic conversion between types.
|
||||
@@ -278,6 +279,14 @@ public class DefaultTypeConverter
|
||||
}
|
||||
});
|
||||
|
||||
INSTANCE.addConverter(String.class, VersionNumber.class, new TypeConverter.Converter<String, VersionNumber>()
|
||||
{
|
||||
public VersionNumber convert(String source)
|
||||
{
|
||||
return new VersionNumber(source);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// From Locale
|
||||
@@ -296,7 +305,20 @@ public class DefaultTypeConverter
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// From VersionNumber
|
||||
//
|
||||
|
||||
INSTANCE.addConverter(VersionNumber.class, String.class, new TypeConverter.Converter<VersionNumber, String>()
|
||||
{
|
||||
public String convert(VersionNumber source)
|
||||
{
|
||||
return source.toString();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// From MLText
|
||||
//
|
||||
|
@@ -28,6 +28,7 @@ import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.service.cmr.repository.MLText;
|
||||
import org.alfresco.util.ISO8601DateFormat;
|
||||
import org.alfresco.util.VersionNumber;
|
||||
|
||||
public class DefaultTypeConverterTest extends TestCase
|
||||
{
|
||||
@@ -98,6 +99,8 @@ public class DefaultTypeConverterTest extends TestCase
|
||||
assertEquals("woof", DefaultTypeConverter.INSTANCE.convert(String.class, mlText));
|
||||
// Locale
|
||||
assertEquals("fr_FR_", DefaultTypeConverter.INSTANCE.convert(String.class, Locale.FRANCE));
|
||||
// VersionNumber
|
||||
assertEquals("1.2.3", DefaultTypeConverter.INSTANCE.convert(String.class, new VersionNumber("1.2.3")));
|
||||
}
|
||||
|
||||
public void testFromString()
|
||||
@@ -131,6 +134,8 @@ public class DefaultTypeConverterTest extends TestCase
|
||||
|
||||
assertEquals(Locale.FRANCE, DefaultTypeConverter.INSTANCE.convert(Locale.class, "fr_FR"));
|
||||
assertEquals(Locale.FRANCE, DefaultTypeConverter.INSTANCE.convert(Locale.class, "fr_FR_"));
|
||||
|
||||
assertEquals(new VersionNumber("1.2.3"), DefaultTypeConverter.INSTANCE.convert(VersionNumber.class, "1.2.3"));
|
||||
}
|
||||
|
||||
public void testPrimativeAccessors()
|
||||
|
@@ -20,14 +20,12 @@ package org.alfresco.util;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.action.ActionService;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.security.AuthenticationService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
@@ -52,8 +50,8 @@ public abstract class BaseAlfrescoTestCase extends TestCase
|
||||
/** The content service */
|
||||
protected ContentService contentService;
|
||||
|
||||
/** The authentication service */
|
||||
protected AuthenticationService authenticationService;
|
||||
/** The authentication component */
|
||||
protected AuthenticationComponent authenticationComponent;
|
||||
|
||||
/** The store reference */
|
||||
protected StoreRef storeRef;
|
||||
@@ -73,14 +71,13 @@ public abstract class BaseAlfrescoTestCase extends TestCase
|
||||
// get the service register
|
||||
this.serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||
//Get a reference to the node service
|
||||
this.nodeService = (NodeService)ctx.getBean("NodeService");
|
||||
this.contentService = (ContentService)ctx.getBean("ContentService");
|
||||
this.authenticationService = (AuthenticationService)ctx.getBean("authenticationService");
|
||||
this.nodeService = serviceRegistry.getNodeService();
|
||||
this.contentService = serviceRegistry.getContentService();
|
||||
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
this.actionService = (ActionService)ctx.getBean("actionService");
|
||||
this.transactionService = (TransactionService)ctx.getBean("transactionComponent");
|
||||
this.transactionService = serviceRegistry.getTransactionService();
|
||||
|
||||
// Authenticate as the system user - this must be done before we create the store
|
||||
AuthenticationComponent authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
authenticationComponent.setSystemUserAsCurrentUser();
|
||||
|
||||
// Create the store and get the root node
|
||||
@@ -94,7 +91,15 @@ public abstract class BaseAlfrescoTestCase extends TestCase
|
||||
@Override
|
||||
protected void tearDown() throws Exception
|
||||
{
|
||||
authenticationService.clearCurrentSecurityContext();
|
||||
try
|
||||
{
|
||||
authenticationComponent.clearCurrentSecurityContext();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// Don't let this mask any previous exceptions
|
||||
}
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
|
36
source/test-resources/module/module-component-test-beans.xml
Normal file
36
source/test-resources/module/module-component-test-beans.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||
|
||||
<!--
|
||||
Context for testing module components.
|
||||
This is a primary context that pulls in all the others required,
|
||||
including the Alfresco context.
|
||||
-->
|
||||
<beans >
|
||||
|
||||
<!-- Import the Alfresco nitty gritty -->
|
||||
<import resource="classpath:alfresco/application-context.xml" />
|
||||
|
||||
<!--
|
||||
Define the module components that need testing.
|
||||
This is pretty much how they will appear in individual module contexts, anyway
|
||||
-->
|
||||
<bean id="module.test.importerComponent" class="org.alfresco.repo.module.ImporterModuleComponent" parent="module.baseComponent">
|
||||
<!-- General properties for component management -->
|
||||
<property name="moduleId" value="module.test" />
|
||||
<property name="name" value="importerComponent" />
|
||||
<property name="description" value="A sample data importer" />
|
||||
<property name="sinceVersion" value="2.0" />
|
||||
<property name="appliesFromVersion" value="1.0" />
|
||||
<property name="appliesToVersion" value="1.4" />
|
||||
<!-- Implementation-specific properties for execution -->
|
||||
<property name="importer" ref="spacesBootstrap" />
|
||||
<property name="bootstrapView">
|
||||
<props>
|
||||
<prop key="path">/cm:categoryRoot/cm:generalclassifiable</prop>
|
||||
<prop key="location">module/module-importer-test-categories.xml</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
@@ -0,0 +1,26 @@
|
||||
<view:view xmlns:view="http://www.alfresco.org/view/repository/1.0"
|
||||
xmlns:sys="http://www.alfresco.org/model/system/1.0"
|
||||
xmlns:cm="http://www.alfresco.org/model/content/1.0"
|
||||
xmlns:test="http://www.alfresco.org/test/1.0">
|
||||
|
||||
<cm:category>
|
||||
<cm:name>module.importer.test.categories</cm:name>
|
||||
<sys:node-uuid>test:xyz-root</sys:node-uuid>
|
||||
<cm:subcategories>
|
||||
<cm:category>
|
||||
<cm:name>X</cm:name>
|
||||
<sys:node-uuid>test:x</sys:node-uuid>
|
||||
</cm:category>
|
||||
<cm:category>
|
||||
<cm:name>Y</cm:name>
|
||||
<sys:node-uuid>test:y</sys:node-uuid>
|
||||
</cm:category>
|
||||
<cm:category>
|
||||
<cm:name>Z</cm:name>
|
||||
<sys:node-uuid>test:z</sys:node-uuid>
|
||||
</cm:category>
|
||||
</cm:subcategories>
|
||||
</cm:category>
|
||||
|
||||
</view:view>
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
source/test-resources/module/test_v2.amp
Normal file
BIN
source/test-resources/module/test_v2.amp
Normal file
Binary file not shown.
Reference in New Issue
Block a user