</states>
</lifecycle>
</extension>
+
+ <!-- The definition of the CollectionSpace SAS lifecycle name "cs_sas" for Shared authority resources -->
+ <!--
+ We have four states: "project", "locked", "deleted", and "locked_deleted".
+
+ We can move from Project <-> (Locked, Delete) <-> LockedAndDeleted
+
+ Project <-> (Locked, Delete) uses these transitions: lock, unlock, delete, undelete
+ LockedAndDeleted <-> (Locked, Deleted) uses these transitions: lock_deleted, unlock_deleted, delete_locked, undelete_locked
+ -->
+
+ <extension target="org.nuxeo.ecm.core.lifecycle.LifeCycleService"
+ point="lifecycle">
+ <documentation>CollectionSpace "SAS" life cycle definition.</documentation>
+ <lifecycle name="cs_sas" defaultInitial="project">
+ <transitions>
+ <!-- Transitions TO the "project" state -->
+ <transition name="unlock" destinationState="project">
+ <description>Unlock the document back to project state.</description>
+ </transition>
+ <transition name="undelete" destinationState="project">
+ <description>Undelete the document to the project state.</description>
+ </transition>
+
+ <!-- Transitions FROM "project" state TO the "locked" and the "deleted" states -->
+ <transition name="lock" destinationState="locked">
+ <description>Lock a document from the project state</description>
+ </transition>
+ <transition name="delete" destinationState="deleted">
+ <description>Soft-delete the document from the project state</description>
+ </transition>
+
+ <!-- Transitions TO "locked_deleted" state -->
+ <transition name="delete_locked" destinationState="locked_deleted">
+ <description>Delete the locked document from the "locked" state</description>
+ </transition>
+ <transition name="lock_deleted" destinationState="locked_deleted">
+ <description>Lock the deleted document from the "deleted" state.</description>
+ </transition>
+
+ <!-- Transitions FROM "locked_deleted" state -->
+ <transition name="undelete_locked" destinationState="locked">
+ <description>Undelete the locked document from locked_deleted state</description>
+ </transition>
+ <transition name="unlock_deleted" destinationState="deleted">
+ <description>Unlock the deleted document.</description>
+ </transition>
+
+ </transitions>
+
+ <states>
+ <state name="project" description="Default state" initial="true">
+ <transitions>
+ <transition>delete</transition> <!-- To "deleted" state -->
+ <transition>lock</transition> <!-- To "locked" state -->
+ </transitions>
+ </state>
+ <state name="locked" description="Locked state">
+ <transition>unlock</transition> <!-- To "project" state -->
+ <transition>delete_locked</transition> <!-- To "locked_deleted" state -->
+ </state>
+ <state name="deleted" description="Document is deleted">
+ <transitions>
+ <transition>undelete</transition> <!-- To "project" state -->
+ <transition>lock_deleted</transition> <!-- To "locked_deleted" state -->
+ </transitions>
+ </state>
+ <state name="locked_deleted" description="Document is locked and deleted">
+ <transitions>
+ <transition>unlock_deleted</transition> <!-- To "deleted" state -->
+ <transition>undelete_locked</transition><!-- To "locked" state -->
+ </transitions>
+ </state>
+ </states>
+ </lifecycle>
+ </extension>
+
</component>
@PathParam("transition") String transition) {
PoxPayloadOut result = null;
+ try {
+ result = updateItemWorkflowWithTransition(null, // Ok to send null
+ csid, itemcsid, transition, AuthorityServiceUtils.UPDATE_REV);
+ } catch (Exception e) {
+ throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
+ }
+
+ return result.getBytes();
+ }
+
+ /**
+ * Update an authority item's workflow state.
+ * @param existingContext
+ * @param csid
+ * @param itemcsid
+ * @param transition
+ * @return
+ */
+ public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext,
+ String csid,
+ String itemcsid,
+ String transition,
+ boolean updateRevNumber) {
+ PoxPayloadOut result = null;
+
try {
//
// Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context
+ //
PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(),
WorkflowClient.SERVICE_COMMONPART_NAME);
MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input);
+ if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
+ ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession()); // If a repo session is already open, we need to use it and not create a new one
+ }
- // Create a service context and document handler for the parent resource.
- ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getItemServiceName());
- DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> parentDocHandler = this.createDocumentHandler(parentCtx);
- ctx.setProperty(WorkflowClient.PARENT_DOCHANDLER, parentDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
+ // Create a service context and document handler for the target resource -not the workflow resource itself.
+ ServiceContext<PoxPayloadIn, PoxPayloadOut> targetCtx = createServiceContext(getItemServiceName());
+ AuthorityItemDocumentModelHandler parentDocHandler = (AuthorityItemDocumentModelHandler) this.createDocumentHandler(targetCtx);
+ parentDocHandler.setShouldUpdateRevNumber(updateRevNumber);
+ ctx.setProperty(WorkflowClient.TARGET_DOCHANDLER, parentDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
- // When looking for the document, we need to use the parent's workspace name -not the "workflow" workspace name
- String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
+ // When looking for the document, we need to use the parent resource's workspace name -not the "workflow" workspace name
+ String parentWorkspaceName = targetCtx.getRepositoryWorkspaceName();
ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
// Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
- TransitionDef transitionDef = getTransitionDef(parentCtx, transition);
+ TransitionDef transitionDef = getTransitionDef(targetCtx, transition);
ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef);
WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
} catch (Exception e) {
throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
}
-
- return result.getBytes();
+
+ return result;
}
private PoxPayloadOut getAuthorityItem(
import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
import org.collectionspace.services.config.service.ObjectPartType;
+import org.collectionspace.services.lifecycle.TransitionDef;
import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
itemIdentifier, parentIdentifier));
+ //
+ // Handle the workflow state
+ //
}
}
documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
}
+ /*
+ * We consider workflow state changes as changes that should bump the revision number
+ * (non-Javadoc)
+ * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
+ */
+ @Override
+ public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
+ // Update the revision number
+ updateRevNumbers(wrapDoc);
+ }
+
@Override
public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
super.handleCreate(wrapDoc);
package org.collectionspace.services.common.vocabulary.nuxeo;
import org.collectionspace.services.client.AuthorityClient;
+import org.collectionspace.services.client.CollectionSpaceClient;
import org.collectionspace.services.client.IQueryManager;
import org.collectionspace.services.client.PoxPayloadIn;
import org.collectionspace.services.client.PoxPayloadOut;
import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
import org.collectionspace.services.config.service.ListResultField;
import org.collectionspace.services.config.service.ObjectPartType;
+import org.collectionspace.services.lifecycle.TransitionDef;
import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
import org.collectionspace.services.nuxeo.util.NuxeoUtils;
import org.collectionspace.services.relation.RelationsCommonList;
import org.collectionspace.services.vocabulary.VocabularyItemJAXBSchema;
-
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.model.PropertyException;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import javax.ws.rs.core.MultivaluedMap;
import java.util.ArrayList;
return list;
}
+ /**
+ * We consider workflow state changes as a change that should bump the revision number.
+ */
+ @Override
+ public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
+ // Update the revision number
+ if (this.getShouldUpdateRevNumber() == true) { // We don't update the rev number of synchronization requests
+ updateRevNumbers(wrapDoc);
+ }
+ }
+
/**
* This method synchronizes/updates a single authority item resource.
*/
public boolean handleSync(DocumentWrapper<Object> wrapDoc) throws Exception {
boolean result = false;
ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
+
//
- // Get the rev number of the local authority item so we can compare with rev number of shared authority
+ // Get information about the local authority item so we can compare with corresponding item on the shared authority server
//
AuthorityItemSpecifier authorityItemSpecifier = (AuthorityItemSpecifier) wrapDoc.getWrappedObject();
DocumentModel itemDocModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), getAuthorityItemCommonSchemaName(),
authorityItemSpecifier.getItemSpecifier().value));
}
Long localItemRev = (Long) NuxeoUtils.getProperyValue(itemDocModel, AuthorityItemJAXBSchema.REV);
+ String localItemCsid = itemDocModel.getName();
+ String localItemWorkflowState = (String) NuxeoUtils.getProperyValue(itemDocModel, CollectionSpaceClient.CORE_WORKFLOWSTATE);
String itemShortId = (String) NuxeoUtils.getProperyValue(itemDocModel, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
+
//
- // Now get the Authority (the parent) information
+ // Now get the item's Authority (the parent) information
//
DocumentModel authorityDocModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName,
authorityItemSpecifier.getParentSpecifier());
String authorityShortId = (String) NuxeoUtils.getProperyValue(authorityDocModel, AuthorityJAXBSchema.SHORT_IDENTIFIER);
+ String localParentCsid = authorityDocModel.getName();
//
// Using the short IDs of the local authority and item, create URN specifiers to retrieve the SAS authority item
//
PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadIn(sasAuthorityItemSpecifier,
getAuthorityServicePath(), getEntityResponseType());
Long sasRev = getRevision(sasPayloadIn);
+ String sasWorkflowState = getWorkflowState(sasPayloadIn);
//
// If the shared authority item is newer, update our local copy
//
PoxPayloadOut payloadOut = authorityResource.updateAuthorityItem(ctx,
resourceMap,
ctx.getUriInfo(),
- authorityDocModel.getName(), // parent's CSID
- itemDocModel.getName(), // item's CSID
- sasPayloadIn, // the payload from the SAS
+ localParentCsid, // parent's CSID
+ localItemCsid, // item's CSID
+ sasPayloadIn, // the payload from the remote SAS
AuthorityServiceUtils.DONT_UPDATE_REV); // don't update the parent's revision number
if (payloadOut != null) {
ctx.setOutput(payloadOut);
result = true;
}
}
+ //
+ // If the workflow states are different, we need to update the local's to reflects the remote's
+ //
+ if (localItemWorkflowState.equalsIgnoreCase(sasWorkflowState) == false) {
+ ResourceMap resourceMap = ctx.getResourceMap();
+ String resourceName = this.getAuthorityServicePath();
+ AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
+ authorityResource.updateItemWorkflowWithTransition(ctx, localParentCsid, localItemCsid, transition, AuthorityServiceUtils.DONT_UPDATE_REV)
+ }
return result;
}
//import java.util.ArrayList;
//import java.util.List;
+import java.util.Random;
+
import javax.ws.rs.core.Response;
import org.collectionspace.services.client.CollectionSpaceClient;
import org.collectionspace.services.authorization.RolesList;
import org.collectionspace.services.client.RoleFactory;
import org.collectionspace.services.client.test.AbstractServiceTestImpl;
-
import org.testng.Assert;
import org.testng.annotations.Test;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The logger. */
private final static String CLASS_NAME = RoleServiceTest.class.getName();
private final static Logger logger = LoggerFactory.getLogger(CLASS_NAME);
-
+
+ // Used to create unique identifiers
+ static private final Random random = new Random(System.currentTimeMillis());
+
// Instance variables specific to this test.
/** The known resource id. */
private String knownRoleName = "ROLE_USERS_MOCK-1";
// Submit the request to the service and store the response.
RoleClient client = new RoleClient();
- Role role = createRoleInstance(knownRoleName + System.currentTimeMillis(),
+ Role role = createRoleInstance(knownRoleName + createIdentifier(),
"role users with non-unique display name",
true);
role.setDisplayName(knownRoleDisplayName);
res.close();
}
}
+
+ protected String createIdentifier() {
+ long identifier = System.currentTimeMillis() + random.nextInt();
+ return Long.toString(identifier);
+ }
/**
* Creates the without role name.
*/
package org.collectionspace.services.client.workflow;
+import java.util.HashMap;
+
import javax.ws.rs.core.Response;
import org.collectionspace.services.client.AbstractCommonListPoxServiceClientImpl;
//
// Workflow states
//
+ public static final String WORKFLOWSTATE_XML_ELEMENT_NAME = COLLECTIONSPACE_CORE_WORKFLOWSTATE;
public static final String WORKFLOWTRANSITION_UNDELETE = "undelete";
public static final String WORKFLOWTRANSITION_DELETE = "delete";
public static final String WORKFLOWSTATE_DELETED = "deleted";
- public static final String WORKFLOWSTATE_ACTIVE = "active";
+ public static final String WORKFLOWSTATE_ACTIVE = "active"; // Is this a used state?
public static final String WORKFLOWSTATE_PROJECT = "project";
public static final String WORKFLOWTRANSITION_LOCK = "lock";
public static final String WORKFLOWSTATE_LOCKED = "locked";
- public static final String WORKFLOWTRANSITION_TO = "to";
+ public static final String WORKFLOWTRANSITION_TO = "to";
//
// DocumentHandler passed properties
//
//
public static final String WORKFLOW_QUERY_NONDELETED = "wf_deleted";
public static final String WORKFLOWSTATE_QUERY = "wf_deleted";
- public static final String PARENT_DOCHANDLER = "wf_dochandler";
+ public static final String TARGET_DOCHANDLER = "wf_dochandler";
+
+ //
+ // A mapping of state to the transition verbs that will get you there. For more details,
+ // see default-life-cycle-contrib.xml in the Nuxeo server configuration
+ //
+ private static final HashMap<String, String> statesMappedToTransitions;
+ static
+ {
+ statesMappedToTransitions = new HashMap<String, String>();
+ statesMappedToTransitions.put(WORKFLOWSTATE_DELETED, WORKFLOWTRANSITION_DELETE);
+ statesMappedToTransitions.put("c", "d");
+ }
@Override
cspace.ssl=false
cspace.auth=true
# default user
-#cspace.user=admin@core.collectionspace.org
-cspace.user=admin@testsci.collectionspace.org
+cspace.user=admin@core.collectionspace.org
+#cspace.user=admin@testsci.collectionspace.org
cspace.password=Administrator
# default tenant
cspace.tenant=1
// Create a service context and document handler for the parent resource.
ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(uriInfo);
- DocumentHandler parentDocHandler = this.createDocumentHandler(parentCtx);
- ctx.setProperty(WorkflowClient.PARENT_DOCHANDLER, parentDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
+ DocumentHandler targetDocHandler = this.createDocumentHandler(parentCtx);
+ ctx.setProperty(WorkflowClient.TARGET_DOCHANDLER, targetDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
// When looking for the document, we need to use the parent's workspace name -not the "workflow" workspace name
- String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
- ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
+ String targetWorkspaceName = parentCtx.getRepositoryWorkspaceName();
+ ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace
// Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
TransitionDef transitionDef = getTransitionDef(parentCtx, transition);
// the super/parent handleUpdate() method.
//
ServiceContext ctx = this.getServiceContext();
- DocumentModelHandler docHandler = (DocumentModelHandler)ctx.getProperty(WorkflowClient.PARENT_DOCHANDLER);
+ DocumentModelHandler targetDocHandler = (DocumentModelHandler)ctx.getProperty(WorkflowClient.TARGET_DOCHANDLER);
+ targetDocHandler.setRepositorySession(this.getRepositorySession()); // Make sure the target doc handler has a repository session to work with
TransitionDef transitionDef = (TransitionDef)ctx.getProperty(WorkflowClient.TRANSITION_ID);
- docHandler.handleWorkflowTransition(wrapDoc, transitionDef);
+ targetDocHandler.handleWorkflowTransition(wrapDoc, transitionDef); // Call the parent resouce's handler first
//
// If no exception occurred, then call the super's method
//
import org.collectionspace.services.client.PoxPayload;
import org.collectionspace.services.client.PoxPayloadIn;
import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.workflow.WorkflowClient;
import org.collectionspace.services.common.ReflectionMapper;
import org.collectionspace.services.common.XmlTools;
import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
return String.class;
}
+ protected String getWorkflowState(PoxPayload payload) {
+ String result = null;
+
+ Document document = payload.getDOMDocument();
+ result = XmlTools.getElementValue(document, "//" + WorkflowClient.WORKFLOWSTATE_XML_ELEMENT_NAME);
+
+ return result;
+ }
+
protected Long getRevision(PoxPayload payload) {
Long result = null;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.collectionspace.services.organization.OrganizationsCommon;
import org.collectionspace.services.organization.OrgTermGroup;
import org.collectionspace.services.organization.OrgTermGroupList;
-
import org.jboss.resteasy.client.ClientResponse;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
protected PoxPayloadOut createItemInstance(String parentCsid, String identifier) {
String headerLabel = new OrgAuthorityClient().getItemCommonPartName();
- String shortId = TEST_SHORT_ID;
+ String shortId = TEST_SHORT_ID + identifier;
Map<String, String> testOrgMap = new HashMap<String, String>();
testOrgMap.put(OrganizationJAXBSchema.SHORT_IDENTIFIER, shortId);
testOrgMap.put(OrganizationJAXBSchema.FOUNDING_DATE, TEST_ORG_FOUNDING_DATE);
// Submit the request to the service and store the response.
OrgAuthorityClient client = new OrgAuthorityClient();
- String shortId = TEST_SHORT_ID;
+ String shortId = TEST_SHORT_ID + System.currentTimeMillis();
Map<String, String> testOrgMap = new HashMap<String, String>();
testOrgMap.put(OrganizationJAXBSchema.SHORT_IDENTIFIER, shortId);
testOrgMap.put(OrganizationJAXBSchema.FOUNDING_DATE, TEST_ORG_FOUNDING_DATE);
import org.collectionspace.services.person.PersonauthoritiesCommon;
import org.collectionspace.services.person.SchoolOrStyleList;
import org.collectionspace.services.person.StructuredDateGroup;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger =
LoggerFactory.getLogger(PersonAuthorityClientUtils.class);
private static final ServiceRequestType READ_REQ = ServiceRequestType.READ;
+ static private final Random random = new Random(System.currentTimeMillis());
/**
* @param csid the id of the PersonAuthority
}
private static String getGeneratedIdentifier() {
- return "id" + new Date().getTime();
+ return "id" + createIdentifier();
}
+
+ private static String createIdentifier() {
+ long identifier = System.currentTimeMillis() + random.nextInt();
+ return Long.toString(identifier);
+ }
}
String headerLabel = new PersonAuthorityClient().getItemCommonPartName();
HashMap<String, String> personInfo = new HashMap<String, String>();
- String shortId = "MarkTwainAuthor";
+ String shortId = "MarkTwainAuthor" + identifier;
personInfo.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId);
List<PersonTermGroup> terms = new ArrayList<PersonTermGroup>();
//
// Fill the property map
//
- String shortId = "johnWayneActor";
+ String shortId = "johnWayneActor" + System.currentTimeMillis(); // short ID needs to be unique
johnWayneMap.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId);
johnWayneMap.put(PersonJAXBSchema.GENDER, "male");
johnWayneMap.put(PersonJAXBSchema.BIRTH_DATE, TEST_BIRTH_DATE);
protected PoxPayloadOut createItemInstance(String parentCsid, String identifier) {
String headerLabel = new VocabularyClient().getItemCommonPartName();
HashMap<String, String> vocabItemInfo = new HashMap<String, String>();
- String shortId = createIdentifier();
+ String shortId = identifier;
vocabItemInfo.put(AuthorityItemJAXBSchema.SHORT_IDENTIFIER, shortId);
vocabItemInfo.put(AuthorityItemJAXBSchema.DISPLAY_NAME, "display-" + shortId);