2 * ===============LICENSE_START=======================================================
4 * ===================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property & Tech Mahindra. All rights reserved.
6 * ===================================================================================
7 * This Acumos software file is distributed by AT&T and Tech Mahindra
8 * under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * This file is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ===============LICENSE_END=========================================================
21 package org.acumos.federation.gateway.adapter;
23 import java.io.Closeable;
24 import java.lang.invoke.MethodHandles;
25 import java.time.Instant;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.List;
34 import javax.annotation.PostConstruct;
35 import javax.annotation.PreDestroy;
37 import org.acumos.cds.AccessTypeCode;
38 import org.acumos.cds.client.ICommonDataServiceRestClient;
39 import org.acumos.cds.domain.MLPPeer;
40 import org.acumos.cds.domain.MLPPeerSubscription;
41 import org.acumos.cds.domain.MLPRevisionDescription;
42 import org.acumos.cds.domain.MLPSolution;
43 import org.acumos.cds.domain.MLPSolutionRevision;
44 import org.acumos.cds.domain.MLPTag;
45 import org.acumos.federation.gateway.cds.Artifact;
46 import org.acumos.federation.gateway.cds.Document;
47 import org.acumos.federation.gateway.cds.PeerSubscription;
48 import org.acumos.federation.gateway.cds.Solution;
49 import org.acumos.federation.gateway.cds.SolutionRevision;
50 import org.acumos.federation.gateway.cds.SubscriptionScope;
51 import org.acumos.federation.gateway.cds.TimestampedEntity;
52 import org.acumos.federation.gateway.common.Clients;
53 import org.acumos.federation.gateway.common.FederationClient;
54 import org.acumos.federation.gateway.common.FederationException;
55 import org.acumos.federation.gateway.common.JsonResponse;
56 import org.acumos.federation.gateway.config.GatewayCondition;
57 import org.acumos.federation.gateway.event.PeerSubscriptionEvent;
58 import org.acumos.federation.gateway.service.CatalogService;
59 import org.acumos.federation.gateway.service.ContentService;
60 import org.acumos.federation.gateway.service.PeerSubscriptionService;
61 import org.acumos.federation.gateway.service.ServiceContext;
62 import org.acumos.federation.gateway.service.ServiceException;
63 import org.acumos.federation.gateway.util.Utils;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66 import org.springframework.beans.factory.BeanInitializationException;
67 import org.springframework.beans.factory.annotation.Autowired;
68 import org.springframework.beans.factory.annotation.Qualifier;
69 import org.springframework.context.annotation.Conditional;
70 import org.springframework.context.annotation.Scope;
71 import org.springframework.context.event.EventListener;
72 import org.springframework.core.env.Environment;
73 import org.springframework.core.io.Resource;
74 import org.springframework.core.task.TaskExecutor;
75 import org.springframework.stereotype.Component;
76 import org.springframework.web.client.HttpStatusCodeException;
79 @Component("peergateway")
81 @Conditional({GatewayCondition.class})
82 public class PeerGateway {
84 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
87 private TaskExecutor taskExecutor;
89 private Environment env;
91 private Clients clients;
93 private ContentService content;
95 private CatalogService catalog;
97 private PeerSubscriptionService peerSubscriptionService;
99 private static final String federationDotOperator = "federation.operator";
101 public PeerGateway() {
102 log.trace("PeerGateway::new");
106 public void initGateway() {
107 log.trace("initPeerGateway");
109 /* make sure an operator was specified and that it is a declared user */
110 if (null == this.env.getProperty(federationDotOperator)) {
111 throw new BeanInitializationException("Missing configuration key " + federationDotOperator);
115 if (null == this.clients.getCDSClient().getUser(this.env.getProperty(federationDotOperator))) {
116 log.warn(federationDotOperator +
117 " does not point to an existing user");
120 catch (/* HttpStatusCode */Exception dx) {
121 log.warn("failed to verify value " + federationDotOperator, dx);
126 log.debug("PeerGateway available");
130 public void cleanupGateway() {
131 log.debug("PeerGateway destroyed");
134 protected String getUserId(MLPPeerSubscription theSubscription/*
135 * , MLPSolution theSolution
137 String userId = theSubscription.getUserId();
138 return userId != null ? userId : this.env.getProperty(federationDotOperator);
142 public void handlePeerSubscriptionUpdate(PeerSubscriptionEvent theEvent) {
143 log.info("received peer subscription update event {}", theEvent);
144 taskExecutor.execute(
145 new PeerGatewayUpdateTask(theEvent.getPeer(), theEvent.getSubscription()));
149 * The list of solutions processed here represents the solutions (with respect
150 * to the subscription filter definition) that were reported by the peer as
151 * being updated since the last check.
153 public class PeerGatewayUpdateTask implements Runnable {
155 private MLPPeer peer;
156 private PeerSubscription sub;
158 public PeerGatewayUpdateTask(MLPPeer thePeer, MLPPeerSubscription theSub) {
160 this.sub = new PeerSubscription(theSub);
167 selector = Utils.jsonStringToMap(this.sub.getSelector());
170 log.error("Failed to parse selector for subscription {}", this.sub);
173 Instant lastProcessed = this.sub.getProcessed();
174 if (lastProcessed != null) {
175 selector.put("modified", lastProcessed.getEpochSecond());
177 lastProcessed = Instant.now();
179 FederationClient peerClient = clients.getFederationClient(this.peer.getApiUrl());
180 if (peerClient == null) {
181 log.error("Failed to get client for peer {}", this.peer);
185 JsonResponse<List<MLPSolution>> peerSolutionsResponse = null;
187 peerSolutionsResponse = peerClient.getSolutions(selector);
189 catch (FederationException fx) {
190 log.info("Processing peer " + this.peer + " subscription " + this.sub.getSubId() + " error.", fx);
194 List<MLPSolution> peerSolutions = peerSolutionsResponse.getContent();
195 log.info("Processing peer {} subscription {}, {} yielded solutions {}", this.peer, this.sub.getSubId(), selector, peerSolutions);
196 if (peerSolutions == null) {
197 log.warn("No solutions available for peer {} subscription {} in {}", this.peer, this.sub.getSubId(), peerSolutionsResponse);
198 peerSolutions = Collections.EMPTY_LIST;
199 //and let it proceed so we end up marking it as processed
202 ServiceContext ctx = catalog.selfService();
203 boolean isComplete = true;
205 for (MLPSolution peerSolution : peerSolutions) {
206 log.info("Processing peer solution {}", peerSolution);
209 isComplete &= mapSolution(peerSolution, peerClient, ctx);
211 catch (Throwable t) {
212 log.error("Mapping of acumos solution failed for " + peerSolution, t);
216 log.info("Processing of subscription {} completed succesfully: {}", this.sub, isComplete);
217 //only commit the last processed date if we completed succesfully
220 this.sub.setProcessed(lastProcessed);
221 peerSubscriptionService.updatePeerSubscription(this.sub);
223 catch (ServiceException sx) {
224 log.error("Failed to update subscription information", sx);
229 //this should go away once the move to service interface based operations is complete
230 //as ugly as they come
231 private ICommonDataServiceRestClient getCDSClient(ServiceContext theContext) {
232 return PeerGateway.this.clients.getCDSClient();
235 private Artifact copyArtifact(Artifact peerArtifact) {
236 return Artifact.buildFrom(peerArtifact)
237 .withUser(getUserId(this.sub))
238 .withCreated(TimestampedEntity.ORIGIN)
239 .withModified(TimestampedEntity.ORIGIN)
243 /* we create a new one as nothing is preserved. assumes matching ids. */
244 private Artifact copyArtifact(Artifact peerArtifact, Artifact localArtifact) {
245 return Artifact.buildFrom(peerArtifact)
246 .withId(localArtifact.getArtifactId())
247 .withUser(getUserId(this.sub))
251 private void putArtifact(String theSolutionId, String theRevisionId, Artifact theArtifact,
252 ServiceContext theContext) throws ServiceException {
254 assert(getCDSClient(theContext) != null);
257 if (theArtifact.getCreated() == Instant.MIN) {
258 getCDSClient(theContext).createArtifact(theArtifact);
259 getCDSClient(theContext).addSolutionRevisionArtifact(theSolutionId, theRevisionId, theArtifact.getArtifactId());
260 log.info("Local artifact created: {}", theArtifact);
263 getCDSClient(theContext).updateArtifact(theArtifact);
264 log.info("Local artifact updated: {}", theArtifact);
268 catch (HttpStatusCodeException restx) {
269 log.error("Artifact CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
270 throw new ServiceException("Artifact CDS call failed.", restx);
272 catch (Exception x) {
273 log.error("Artifact unexpected failure", x);
274 throw new ServiceException("Artifact CDS call failed.", x);
278 private Document copyDocument(Document peerDocument) {
279 return Document.buildFrom(peerDocument)
280 .withUser(getUserId(this.sub))
281 .withCreated(TimestampedEntity.ORIGIN)
282 .withModified(TimestampedEntity.ORIGIN)
286 private Document copyDocument(Document peerDocument, Document localDocument) {
287 return Document.buildFrom(peerDocument)
288 .withId(localDocument.getDocumentId())
289 .withUser(getUserId(this.sub))
293 private void putDocument(String theSolutionId, String theRevisionId, Document theDocument,
294 ServiceContext theContext) throws ServiceException {
297 if (theDocument.getCreated() == Instant.MIN) {
298 getCDSClient(theContext).createDocument(theDocument);
299 getCDSClient(theContext).addSolutionRevisionDocument(theRevisionId, AccessTypeCode.PB.name(), theDocument.getDocumentId());
300 log.info("Local document created: {}", theDocument);
303 getCDSClient(theContext).updateDocument(theDocument);
304 log.info("Local document updated: {}", theDocument);
307 catch (HttpStatusCodeException restx) {
308 log.error("Document CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
309 throw new ServiceException("Document CDS call failed.", restx);
311 catch (Exception x) {
312 log.error("Document handling unexpected failure", x);
313 throw new ServiceException("Document handling unexpected failure", x);
317 private MLPRevisionDescription copyRevisionDescription(MLPRevisionDescription peerDescription) {
318 MLPRevisionDescription localDescription = new MLPRevisionDescription(peerDescription);
319 localDescription.setCreated(TimestampedEntity.ORIGIN);
320 localDescription.setModified(TimestampedEntity.ORIGIN);
321 return localDescription;
324 private MLPRevisionDescription copyRevisionDescription(MLPRevisionDescription peerDescription, MLPRevisionDescription localDescription) {
325 localDescription.setDescription(peerDescription.getDescription());
326 return localDescription;
329 private void putRevisionDescription(MLPRevisionDescription theDescription,ServiceContext theContext) throws ServiceException {
332 if (theDescription.getCreated() == Instant.MIN) {
333 getCDSClient(theContext).createRevisionDescription(theDescription);
334 log.info("Local description created: {}", theDescription);
337 getCDSClient(theContext).updateRevisionDescription(theDescription);
340 catch (HttpStatusCodeException restx) {
341 log.error("Revision description CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
342 throw new ServiceException("Revision description CDS call failed.", restx);
344 catch (Exception x) {
345 log.error("Revision description handling unexpected failure", x);
346 throw new ServiceException("Revision description handling unexpected failure", x);
350 private boolean hasChanged(Artifact thePeerArtifact, Artifact theLocalArtifact) {
351 if (thePeerArtifact.getVersion() != null && theLocalArtifact.getVersion() != null) {
352 return !thePeerArtifact.getVersion().equals(theLocalArtifact.getVersion());
355 if (thePeerArtifact.getSize() != null && theLocalArtifact.getSize() != null) {
356 return !thePeerArtifact.getSize().equals(theLocalArtifact.getSize());
362 private boolean hasChanged(Document thePeerDoc, Document theLocalDoc) {
363 if (thePeerDoc.getVersion() != null && theLocalDoc.getVersion() != null) {
364 return !thePeerDoc.getVersion().equals(theLocalDoc.getVersion());
367 if (thePeerDoc.getSize() != null && theLocalDoc.getSize() != null) {
368 return !thePeerDoc.getSize().equals(theLocalDoc.getSize());
374 private Solution copySolution(Solution thePeerSolution) {
375 return Solution.buildFrom(thePeerSolution)
376 .withCreated(TimestampedEntity.ORIGIN)
377 .withModified(TimestampedEntity.ORIGIN)
378 .withUser(getUserId(this.sub))
379 .withSource(this.peer.getPeerId())
380 .withPicture(thePeerSolution.getPicture())
385 private Solution copySolution(Solution thePeerSolution, Solution theLocalSolution) {
386 String newUserId = getUserId(this.sub),
387 newSourceId = this.peer.getPeerId();
389 //some basic warnings
390 if (!theLocalSolution.getUserId().equals(newUserId)) {
391 // is this solution being updated as part of different/new subscription?
392 log.warn("Updating solution {} triggers a user change", theLocalSolution.getSolutionId());
393 //but make the change anyway
394 theLocalSolution.setUserId(newUserId);
397 if (theLocalSolution.getSourceId() == null) {
398 //this is a local solution that made its way back
399 log.info("Solution {} was originally provisioned locally, avoiding user update", theLocalSolution.getSolutionId());
402 if (!theLocalSolution.getSourceId().equals(newSourceId)) {
403 // we will see this if a solution is available in more than one peer
404 log.warn("Solution {} triggers a source change", theLocalSolution.getSolutionId());
405 //but make the change anyway
406 theLocalSolution.setSourceId(newSourceId);
410 theLocalSolution.setPicture(thePeerSolution.getPicture());
411 //tags, keep only the delta
412 Set<MLPTag> tags = thePeerSolution.getTags();
413 tags.removeAll(theLocalSolution.getTags());
414 theLocalSolution.setTags(tags);
416 return theLocalSolution;
419 private boolean hasChanged(Solution thePeerSolution, Solution theLocalSolution) {
420 if (!Arrays.equals(theLocalSolution.getPicture(), thePeerSolution.getPicture())) {
423 if (!theLocalSolution.getTags().containsAll(thePeerSolution.getTags()))
430 * Here comes the core process of updating a local solution's related
431 * information with what is available from a peer.
434 * the local solution who's related information (revisions and
435 * artifacts) we are trying to sync
436 * @param thePeerClient
439 * the context in which we perform the catalog operations
440 * @return true if mapping was succesful, false otherwise
442 * any error related to CDS and peer interaction
444 protected boolean mapSolution(MLPSolution theSolution, FederationClient thePeerClient, ServiceContext theContext) throws Exception {
446 boolean isComplete = true,
447 isSolutionNew = false,
448 hasSolutionChanged = false;
450 Solution localSolution = null,
453 //retrieve the full representation from the peer
454 JsonResponse<MLPSolution> peerSolutionResponse = null;
456 peerSolutionResponse = thePeerClient.getSolution(theSolution.getSolutionId());
458 catch (FederationException fx) {
459 log.warn("Failed to retrieve peer solution details for " + theSolution, fx);
463 peerSolution = (Solution)peerSolutionResponse.getContent();
464 if (peerSolution == null) {
465 log.warn("No solution details available for {} in {}", theSolution, peerSolutionResponse);
469 localSolution = catalog.getSolution(peerSolution.getSolutionId());
470 if (localSolution == null) {
471 localSolution = catalog.putSolution(copySolution(peerSolution), theContext);
472 isSolutionNew = true;
475 hasSolutionChanged = hasChanged(peerSolution, localSolution);
478 List<MLPSolutionRevision> peerRevisions = (List)peerSolution.getRevisions();
479 Collections.sort(peerRevisions, (arev, brev) -> arev.getModified().compareTo(brev.getModified()));
481 // this should not happen as any solution should have at least one
482 // revision (but that's an assumption on how on-boarding works)
483 if (peerRevisions == null || peerRevisions.size() == 0) {
484 log.warn("No peer revisions were retrieved");
488 // check if we have locally the latest revision available on the peer
489 List<MLPSolutionRevision> catalogRevisions = (List)localSolution.getRevisions();
490 final List<MLPSolutionRevision> localRevisions = catalogRevisions == null ? Collections.EMPTY_LIST : catalogRevisions;
492 // map peer revisions to local ones; new peer revisions have a null mapping
493 Map<MLPSolutionRevision, MLPSolutionRevision> peerToLocalRevisions =
494 new LinkedHashMap<MLPSolutionRevision, MLPSolutionRevision>();
495 peerRevisions.forEach(peerRevision -> peerToLocalRevisions.put(peerRevision,
496 localRevisions.stream()
497 .filter(localRevision -> localRevision.getRevisionId().equals(peerRevision.getRevisionId()))
498 .findFirst().orElse(null)));
500 for (Map.Entry<MLPSolutionRevision, MLPSolutionRevision> revisionEntry : peerToLocalRevisions.entrySet()) {
501 MLPSolutionRevision peerRevision = revisionEntry.getKey(), localRevision = revisionEntry.getValue();
503 boolean isRevisionNew = false,
504 hasRevisionChanged = false;
506 //revision related information (artifacts/documents/description/..) is now embedded in the revision details
507 //federation api call so one call is all is needed
508 JsonResponse<MLPSolutionRevision> peerRevisionResponse = null;
510 peerRevisionResponse = thePeerClient.getSolutionRevision(peerSolution.getSolutionId(), peerRevision.getRevisionId());
512 catch (FederationException fx) {
513 isComplete = false; //try the next revision but mark the overall processing as incomplete
517 peerRevision = peerRevisionResponse.getContent();
518 if (peerRevision == null) {
519 isComplete = false; //try the next revision but mark the overall processing as incomplete
523 if (localRevision == null) {
525 localRevision = catalog.putSolutionRevision(
526 SolutionRevision.buildFrom(peerRevision)
527 .withCreated(TimestampedEntity.ORIGIN)
528 .withModified(TimestampedEntity.ORIGIN)
529 .withUser(getUserId(this.sub))
530 .withSource(this.peer.getPeerId())
531 .withAccessTypeCode(this.sub.getAccessType())
532 .build(), theContext);
534 catch (ServiceException sx) {
535 log.error("Failed to put revision " + theSolution.getSolutionId() + "/" + peerRevision.getRevisionId() + " into catalog", sx);
536 isComplete = false; //try procecessing the next revision but mark the processing as incomplete
539 isRevisionNew = true;
542 List<Artifact> peerArtifacts = (List)((SolutionRevision)peerRevision).getArtifacts();
543 List<Document> peerDocuments = (List)((SolutionRevision)peerRevision).getDocuments();
545 List<Artifact> catalogArtifacts = (List)((SolutionRevision)localRevision).getArtifacts();
546 List<Document> catalogDocuments = (List)((SolutionRevision)localRevision).getDocuments();
548 final List<Artifact> localArtifacts = catalogArtifacts;
550 // TODO: track deleted artifacts
551 Map<Artifact, Artifact> peerToLocalArtifacts = new HashMap<Artifact, Artifact>();
552 peerArtifacts.forEach(peerArtifact -> peerToLocalArtifacts.put(peerArtifact, localArtifacts.stream()
553 .filter(localArtifact -> localArtifact.getArtifactId().equals(peerArtifact.getArtifactId()))
554 .findFirst().orElse(null)));
556 for (Map.Entry<Artifact, Artifact> artifactEntry : peerToLocalArtifacts.entrySet()) {
557 Artifact peerArtifact = artifactEntry.getKey(),
558 localArtifact = artifactEntry.getValue();
559 boolean doCatalog = false;
561 log.info("Processing peer artifact {} against local artifact {}", peerArtifact, localArtifact);
563 if (localArtifact == null) {
564 localArtifact = copyArtifact(peerArtifact);
568 if (hasChanged(peerArtifact, localArtifact)) {
569 // update local artifact
570 localArtifact = copyArtifact(peerArtifact, localArtifact);
575 boolean doContent = doCatalog &&
576 (peerArtifact.getUri() != null) &&
577 (SubscriptionScope.Full == SubscriptionScope.forCode(this.sub.getScopeType()));
579 log.info("Processing content for artifact {}", peerArtifact);
580 // TODO: we are trying to access the artifact by its identifier which
581 // is fine in the common case but the uri specified in the artifact
582 // data is the right approach (as it does not rely on the E5 definition).
583 Resource artifactContent = null;
585 artifactContent = thePeerClient.getArtifactContent(
586 peerSolution.getSolutionId(), peerRevision.getRevisionId(), peerArtifact.getArtifactId());
587 log.info("Received {} bytes of artifact content", artifactContent.contentLength());
589 catch (FederationException x) {
590 log.error("Failed to retrieve acumos artifact content", x);
591 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
595 if (artifactContent != null) {
597 content.putArtifactContent(
598 localSolution.getSolutionId(), localRevision.getRevisionId(), localArtifact, artifactContent);
601 catch (ServiceException sx) {
602 log.error("Failed to store artifact content to local repo", sx);
603 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
607 if (artifactContent instanceof Closeable) {
608 ((Closeable)artifactContent).close();
616 putArtifact(localSolution.getSolutionId(), localRevision.getRevisionId(), localArtifact, theContext);
618 catch (ServiceException sx) {
619 log.error("Artifact processing failed.", sx);
622 hasRevisionChanged = true;
624 } //end map artifacts loop
627 final List<Document> localDocuments = catalogDocuments;
629 // TODO: track deleted documents
630 Map<Document, Document> peerToLocalDocuments = new HashMap<Document, Document>();
631 peerDocuments.forEach(peerDocument -> peerToLocalDocuments.put(peerDocument, localDocuments.stream()
632 .filter(localDocument -> localDocument.getDocumentId().equals(peerDocument.getDocumentId()))
633 .findFirst().orElse(null)));
635 for (Map.Entry<Document, Document> documentEntry : peerToLocalDocuments.entrySet()) {
636 Document peerDocument = documentEntry.getKey(),
637 localDocument = documentEntry.getValue();
638 boolean doCatalog = false;
640 log.info("Processing peer document {} against local version {}", peerDocument, localDocument);
641 if (localDocument == null) {
642 localDocument = copyDocument(peerDocument);
646 //version strings are not standard so comparing them is not necessarly safe
647 if (hasChanged(peerDocument, localDocument)) {
649 localDocument = copyDocument(peerDocument, localDocument);
654 boolean doContent = doCatalog &&
655 (peerDocument.getUri() != null) &&
656 (SubscriptionScope.Full == SubscriptionScope.forCode(this.sub.getScopeType()));
658 log.info("Processing content for document {}", peerDocument);
659 // TODO: we are trying to access the document by its identifier which
660 // is fine in the common case but the uri specified in the document
661 // data is a more flexible approach.
662 Resource documentContent = null;
664 documentContent = thePeerClient.getDocumentContent(
665 peerSolution.getSolutionId(), peerRevision.getRevisionId(), peerDocument.getDocumentId());
666 log.info("Received {} bytes of document content", documentContent.contentLength());
668 catch (FederationException x) {
669 log.error("Failed to retrieve acumos document content", x);
670 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
674 if (documentContent != null) {
676 content.putDocumentContent(
677 localSolution.getSolutionId(), localRevision.getRevisionId(), localDocument, documentContent);
680 catch (ServiceException sx) {
681 log.error("Failed to store document content to local repo", sx);
682 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
690 putDocument(localSolution.getSolutionId(), localRevision.getRevisionId(), localDocument, theContext);
692 catch (ServiceException sx) {
693 log.error("Document processing failed", sx);
696 hasRevisionChanged = true;
699 } // end map documents loop
701 MLPRevisionDescription localDescription = ((SolutionRevision)localRevision).getRevisionDescription();
702 MLPRevisionDescription peerDescription = ((SolutionRevision)peerRevision).getRevisionDescription();
704 if (peerDescription != null) {
705 boolean doCatalog = false;
707 if (localDescription == null) {
708 localDescription = copyRevisionDescription(peerDescription);
712 //is this a good enough test ?? it implies time sync ..
713 if (peerDescription.getModified().isAfter(localDescription.getModified())) {
714 localDescription = copyRevisionDescription(peerDescription, localDescription);
721 putRevisionDescription(localDescription, theContext);
723 catch (ServiceException sx) {
724 log.error("Description processing failed", sx);
727 hasRevisionChanged = true;
729 } //end revision processing
731 if (!isRevisionNew && hasRevisionChanged) {
733 //we do not actually update any properties, just give CDS a chance to update the timestamps as to mark it as updated.
734 catalog.putSolutionRevision(SolutionRevision.buildFrom(localRevision).build(),
737 catch (ServiceException sx) {
738 log.error("Failed to update local revision", sx);
743 hasSolutionChanged |= (isRevisionNew || hasRevisionChanged);
744 } //end revisions processing
746 if (!isSolutionNew && hasSolutionChanged) {
748 catalog.putSolution(copySolution(peerSolution, localSolution), theContext);
750 catch (ServiceException sx) {
751 log.error("Failed to update local solution", sx);