]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
c3d634009e62a199dd0ef7aa06e3928be268f031
[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.DocumentNotFoundException;
48 import org.collectionspace.services.common.document.DocumentWrapper;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
51 import org.collectionspace.services.common.vocabulary.AuthorityResource;
52 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
53 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
54 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
55 import org.collectionspace.services.config.service.ObjectPartType;
56 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
57 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
58 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
59 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
60 import org.dom4j.Document;
61 import org.dom4j.Element;
62 import org.nuxeo.ecm.core.api.ClientException;
63 import org.nuxeo.ecm.core.api.DocumentModel;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 /**
68  * AuthorityDocumentModelHandler
69  *
70  * $LastChangedRevision: $
71  * $LastChangedDate: $
72  */
73 public abstract class AuthorityDocumentModelHandler<AuthCommon>
74         extends NuxeoDocumentModelHandler<AuthCommon> {
75
76     private final Logger logger = LoggerFactory.getLogger(AuthorityDocumentModelHandler.class); 
77     protected String authorityCommonSchemaName;
78     protected String authorityItemCommonSchemaName;
79
80     public AuthorityDocumentModelHandler(String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
81         this.authorityCommonSchemaName = authorityCommonSchemaName;
82         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
83     }
84     
85     /**
86      * The entity type expected from the JAX-RS Response object
87      */
88     public Class<String> getEntityResponseType() {
89         return String.class;
90     }
91         
92     protected PayloadInputPart extractPart(Response res, String partLabel)
93             throws Exception {
94             PoxPayloadIn input = new PoxPayloadIn((String)res.readEntity(getEntityResponseType()));
95             PayloadInputPart payloadInputPart = input.getPart(partLabel);
96             if (payloadInputPart == null) {
97                 logger.error("Part " + partLabel + " was unexpectedly null.");
98             }
99             return payloadInputPart;
100     }
101     
102     @Override
103     public boolean handleSync(DocumentWrapper<Object> wrapDoc) throws Exception {
104         boolean result = false;
105         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
106         Specifier specifier = (Specifier) wrapDoc.getWrappedObject();
107         //
108         // Get the rev number of the authority so we can compare with rev number of shared authority
109         //
110         DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, specifier);
111         Long rev = (Long) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REV);
112         String shortId = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.SHORT_IDENTIFIER);
113         String refName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REF_NAME);
114         //
115         // Using the short ID of the local authority, create a URN specifier to retrieve the SAS authority
116         //
117         Specifier sasSpecifier = new Specifier(SpecifierForm.URN_NAME, RefNameUtils.createShortIdRefName(shortId));
118         PoxPayloadIn sasPayloadIn = getPayloadIn(ctx, sasSpecifier);
119
120         Long sasRev = getRevision(sasPayloadIn);
121         if (sasRev > rev) {
122                 syncAllItems(ctx, sasSpecifier);
123                 ResourceMap resourceMap = ctx.getResourceMap();
124                 String resourceName = ctx.getClient().getServiceName();
125                 AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
126                 PoxPayloadOut payloadOut = authorityResource.update(ctx, resourceMap, null, docModel.getName(), sasPayloadIn);
127                 if (payloadOut != null) {
128                         ctx.setOutput(payloadOut);
129                         result = true;
130                 }
131         }
132         
133         return result;
134     }
135     
136     /*
137      * Get the list of authority items from the remote shared authority server (SAS) and try
138      * to synchronize them with the local items.  If items exist on the remote but not the local, we'll create them.
139      */
140     protected int syncAllItems(ServiceContext ctx, Specifier sasSpecifier) throws Exception {
141         int result = -1;
142         int created = 0;
143         int synched = 0;
144         int alreadySynched = 0;
145         int totalItemsProcessed = 0;
146         
147         PoxPayloadIn sasPayloadInItemList = getPayloadInItemList(ctx, sasSpecifier);
148         List<Element> itemList = getItemList(sasPayloadInItemList);
149         if (itemList != null) {
150                 for (Element e:itemList) {
151                         String remoteRefName = XmlTools.getElementValue(e, "//refName");
152                         long status = syncRemoteItemWithLocalItem(ctx, remoteRefName);
153                         if (status == 1) {
154                                 created++;
155                         } else if (status == 0) {
156                                 synched++;
157                         } else {
158                                 alreadySynched++;
159                         }
160                         totalItemsProcessed++;
161                 }
162         }
163         
164         logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed));
165         logger.info(String.format("Number of items synchronized: %d", synched));
166         logger.info(String.format("Number of items created during sync: %d", created));
167         logger.info(String.format("Number not needing synchronization: %d", alreadySynched));
168
169         return result;
170     }
171     
172     /**
173      * 
174      * @param ctx
175      * @param parentIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
176      * @param itemIdentifier   - Must be in short-id-refname form -i.e., urn:cspace:name(shortid)
177      * @throws Exception 
178      */
179     protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier) throws Exception {
180         //
181         // Create a URN short ID specifier for the getting to the remote item payload
182         Specifier authoritySpecifier = new Specifier(SpecifierForm.URN_NAME, parentIdentifier);
183         Specifier itemSpecifier = new Specifier(SpecifierForm.URN_NAME, itemIdentifier);
184         AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier);
185         //
186         // Get the remote payload
187         //
188         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.getPayloadIn(sasAuthorityItemSpecifier, 
189                         ctx.getServiceName(), getEntityResponseType());
190         //
191         // Using the payload from the remote server, create a local copy of the item
192         //
193         ResourceMap resourceMap = ctx.getResourceMap();
194         String resourceName = ctx.getClient().getServiceName();
195         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
196         Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.value, sasPayloadIn);
197         if (response.getStatus() != Response.Status.CREATED.getStatusCode()) {
198                 throw new DocumentException(String.format("Could not create new authority item '%s' during synchronization of the '%s' authority.",
199                                 itemIdentifier, parentIdentifier));
200         }
201     }
202     
203     /**
204      * Try to synchronize a remote item (using its refName) with a local item.  If the local doesn't yet
205      * exist, we'll create it.
206      * Result values:
207      *  -1 = sync not needed; i.e., already in sync
208      *   0 = sync succeeded
209      *   1 = local item was missing so we created it
210      * @param ctx
211      * @param refName
212      * @return
213      * @throws Exception
214      */
215     protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String remoteRefName) throws Exception {
216         long result = -1;
217         
218         AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(remoteRefName);
219         String parentIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.inAuthority.name);
220         String itemIdentifier = RefNameUtils.createShortIdRefName(authorityTermInfo.name);
221
222         ResourceMap resourceMap = ctx.getResourceMap();
223         String resourceName = ctx.getClient().getServiceName();
224         AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName);
225         
226         PoxPayloadOut localItemPayloadOut;
227         try {
228                 localItemPayloadOut = authorityResource.getAuthorityItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
229         } catch (DocumentNotFoundException dnf) {
230                 logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", remoteRefName));
231                 createLocalItem(ctx, parentIdentifier, itemIdentifier);
232                 return 1; // exit with status of 1 means we created a new authority item
233         }
234         //
235         // If we get here, we know the item exists both locally and remotely, so we need to synchronize them
236         //
237         authorityResource.synchronizeItemWithParentContext(ctx, parentIdentifier, itemIdentifier);
238         result = 0;
239         
240         return result;
241     }
242         
243     private PoxPayloadIn getPayloadInItemList(ServiceContext ctx, Specifier specifier) throws Exception {
244         PoxPayloadIn result = null;
245         
246         AuthorityClient client = (AuthorityClient) ctx.getClient();
247         Response res = client.readItemList(specifier.value,
248                         null,   // partial term string
249                         null    // keyword string
250                         );
251         try {
252                 int statusCode = res.getStatus();
253         
254                 // Check the status code of the response: does it match
255                 // the expected response(s)?
256                 if (logger.isDebugEnabled()) {
257                     logger.debug(client.getClass().getCanonicalName() + ": status = " + statusCode);
258                 }
259                 
260             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response!             
261         } finally {
262                 res.close();
263         }
264         
265         return result;
266     }
267     
268     private PoxPayloadIn getPayloadIn(ServiceContext ctx, Specifier specifier) throws Exception {
269         PoxPayloadIn result = null;
270         
271         AuthorityClient client = (AuthorityClient) ctx.getClient();
272         Response res = client.read(specifier.value);
273         try {
274                 int statusCode = res.getStatus();
275         
276                 // Check the status code of the response: does it match
277                 // the expected response(s)?
278                 if (logger.isDebugEnabled()) {
279                     logger.debug(client.getClass().getCanonicalName() + ": status = " + statusCode);
280                 }
281                 
282             result = new PoxPayloadIn((String)res.readEntity(getEntityResponseType())); // Get the entire response!             
283         } finally {
284                 res.close();
285         }
286         
287         return result;
288     }
289
290     /*
291      * Non standard injection of CSID into common part, since caller may access through
292      * shortId, and not know the CSID.
293      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
294      */
295     @Override
296     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
297             throws Exception {
298         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
299
300         // Add the CSID to the common part
301         if (partMeta.getLabel().equalsIgnoreCase(authorityCommonSchemaName)) {
302             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
303             unQObjectProperties.put("csid", csid);
304         }
305
306         return unQObjectProperties;
307     }
308     
309     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
310         super.fillAllParts(wrapDoc, action);
311         //
312         // Update the record's revision number on both CREATE and UPDATE actions
313         //
314         updateRevNumbers(wrapDoc);
315     }
316     
317     protected void updateRevNumbers(DocumentWrapper<DocumentModel> wrapDoc) {
318         DocumentModel documentModel = wrapDoc.getWrappedObject();
319         Long rev = (Long)documentModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV);
320         if (rev == null) {
321                 rev = (long)0;
322         } else {
323                 rev++;
324         }
325         documentModel.setProperty(authorityCommonSchemaName, AuthorityJAXBSchema.REV, rev);
326     }
327     
328     @Override
329     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
330         super.handleCreate(wrapDoc);
331         // CSPACE-3178:
332         // Uncomment once debugged and App layer is read to integrate
333         // Experimenting with this uncommented now ...
334         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
335         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
336     }
337     
338     protected String buildWhereForShortId(String name) {
339         return authorityCommonSchemaName
340                 + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
341                 + "='" + name + "'";
342     }
343     
344     private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
345         return true;
346     }
347     
348     private boolean temp_isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
349         boolean result = true;
350         
351         ServiceContext ctx = this.getServiceContext();
352         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
353         String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
354         try {
355                         DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
356                         if (searchResultWrapper != null) {
357                                 result = false;
358                                 if (logger.isInfoEnabled() == true) {
359                                         DocumentModel searchResult = searchResultWrapper.getWrappedObject();
360                                         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'",
361                                                         shortIdentifier, searchResult.getName());
362                                         logger.trace(debugMsg);
363                                 }
364                         }
365                 } catch (DocumentNotFoundException e) {
366                         // Not a problem, just means we couldn't find another authority with that short ID
367                 }
368         
369         return result;
370     }
371
372     /**
373      * If no short identifier was provided in the input payload,
374      * generate a short identifier from the display name. Either way though,
375      * the short identifier needs to be unique.
376      */
377     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
378         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
379         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
380         String shortDisplayName = "";
381         String generateShortIdentifier = null;
382         if (Tools.isEmpty(shortIdentifier)) {
383                 generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
384             docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
385         }
386         
387         if (isUnique(docModel, schemaName) == false) {
388                 String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
389                 String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
390                 String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
391                                 errMsgVerb, shortId);
392                 throw new DocumentException(errMsg);
393         }
394     }
395  
396     /**
397      * Generate a refName for the authority from the short identifier
398      * and display name.
399      * 
400      * All refNames for authorities are generated.  If a client supplies
401      * a refName, it will be overwritten during create (per this method) 
402      * or discarded during update (per filterReadOnlyPropertiesForPart).
403      * 
404      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
405      * 
406      */
407     protected void updateRefnameForAuthority(DocumentWrapper<DocumentModel> wrapDoc, String schemaName) throws Exception {
408         DocumentModel docModel = wrapDoc.getWrappedObject();
409         RefName.Authority authority = (Authority) getRefName(getServiceContext(), docModel);
410         String refName = authority.toString();
411         docModel.setProperty(schemaName, AuthorityJAXBSchema.REF_NAME, refName);
412     }
413     
414     @Override
415     public RefName.RefNameInterface getRefName(ServiceContext ctx,
416                 DocumentModel docModel) {
417         RefName.RefNameInterface refname = null;
418
419         try {
420                 String shortIdentifier = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
421                 String displayName = (String) docModel.getProperty(authorityCommonSchemaName, AuthorityJAXBSchema.DISPLAY_NAME);
422                 RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
423                         ctx.getServiceName(),
424                         null,   // Only use shortId form!!!
425                         shortIdentifier,
426                         displayName);
427                 refname = authority;
428         } catch (Exception e) {
429                 logger.error(e.getMessage(), e);
430         }
431         
432         return refname;
433     }
434     
435     @Override
436     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
437         String result = null;
438         
439         DocumentModel docModel = docWrapper.getWrappedObject();
440         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
441         RefName.Authority refname = (RefName.Authority)getRefName(ctx, docModel);
442         result = refname.getDisplayName();
443         
444         return result;
445     }    
446     
447     public String getShortIdentifier(String authCSID, String schemaName) throws Exception {
448         String shortIdentifier = null;
449         CoreSessionInterface repoSession = null;
450
451         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
452         RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
453         try {
454                 repoSession = nuxeoRepoClient.getRepositorySession(ctx);
455             DocumentWrapper<DocumentModel> wrapDoc = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, authCSID);
456             DocumentModel docModel = wrapDoc.getWrappedObject();
457             if (docModel == null) {
458                 throw new DocumentNotFoundException(String.format("Could not find authority resource with CSID='%s'.", authCSID));
459             }
460             shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
461         } catch (ClientException ce) {
462             throw new RuntimeException("AuthorityDocHandler Internal Error: cannot get shortId!", ce);
463         } finally {
464                 if (repoSession != null) {
465                         nuxeoRepoClient.releaseRepositorySession(ctx, repoSession);
466                 }
467         }
468         
469         return shortIdentifier;
470     }
471
472     /**
473      * Filters out selected values supplied in an update request.
474      * 
475      * @param objectProps the properties filtered out from the update payload
476      * @param partMeta metadata for the object to fill
477      */
478     @Override
479     public void filterReadOnlyPropertiesForPart(
480             Map<String, Object> objectProps, ObjectPartType partMeta) {
481         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
482         String commonPartLabel = getServiceContext().getCommonPartLabel();
483         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
484             objectProps.remove(AuthorityJAXBSchema.CSID);
485             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
486             objectProps.remove(AuthorityJAXBSchema.REF_NAME);
487         }
488     }    
489 }