]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
e82bb881a7b6ce9395b78cee90533a2bf741fb1e
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.common.vocabulary;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9
10 import javax.ws.rs.core.Response;
11
12 import org.collectionspace.services.client.AuthorityClient;
13 import org.collectionspace.services.client.PoxPayloadIn;
14 import org.collectionspace.services.client.workflow.WorkflowClient;
15 import org.collectionspace.services.common.ServiceMain;
16 import org.collectionspace.services.common.api.RefNameUtils;
17 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
18 import org.collectionspace.services.common.api.Tools;
19 import org.collectionspace.services.common.context.MultipartServiceContextImpl;
20 import org.collectionspace.services.common.context.ServiceContext;
21 import org.collectionspace.services.common.document.DocumentException;
22 import org.collectionspace.services.common.document.DocumentNotFoundException;
23 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
24 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
25 import org.collectionspace.services.config.service.ServiceBindingType;
26 import org.collectionspace.services.config.tenant.RemoteClientConfig;
27 import org.collectionspace.services.config.tenant.RemoteClientConfigurations;
28 import org.collectionspace.services.config.tenant.TenantBindingType;
29 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
30 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
31 import org.dom4j.Document;
32 import org.dom4j.DocumentHelper;
33 import org.dom4j.Node;
34 import org.dom4j.XPath;
35 import org.nuxeo.ecm.core.api.DocumentModel;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 @SuppressWarnings("rawtypes")
40 public class AuthorityServiceUtils {
41         private static final Logger logger = LoggerFactory.getLogger(AuthorityServiceUtils.class);
42         //
43         // Used to keep track if an authority item's is deprecated
44         public static final String DEFAULT_REMOTECLIENT_CONFIG_NAME = "default";
45         public static final String IS_DEPRECATED_PROPERTY = "IS_DEPRECATED_PROPERTY";
46         public static final Boolean DEPRECATED = true;
47         public static final Boolean NOT_DEPRECATED = !DEPRECATED;
48
49         // Used to keep track if an authority item's rev number should be updated
50         public static final String SHOULD_UPDATE_REV_PROPERTY = "SHOULD_UPDATE_REV_PROPERTY";
51         public static final boolean UPDATE_REV = true;
52         public static final boolean DONT_UPDATE_REV = !UPDATE_REV;
53
54         public static final boolean ROLLBACK_ON_EXCEPTION = true;
55         public static final boolean DONT_ROLLBACK_ON_EXCEPTION = !ROLLBACK_ON_EXCEPTION;
56
57         // Used to keep track if an authority item is a locally proposed member of a SAS authority
58         public static final String IS_PROPOSED_PROPERTY = "IS_PROPOSED";
59         public static final Boolean PROPOSED = true;
60         public static final Boolean NOT_PROPOSED = !PROPOSED;
61         public static final Boolean SAS_ITEM = true;
62         public static final Boolean NOT_SAS_ITEM = !SAS_ITEM;
63
64         public static final Boolean NO_CHANGE = null;
65
66         // Matches the domain name part of a refname. For example, "core.collectionspace.org" of
67         // urn:cspace:core.collectionspace.org:personauthorities:name(person):item:name(BigBird1461101206103)'Big Bird'.
68         public static final     Pattern REFNAME_DOMAIN_PATTERN = Pattern.compile("(urn:cspace:)(([a-z]{1,}\\.?)*)");
69
70         /*
71          * Try to find a named remote client configuration in the current tenant bindings.  If the value of the incoming param 'remoteClientConfigName' is
72          * blank or null, we'll try to find a name in the authority service's bindings.  If we can't find a name there, we'll try using the default name.
73          *
74          * If the incoming param 'remoteClientConfigName' is not null, we'll look through all the named remote client configurations in the tenant's binding
75          * to find the configuration.  If we can't find the named configuration, we'll throw an exception.
76          *
77          * If there are no remote client configurations in the tenant's bindings, we'll throw an exception.
78          */
79         public static final RemoteClientConfig getRemoteClientConfig(ServiceContext ctx, String remoteClientConfigName) throws Exception {
80                 RemoteClientConfig result = null;
81
82                 TenantBindingType tenantBinding = ServiceMain.getInstance().getTenantBindingConfigReader().getTenantBinding(ctx.getTenantId());
83                 RemoteClientConfigurations remoteClientConfigurations = tenantBinding.getRemoteClientConfigurations();
84                 if (remoteClientConfigurations != null) {
85                         if (Tools.isEmpty(remoteClientConfigName) == true) {
86                                 // Since the authority instance didn't specify a remote client config name, let's see if the authority type's service bindings specifies one
87                                 ServiceBindingType serviceBindingType =
88                                                 ServiceMain.getInstance().getTenantBindingConfigReader().getServiceBinding(ctx.getTenantId(), ctx.getServiceName());
89                                 remoteClientConfigName = serviceBindingType.getRemoteClientConfigName();
90                         }
91                         //
92                         // If we still don't have a remote client config name, let's use the default value.
93                         //
94                         if (Tools.isEmpty(remoteClientConfigName) == true) {
95                                 remoteClientConfigName = DEFAULT_REMOTECLIENT_CONFIG_NAME;
96                         }
97
98                         List<RemoteClientConfig> remoteClientConfigList = remoteClientConfigurations.getRemoteClientConfig();
99                         for (RemoteClientConfig config : remoteClientConfigList) {
100                                 if (config.getName().equalsIgnoreCase(remoteClientConfigName)) {
101                                         result = config;
102                                         break;
103                                 }
104                         }
105                 } else {
106                         String errMsg = String.format("No remote client configurations could be found in the tenant bindings for tenant named '%s'.",
107                                         ctx.getTenantName());
108                         logger.error(errMsg);
109                         throw new Exception(errMsg);
110                 }
111
112                 if (result == null) {
113                         String errMsg = String.format("Could not find a remote client configuration named '%s' in the tenant bindings for tenant named '%s'",
114                                         remoteClientConfigName, ctx.getTenantName());
115                         logger.error(errMsg);
116                         throw new Exception(errMsg);
117                 }
118
119                 return result;
120         }
121
122         /**
123          * Make a request to the SAS Server for an authority payload.
124          *
125          * @param ctx
126          * @param specifier
127          * @param responseType
128          * @return
129          * @throws Exception
130          */
131         public static PoxPayloadIn requestPayloadInFromRemoteServer(ServiceContext ctx, String remoteClientConfigName, Specifier specifier, Class responseType) throws Exception {
132                 PoxPayloadIn result = null;
133
134                 RemoteClientConfig remoteClientConfig = getRemoteClientConfig(ctx, remoteClientConfigName);
135                 AuthorityClient client = (AuthorityClient) ctx.getClient(remoteClientConfig);
136
137                 Response res = client.read(specifier.getURNValue());
138                 try {
139                         int statusCode = res.getStatus();
140                         if (statusCode == org.apache.commons.httpclient.HttpStatus.SC_OK) {
141                                 result = new PoxPayloadIn((String)res.readEntity(responseType)); // Get the entire response!
142                         } else {
143                                 String errMsg = String.format("Could not retrieve authority information for '%s' on remote server '%s'.  Server returned status code %d",
144                                                 specifier.getURNValue(), remoteClientConfig.getUrl(), statusCode);
145                                 if (logger.isDebugEnabled()) {
146                                         logger.debug(errMsg);
147                                 }
148                                 throw new DocumentException(statusCode, errMsg);
149                         }
150                 } finally {
151                         res.close();
152                 }
153
154                 return result;
155         }
156
157         //
158         // Makes a call to the remote SAS server for a authority item payload
159         //
160         public static PoxPayloadIn requestPayloadInFromRemoteServer(
161                         AuthorityItemSpecifier specifier,
162                         String remoteClientConfigName,
163                         String serviceName,
164                         Class responseType,
165                         boolean syncHierarchicalRelationships) throws Exception {
166                 PoxPayloadIn result = null;
167
168                 ServiceContext authorityCtx = new MultipartServiceContextImpl(serviceName);
169                 RemoteClientConfig remoteClientConfig = getRemoteClientConfig(authorityCtx, remoteClientConfigName);
170                 AuthorityClient client = (AuthorityClient) authorityCtx.getClient(remoteClientConfig);
171                 Response res = client.readItem(specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(),
172                                 AuthorityClient.INCLUDE_DELETED_ITEMS, syncHierarchicalRelationships);
173
174                 try {
175                         int statusCode = res.getStatus();
176                         if (statusCode == org.apache.commons.httpclient.HttpStatus.SC_OK) {
177                                 result = new PoxPayloadIn((String)res.readEntity(responseType)); // Get the entire response.
178                         } else {
179                                 String errMsg = String.format("Could not retrieve authority item information for '%s:%s' on remote server '%s'.  Server returned status code %d",
180                                                 specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(), remoteClientConfig.getUrl(), statusCode);
181                                 if (logger.isDebugEnabled()) {
182                                         logger.debug(errMsg);
183                                 }
184                                 throw new DocumentException(statusCode, errMsg);
185                         }
186                 } finally {
187                         res.close();
188                 }
189
190                 return result;
191         }
192
193         /*
194          * The domain name part of refnames on a remote SAS may not match that of local refnames.
195          * Update all the payload's refnames with the local domain name.
196          */
197         public static PoxPayloadIn localizeRefNameDomains(ServiceContext ctx, PoxPayloadIn payload) throws org.dom4j.DocumentException {
198                 String localDomain = ctx.getTenantName();
199                 Matcher matcher = REFNAME_DOMAIN_PATTERN.matcher(payload.getXmlPayload());
200                 StringBuffer localizedXmlBuffer = new StringBuffer();
201
202                 while (matcher.find() == true) {
203                         String remoteDomain = matcher.group(2);
204
205                         if (logger.isDebugEnabled()) {
206                                 logger.debug("Replacing " + remoteDomain + " with " + localDomain);
207                         }
208
209                         matcher.appendReplacement(localizedXmlBuffer, matcher.group(1) + localDomain);
210                 }
211
212                 matcher.appendTail(localizedXmlBuffer);
213
214                 if (logger.isTraceEnabled()) {
215                         logger.trace(String.format("Updated payload:\n%s", localizedXmlBuffer));
216                 }
217
218                 return new PoxPayloadIn(localizedXmlBuffer.toString());
219         }
220
221         /**
222          * Localizes the relations list in an authority item payload from a remote server during SAS sync.
223          *
224          * Relations to items that do not exist in the local authority are removed. If the related item
225          * doesn't exist locally because it is new on the remote and hasn't been synced yet, the relations
226          * will be created when the new item is synced, because the new item's relations list will include
227          * them.
228          *
229          * The following elements are removed from each relation: uri, csid, subjectCsid, objectCsid,
230          * object/csid, object/uri, subject/csid, subject/uri. These apply only to the remote. By removing
231          * them, the relation will be created locally if necessary.
232          *
233          * @param ctx
234          * @param authorityResource
235          * @param parentCsid
236          * @param itemSpecifier
237          * @param payload
238          * @return
239          * @throws Exception
240          */
241         public static PoxPayloadIn localizeRelations(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, Specifier itemSpecifier, PoxPayloadIn payload) throws Exception {
242                 // TODO: Relations to items that don't exist need to be removed, because a create/update will fail
243                 // if the subject/object of any supplied relation can't be found. Consider changing the create/update
244                 // code to ignore any relations to items that don't exist, but still save the record and any other
245                 // relations. This will speed up sync when many items have relations, since the checks to see if
246                 // all related items exist locally can be skipped.
247
248                 String itemShortId = itemSpecifier.value;
249                 Document document = payload.getDOMDocument();
250
251                 Map<String, String> namespaceUris = new HashMap<String, String>();
252                 namespaceUris.put("rel", "http://collectionspace.org/services/relation");
253
254                 XPath xPath = DocumentHelper.createXPath("//rel:relations-common-list/relation-list-item");
255                 xPath.setNamespaceURIs(namespaceUris);
256
257                 List<Node> listItemNodes = xPath.selectNodes(document);
258
259                 if (logger.isDebugEnabled()) {
260                         logger.debug(String.format("Found %d relation list items", listItemNodes.size()));
261                 }
262
263                 for (Node listItemNode : listItemNodes) {
264                         String objectRefName = listItemNode.selectSingleNode("object/refName").getText();
265                         AuthorityTermInfo objectTermInfo = RefNameUtils.parseAuthorityTermInfo(objectRefName);
266                         String objectShortId = objectTermInfo.name;
267
268                         if (
269                                 !objectShortId.equals(itemShortId)
270                                 && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(objectShortId))
271                         ) {
272                                 if (logger.isDebugEnabled()) {
273                                         logger.debug(String.format("Omitting remote relation: object with short id %s does does not exist locally", objectShortId));
274                                 }
275
276                                 listItemNode.detach();
277                                 continue;
278                         }
279
280                         String subjectRefName = listItemNode.selectSingleNode("subject/refName").getText();
281                         AuthorityTermInfo subjectTermInfo = RefNameUtils.parseAuthorityTermInfo(subjectRefName);
282                         String subjectShortId = subjectTermInfo.name;
283
284                         if (
285                                 !subjectShortId.equals(itemShortId)
286                                 && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(subjectShortId))
287                         ) {
288                                 if (logger.isDebugEnabled()) {
289                                         logger.debug(String.format("Omitting remote relation: subject with short id %s does does not exist locally", subjectShortId));
290                                 }
291
292                                 listItemNode.detach();
293                                 continue;
294                         }
295
296                         listItemNode.selectSingleNode("csid").detach();
297                         listItemNode.selectSingleNode("objectCsid").detach();
298                         listItemNode.selectSingleNode("subjectCsid").detach();
299                         listItemNode.selectSingleNode("uri").detach();
300                         listItemNode.selectSingleNode("object/csid").detach();
301                         listItemNode.selectSingleNode("object/uri").detach();
302                         listItemNode.selectSingleNode("subject/csid").detach();
303                         listItemNode.selectSingleNode("subject/uri").detach();
304                 }
305
306                 String xml = document.asXML();
307
308                 if (logger.isTraceEnabled()) {
309                         logger.trace("Prepared remote relations:\n" + xml);
310                 }
311
312                 return new PoxPayloadIn(xml);
313         }
314
315         /**
316          * Check if an item with a given short ID exists in a parent.
317          *
318          * @param ctx
319          * @param authorityResource
320          * @param parentCsid
321          * @param itemSpecifier
322          * @return true if the item exists, false otherwise.
323          * @throws Exception
324          */
325         public static boolean checkItemExists(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, String itemSpecifier) throws Exception {
326                 String itemCsid = null;
327
328                 try {
329                         itemCsid = authorityResource.lookupItemCSID(ctx, itemSpecifier, parentCsid, "checkItemExists()", "CHECK_ITEM_EXISTS");
330                 } catch (DocumentNotFoundException e) {
331                         itemCsid = null;
332                 }
333
334                 return (itemCsid != null);
335         }
336
337         /**
338          * Mark the authority item as deprecated.
339          *
340          * @param ctx
341          * @param itemInfo
342          * @throws Exception
343          */
344         public static boolean setAuthorityItemDeprecated(ServiceContext ctx, AuthorityResource authorityResource, String authorityItemCommonSchemaName, AuthorityItemSpecifier authorityItemSpecifier) throws Exception {
345                 DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, (CoreSessionInterface)ctx.getCurrentRepositorySession(),
346                                 authorityItemCommonSchemaName, authorityItemSpecifier);
347
348                 return setAuthorityItemDeprecated(ctx, authorityResource, authorityItemSpecifier.getParentSpecifier().value, authorityItemSpecifier.getItemSpecifier().getURNValue(), docModel);
349         }
350
351         public static boolean setAuthorityItemDeprecated(ServiceContext ctx, AuthorityResource authorityResource, String parentIdentifier, String itemIdentifier, DocumentModel docModel) throws Exception {
352                 String currentWorkflowState = docModel.getCurrentLifeCycleState();
353
354                 // Find the transitions needed to get from the current state to the replicated deprecated state.
355                 List<String> transitions = AuthorityServiceUtils.getTransitionList(WorkflowClient.WORKFLOWSTATE_DEPRECATED, currentWorkflowState);
356
357                 if (!transitions.isEmpty()) {
358                         for (String transition : transitions) {
359                                 authorityResource.updateItemWorkflowWithTransition(ctx, parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.DONT_UPDATE_REV);
360                         }
361                 }
362
363                 return true;
364         }
365
366         /**
367          * We need to change the local item's state to one that maps to the replication server's workflow
368          * state.  This might involve making multiple transitions.
369          *
370          * WIKI:
371          *       See table at https://collectionspace.atlassian.net/wiki/spaces/SDR/pages/665886940/Workflow+transitions+to+map+SAS+item+states+to+Local+item+states
372          *       (was https://wiki.collectionspace.org/pages/viewpage.action?pageId=162496564)
373          *
374          */
375         public static List<String> getTransitionList(String sasWorkflowState, String localItemWorkflowState) throws DocumentException {
376                 List<String> result = new ArrayList<String>();
377                 //
378                 // The first set of conditions maps a replication-server "project" state to a local client state of "replicated"
379                 //
380                 if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT)) {
381                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
382                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
383                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
384                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
385                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED)) {
386                         // Do nothing.  We're good with this state
387                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED)) {
388                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
389                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED)) {
390                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
391                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED_DELETED)) {
392                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
393                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
394                 //
395                 // The second set of conditions maps a replication-server "deleted" state to a local client state of "deleted"
396                 //
397                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT)) {
398                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
399                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
400                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
401                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
402                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED)) {
403                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
404                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED)) {
405                         // Do nothing.  We're good with this state
406                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED)) {
407                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
408                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
409                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED_DELETED)) {
410                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
411                 //
412                 // The third set of conditions maps a replication-server "replicated" state to a local state of "replicated"
413                 //
414                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT)) {
415                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
416                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
417                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
418                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
419                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED)) {
420                         // Do nothing.  We're good with this state
421                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED)) {
422                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
423                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED)) {
424                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
425                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED_DELETED)) {
426                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
427                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
428                 //
429                 // The fourth set of conditions maps a replicatation-server "deprecated" state to a local state of "replicated_deprecated"
430                 //
431                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT)) {
432                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
433                         result.add(WorkflowClient.WORKFLOWTRANSITION_DEPRECATE);
434                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
435                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
436                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
437                         result.add(WorkflowClient.WORKFLOWTRANSITION_DEPRECATE);
438                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED)) {
439                         result.add(WorkflowClient.WORKFLOWTRANSITION_DEPRECATE);
440                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED)) {
441                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
442                         result.add(WorkflowClient.WORKFLOWTRANSITION_DEPRECATE);
443                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED)) {
444                         // Do nothing.
445                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DEPRECATED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED_DELETED)) {
446                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDELETE);
447                 //
448                 // The last set of conditions maps a replication-server "replicated_deleted" state to a local client state of "deleted"
449                 //
450                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_PROJECT)) {
451                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
452                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
453                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
454                         result.add(WorkflowClient.WORKFLOWTRANSITION_REPLICATE);
455                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED)) {
456                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
457                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED)) {
458                         // Do nothing.  We're good with this state
459                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED)) {
460                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
461                         result.add(WorkflowClient.WORKFLOWTRANSITION_DELETE);
462                 } else if (sasWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DELETED) && localItemWorkflowState.equals(WorkflowClient.WORKFLOWSTATE_REPLICATED_DEPRECATED_DELETED)) {
463                         result.add(WorkflowClient.WORKFLOWTRANSITION_UNDEPRECATE);
464                 } else {
465                         //
466                         // If we get here, we've encountered a SAS workflow state that we don't recognize.
467                         //
468                         throw new DocumentException(String.format("Encountered an invalid workflow state of '%s' on a SAS authority item.", sasWorkflowState));
469                 }
470
471                 return result;
472         }
473 }