]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
ee0ead044881e8f737f2bbc9930acc320fb870af
[tmp/jakarta-migration.git] /
1 /**
2  *  This document is a part of the source code and related artifacts
3  *  for CollectionSpace, an open source collections management system
4  *  for museums and related institutions:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
18  *  Unless required by applicable law or agreed to in writing, software
19  *  distributed under the License is distributed on an "AS IS" BASIS,
20  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21  *  See the License for the specific language governing permissions and
22  *  limitations under the License.
23  */
24 package org.collectionspace.services.common.vocabulary.nuxeo;
25
26 import java.util.List;
27 import java.util.Map;
28
29 import javax.ws.rs.core.Response;
30
31 import org.collectionspace.services.client.AuthorityClient;
32 import org.collectionspace.services.client.CollectionSpaceClient;
33 import org.collectionspace.services.client.PayloadInputPart;
34 import org.collectionspace.services.client.VocabularyClient;
35 import org.collectionspace.services.client.PoxPayloadIn;
36 import org.collectionspace.services.client.PoxPayloadOut;
37 import org.collectionspace.services.common.ResourceMap;
38 import org.collectionspace.services.common.XmlTools;
39 import org.collectionspace.services.common.api.RefName;
40 import org.collectionspace.services.common.api.RefName.Authority;
41 import org.collectionspace.services.common.api.RefNameUtils;
42 import org.collectionspace.services.common.api.RefNameUtils.AuthorityInfo;
43 import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo;
44 import org.collectionspace.services.common.api.Tools;
45 import org.collectionspace.services.common.context.ServiceContext;
46 import org.collectionspace.services.common.document.DocumentException;
47 import org.collectionspace.services.common.document.DocumentHandler;
48 import org.collectionspace.services.common.document.DocumentNotFoundException;
49 import org.collectionspace.services.common.document.DocumentWrapper;
50 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
51 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
52 import org.collectionspace.services.common.vocabulary.AuthorityResource;
53 import org.collectionspace.services.common.vocabulary.AuthorityServiceUtils;
54 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
55 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
56 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
57 import org.collectionspace.services.config.service.ObjectPartType;
58 import org.collectionspace.services.lifecycle.TransitionDef;
59 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
60 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
61 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
62 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
63 import org.dom4j.Document;
64 import org.dom4j.Element;
65 import org.nuxeo.ecm.core.api.ClientException;
66 import org.nuxeo.ecm.core.api.DocumentModel;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 /**
71  * AuthorityDocumentModelHandler
72  *
73  * $LastChangedRevision: $
74  * $LastChangedDate: $
75  */
76 public abstract class AuthorityDocumentModelHandler<AuthCommon>
77         extends NuxeoDocumentModelHandler<AuthCommon> {
78     
79         private final Logger logger = LoggerFactory.getLogger(AuthorityDocumentModelHandler.class);     
80     
81         protected String authorityCommonSchemaName;
82     protected String authorityItemCommonSchemaName;
83     protected boolean shouldUpdateRevNumber = true; // default to updating the revision number
84
85     public AuthorityDocumentModelHandler(String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
86         this.authorityCommonSchemaName = authorityCommonSchemaName;
87         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
88     }
89     
90     public void setShouldUpdateRevNumber(boolean flag) {
91         this.shouldUpdateRevNumber = flag;
92     }
93     
94     public boolean getShouldUpdateRevNumber() {
95         return this.shouldUpdateRevNumber;
96     }
97     
98     /**
99      * The entity type expected from the JAX-RS Response object
100      */
101     public Class<String> getEntityResponseType() {
102         return String.class;
103     }
104     
105     @Override
106     public void prepareSync() throws Exception {
107         this.setShouldUpdateRevNumber(AuthorityServiceUtils.DONT_UPDATE_REV);  // Never update rev nums on sync operations
108     }
109
110     protected PayloadInputPart extractPart(Response res, String partLabel)
111             throws Exception {
112             PoxPayloadIn input = new PoxPayloadIn((String)res.readEntity(getEntityResponseType()));
113             PayloadInputPart payloadInputPart = input.getPart(partLabel);
114             if (payloadInputPart == null) {
115                 logger.error("Part " + partLabel + " was unexpectedly null.");
116             }
117             return payloadInputPart;
118     }
119     
120     @Override
121     public boolean handleSync(DocumentWrapper<Object> wrapDoc) throws Exception {
122         boolean result = false;
123         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
124         Specifier specifier = (Specifier) wrapDoc.getWrappedObject();
125         //
126         // Get the rev number of the authority so we can compare with rev number of shared authority
127         //
128         DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, specifier);
129         Long localRev = (Long) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REV);
130         String shortId = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.SHORT_IDENTIFIER);
131         //
132         // Using the short ID of the local authority, create a URN specifier to retrieve the SAS authority
133         //
134         Specifier sasSpecifier = new Specifier(SpecifierForm.URN_NAME, RefNameUtils.createShortIdRefName(shortId));
135         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadIn(ctx, sasSpecifier, getEntityResponseType());
136         //
137         // If the authority on the SAS is newer, synch all the items and then the authority record as well
138         //
139         Long sasRev = getRevision(sasPayloadIn);
140         if (sasRev > localRev) {
141                 //
142                 // First, sync all the authority items
143                 //
144                 syncAllItems(ctx, sasSpecifier);
145                 //
146                 // Next, sync the authority resource/record itself
147                 //
148                 ResourceMap resourceMap = ctx.getResourceMap();
149                 String resourceName = ctx.getClient().getServiceName();
150                 AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
151                 PoxPayloadOut payloadOut = authorityResource.update(ctx, resourceMap, ctx.getUriInfo(), docModel.getName(), 
152                                 sasPayloadIn);
153                 if (payloadOut != null) {
154                         ctx.setOutput(payloadOut);
155                         result = true;
156                 }
157         }
158         
159         return result;
160     }
161     
162     /*
163      * Get the list of authority items from the remote shared authority server (SAS) and try
164      * to synchronize them with the local items.  If items exist on the remote but not the local, we'll create them.
165      */
166     protected int syncAllItems(ServiceContext ctx, Specifier sasSpecifier) throws Exception {
167         int result = -1;
168         int created = 0;
169         int synched = 0;
170         int alreadySynched = 0;
171         int totalItemsProcessed = 0;
172         //
173         // Iterate over the list of items/terms in the remote authority
174         //
175         PoxPayloadIn sasPayloadInItemList = getPayloadInItemList(ctx, sasSpecifier);
176         List<Element> itemList = getItemList(sasPayloadInItemList);
177         if (itemList != null) {
178                 for (Element e:itemList) {
179                         String remoteRefName = XmlTools.getElementValue(e, "refName");
180                         long status = syncRemoteItemWithLocalItem(ctx, remoteRefName);
181                         if (status == 1) {
182                                 created++;
183                         } else if (status == 0) {
184                                 synched++;
185                         } else {
186                                 alreadySynched++;
187                         }
188                         totalItemsProcessed++;
189                 }
190         }
191         
192         logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed));
193         logger.info(String.format("Number of items synchronized: %d", synched));
194         logger.info(String.format("Number of items created during sync: %d", created));
195         logger.info(String.format("Number not needing synchronization: %d", alreadySynched));
196
197         return result;
198     }
199     
200     /**
201      * This is a sync method.
202      * @param ctx
203      * @param parentIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
204      * @param itemIdentifier   - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
205      * @throws Exception 
206      */
207     protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier) throws Exception {
208         //
209         // Create a URN short ID specifier for the getting a copy of the remote authority item
210         //
211         Specifier authoritySpecifier = new Specifier(SpecifierForm.URN_NAME, parentIdentifier);
212         Specifier itemSpecifier = new Specifier(SpecifierForm.URN_NAME, itemIdentifier);
213         AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier);
214         //
215         // Get the remote payload
216         //
217         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadIn(sasAuthorityItemSpecifier, 
218                         ctx.getServiceName(), getEntityResponseType());
219         //
220         // Using the payload from the remote server, create a local copy of the item
221         //
222         ResourceMap resourceMap = ctx.getResourceMap();
223         String resourceName = ctx.getClient().getServiceName();
224         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
225         Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.value,
226                         sasPayloadIn, AuthorityServiceUtils.DONT_UPDATE_REV);
227         //
228         // Check the response for successful POST result
229         //
230         if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
231                 throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
232                                 itemIdentifier, parentIdentifier));
233         //
234         // Handle the workflow state
235         //
236         }
237     }
238     
239     /**
240      * Try to synchronize a remote item (using its refName) with a local item.  If the local doesn't yet
241      * exist, we'll create it.
242      * Result values:
243      *  -1 = sync not needed; i.e., already in sync
244      *   0 = sync succeeded
245      *   1 = local item was missing so we created it
246      * @param ctx
247      * @param refName
248      * @return
249      * @throws Exception
250      */
251     protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String remoteRefName) throws Exception {
252         long result = -1;
253         //
254         // Using the remote refname, create specifiers that we'll use to find the local versions
255         //
256         AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(remoteRefName);
257         String parentIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.inAuthority.name);
258         String itemIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.name);
259         //
260         // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates)
261         //
262         ResourceMap resourceMap = ctx.getResourceMap();
263         String resourceName = ctx.getClient().getServiceName();
264         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
265         
266         PoxPayloadOut localItemPayloadOut;
267         try {
268                 localItemPayloadOut = authorityResource.getAuthorityItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
269         } catch (DocumentNotFoundException dnf) {
270                 //
271                 // Document not found, means we need to create an item/term that exists only on the SAS
272                 //
273                 logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", remoteRefName));
274                 createLocalItem(ctx, parentIdentifier, itemIdentifier);
275                 return 1; // exit with status of 1 means we created a new authority item
276         }
277         //
278         // If we get here, we know the item exists both locally and remotely, so we need to synchronize them
279         //
280         PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
281         if (theUpdate != null) {
282                 result = 0; // mean we neeed to sync this item with SAS
283                 logger.debug(String.format("Sync'd authority item parent='%s' id='%s with SAS.  Updated payload is: \n%s",
284                                 parentIdentifier, itemIdentifier, theUpdate.getXmlPayload()));
285         }
286         
287         return result; // -1 = no sync needed, 0 = sync'd, 1 = created new item
288     }
289         
290     private PoxPayloadIn getPayloadInItemList(ServiceContext ctx, Specifier specifier) throws Exception {
291         PoxPayloadIn result = null;
292         
293         AuthorityClient client = (AuthorityClient) ctx.getClient();
294         Response res = client.readItemList(specifier.value,
295                         null,   // partial term string
296                         null    // keyword string
297                         );
298         try {
299                 int statusCode = res.getStatus();
300         
301                 // Check the status code of the response: does it match
302                 // the expected response(s)?
303                 if (logger.isDebugEnabled()) {
304                     logger.debug(client.getClass().getCanonicalName() + ": status = " + statusCode);
305                 }
306                 
307             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response!             
308         } finally {
309                 res.close();
310         }
311         
312         return result;
313     }
314     
315
316     /*
317      * Non standard injection of CSID into common part, since caller may access through
318      * shortId, and not know the CSID.
319      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
320      */
321     @Override
322     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
323             throws Exception {
324         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
325
326         // Add the CSID to the common part
327         if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
328             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
329             unQObjectProperties.put("csid", csid);
330         }
331
332         return unQObjectProperties;
333     }
334     
335     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
336         super.fillAllParts(wrapDoc, action);
337         //
338         // Update the record's revision number on both CREATE and UPDATE actions, but not on SYNC
339         //
340         if (this.getShouldUpdateRevNumber() == true) { // We won't update rev numbers on synchronization with SAS
341                 updateRevNumbers(wrapDoc);
342         }
343     }
344     
345     protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
346         DocumentModel documentModel = wrapDoc.getWrappedObject();
347         Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
348         if (rev == null) {
349                 rev = (long)0;
350         } else {
351                 rev++;
352         }
353         documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
354     }
355     
356     /*
357      * We consider workflow state changes as changes that should bump the revision number
358      * (non-Javadoc)
359      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
360      */
361     @Override
362     public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef) throws Exception {
363         // Update the revision number
364         updateRevNumbers(wrapDoc);
365     }
366     
367     @Override
368     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
369         super.handleCreate(wrapDoc);
370         // CSPACE-3178:
371         // Uncomment once debugged and App layer is read to integrate
372         // Experimenting with this uncommented now ...
373         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
374         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
375     }
376     
377     protected String buildWhereForShortId(String name) {
378         return authorityCommonSchemaName
379                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
380                 + "='" + name + "'";
381     }
382     
383     private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
384         return true;
385     }
386     
387     private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
388         boolean result = true;
389         
390         ServiceContext ctx = this.getServiceContext();
391         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
392         String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
393         try {
394                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
395                         if (searchResultWrapper != null) {
396                                 result = false;
397                                 if (logger.isInfoEnabled() == true) {
398                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
399                                         String debugMsg = String.format("Could not create a new authority with a short identifier of '%s', because one already exists with the same short identifer: CSID = '%s'",
400                                                         shortIdentifier, searchResult.getName());
401                                         logger.trace(debugMsg);
402                                 }
403                         }
404                 } catch (DocumentNotFoundException e) {
405                         // Not a problem, just means we couldn't find another authority with that short ID
406                 }
407         
408         return result;
409     }
410
411     /**
412      * If no short identifier was provided in the input payload,
413      * generate a short identifier from the display name. Either way though,
414      * the short identifier needs to be unique.
415      */
416     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
417         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
418         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
419         String shortDisplayName = "";
420         String generateShortIdentifier = null;
421         if (Tools.isEmpty(shortIdentifier)) {
422                 generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
423             docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
424         }
425         
426         if (isUnique(docModel, schemaName) == false) {
427                 String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
428                 String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
429                 String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
430                                 errMsgVerb, shortId);
431                 throw new DocumentException(errMsg);
432         }
433     }
434  
435     /**
436      * Generate a refName for the authority from the short identifier
437      * and display name.
438      * 
439      * All refNames for authorities are generated.  If a client supplies
440      * a refName, it will be overwritten during create (per this method) 
441      * or discarded during update (per filterReadOnlyPropertiesForPart).
442      * 
443      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
444      * 
445      */
446     protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
447         DocumentModel docModel = wrapDoc.getWrappedObject();
448         RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
449         String refName = authority.toString();
450         docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
451     }
452     
453     @Override
454     public RefName.RefNameInterface getRefName(ServiceContext ctx,
455                 DocumentModel docModel) {
456         RefName.RefNameInterface refname = null;
457
458         try {
459                 String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
460                 String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
461                 RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
462                         ctx.getServiceName(),
463                         null,   // Only use shortId form!!!
464                         shortIdentifier,
465                         displayName);
466                 refname = authority;
467         } catch (Exception e) {
468                 logger.error(e.getMessage(), e);
469         }
470         
471         return refname;
472     }
473     
474     @Override
475     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
476         String result = null;
477         
478         DocumentModel docModel = docWrapper.getWrappedObject();
479         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
480         RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
481         result = refname.getDisplayName();
482         
483         return result;
484     }    
485     
486     public String getShortIdentifier(ServiceContext ctx, String authCSID, String schemaName) throws Exception {
487         String shortIdentifier = null;
488         CoreSessionInterface repoSession = null;
489         boolean releaseSession = false;
490
491         RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
492         try {
493                 repoSession = nuxeoRepoClient.getRepositorySession(ctx);
494             DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
495             DocumentModel docModel = wrapDoc.getWrappedObject();
496             if (docModel == null) {
497                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
498             }
499             shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
500         } catch (ClientException ce) {
501             throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
502         } finally {
503                 if (repoSession != null) {
504                         nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
505                 }
506         }
507         
508         return shortIdentifier;
509     }
510
511     /**
512      * Filters out selected values supplied in an update request.
513      * 
514      * @param objectProps the properties filtered out from the update payload
515      * @param partMeta metadata for the object to fill
516      */
517     @Override
518     public void filterReadOnlyPropertiesForPart(
519             Map<String, Object> objectProps, ObjectPartType partMeta) {
520         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
521         String commonPartLabel = getServiceContext().getCommonPartLabel();
522         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
523             objectProps.remove(AuthorityJAXBSchema.CSID);
524             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
525             objectProps.remove(AuthorityJAXBSchema.REF_NAME);
526         }
527     }    
528 }