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.Collections;
27 import java.util.HashMap;
28 import java.util.LinkedHashMap;
29 import java.util.List;
33 import javax.annotation.PostConstruct;
34 import javax.annotation.PreDestroy;
36 import org.acumos.cds.AccessTypeCode;
37 import org.acumos.cds.client.ICommonDataServiceRestClient;
38 import org.acumos.cds.domain.MLPPeer;
39 import org.acumos.cds.domain.MLPPeerSubscription;
40 import org.acumos.cds.domain.MLPRevisionDescription;
41 import org.acumos.cds.domain.MLPSolution;
42 import org.acumos.cds.domain.MLPSolutionRevision;
43 import org.acumos.cds.domain.MLPTag;
44 import org.acumos.federation.gateway.cds.Artifact;
45 import org.acumos.federation.gateway.cds.Document;
46 import org.acumos.federation.gateway.cds.PeerSubscription;
47 import org.acumos.federation.gateway.cds.Solution;
48 import org.acumos.federation.gateway.cds.SolutionRevision;
49 import org.acumos.federation.gateway.cds.SubscriptionScope;
50 import org.acumos.federation.gateway.cds.TimestampedEntity;
51 import org.acumos.federation.gateway.common.Clients;
52 import org.acumos.federation.gateway.common.FederationClient;
53 import org.acumos.federation.gateway.common.FederationException;
54 import org.acumos.federation.gateway.common.JsonResponse;
55 import org.acumos.federation.gateway.config.GatewayCondition;
56 import org.acumos.federation.gateway.event.PeerSubscriptionEvent;
57 import org.acumos.federation.gateway.service.CatalogService;
58 import org.acumos.federation.gateway.service.ContentService;
59 import org.acumos.federation.gateway.service.PeerSubscriptionService;
60 import org.acumos.federation.gateway.service.ServiceContext;
61 import org.acumos.federation.gateway.service.ServiceException;
62 import org.acumos.federation.gateway.util.Utils;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65 import org.springframework.beans.factory.BeanInitializationException;
66 import org.springframework.beans.factory.annotation.Autowired;
67 import org.springframework.beans.factory.annotation.Qualifier;
68 import org.springframework.context.annotation.Conditional;
69 import org.springframework.context.annotation.Scope;
70 import org.springframework.context.event.EventListener;
71 import org.springframework.core.env.Environment;
72 import org.springframework.core.io.Resource;
73 import org.springframework.core.task.TaskExecutor;
74 import org.springframework.stereotype.Component;
75 import org.springframework.web.client.HttpStatusCodeException;
78 @Component("peergateway")
80 @Conditional({GatewayCondition.class})
81 public class PeerGateway {
83 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
86 private TaskExecutor taskExecutor;
88 private Environment env;
90 private Clients clients;
92 private ContentService content;
94 private CatalogService catalog;
96 private PeerSubscriptionService peerSubscriptionService;
98 private static final String federationDotOperator = "federation.operator";
100 public PeerGateway() {
101 log.trace("PeerGateway::new");
105 public void initGateway() {
106 log.trace("initPeerGateway");
108 /* make sure an operator was specified and that it is a declared user */
109 if (null == this.env.getProperty(federationDotOperator)) {
110 throw new BeanInitializationException("Missing configuration key " + federationDotOperator);
114 if (null == this.clients.getCDSClient().getUser(this.env.getProperty(federationDotOperator))) {
115 log.warn(federationDotOperator +
116 " does not point to an existing user");
119 catch (/* HttpStatusCode */Exception dx) {
120 log.warn("failed to verify value " + federationDotOperator, dx);
125 log.debug("PeerGateway available");
129 public void cleanupGateway() {
130 log.debug("PeerGateway destroyed");
133 protected String getUserId(MLPPeerSubscription theSubscription/*
134 * , MLPSolution theSolution
136 String userId = theSubscription.getUserId();
137 return userId != null ? userId : this.env.getProperty(federationDotOperator);
141 public void handlePeerSubscriptionUpdate(PeerSubscriptionEvent theEvent) {
142 log.info("received peer subscription update event {}", theEvent);
143 taskExecutor.execute(
144 new PeerGatewayUpdateTask(theEvent.getPeer(), theEvent.getSubscription()));
148 * The list of solutions processed here represents the solutions (with respect
149 * to the subscription filter definition) that were reported by the peer as
150 * being updated since the last check.
152 public class PeerGatewayUpdateTask implements Runnable {
154 private MLPPeer peer;
155 private PeerSubscription sub;
157 public PeerGatewayUpdateTask(MLPPeer thePeer, MLPPeerSubscription theSub) {
159 this.sub = new PeerSubscription(theSub);
166 selector = Utils.jsonStringToMap(this.sub.getSelector());
169 log.error("Failed to parse selector for subscription {}", this.sub);
172 Instant lastProcessed = this.sub.getProcessed();
173 if (lastProcessed != null) {
174 selector.put("modified", lastProcessed);
176 lastProcessed = Instant.now();
178 FederationClient peerClient = clients.getFederationClient(this.peer.getApiUrl());
179 if (peerClient == null) {
180 log.error("Failed to get client for peer {}", this.peer);
184 JsonResponse<List<MLPSolution>> peerSolutionsResponse = null;
186 peerSolutionsResponse = peerClient.getSolutions(selector);
188 catch (FederationException fx) {
189 log.info("Processing peer " + this.peer + " subscription " + this.sub.getSubId() + " error.", fx);
193 List<MLPSolution> peerSolutions = peerSolutionsResponse.getContent();
194 log.info("Processing peer {} subscription {}, {} yielded solutions {}", this.peer, this.sub.getSubId(), selector, peerSolutions);
195 if (peerSolutions == null) {
196 log.warn("No solutions available for peer {} subscription {} in {}", this.peer, this.sub.getSubId(), peerSolutionsResponse);
197 peerSolutions = Collections.EMPTY_LIST;
198 //and let it proceed so we end up marking it as processed
201 ServiceContext ctx = catalog.selfService();
202 boolean isComplete = true;
204 for (MLPSolution peerSolution : peerSolutions) {
205 log.info("Processing peer solution {}", peerSolution);
208 isComplete &= mapSolution(peerSolution, peerClient, ctx);
210 catch (Throwable t) {
211 log.error("Mapping of acumos solution failed for " + peerSolution, t);
215 log.info("Processing of subscription {} completed succesfully: {}", this.sub, isComplete);
216 //only commit the last processed date if we completed succesfully
219 this.sub.setProcessed(lastProcessed);
220 peerSubscriptionService.updatePeerSubscription(this.sub);
222 catch (ServiceException sx) {
223 log.error("Failed to update subscription information", sx);
228 //this should go away once the move to service interface based operations is complete
229 //as ugly as they come
230 private ICommonDataServiceRestClient getCDSClient(ServiceContext theContext) {
231 return PeerGateway.this.clients.getCDSClient();
234 private Artifact copyArtifact(Artifact peerArtifact) {
235 return Artifact.buildFrom(peerArtifact)
236 .withUser(getUserId(this.sub))
237 .withCreated(TimestampedEntity.ORIGIN)
238 .withModified(TimestampedEntity.ORIGIN)
242 /* we create a new one as nothing is preserved. assumes matching ids. */
243 private Artifact copyArtifact(Artifact peerArtifact, Artifact localArtifact) {
244 return Artifact.buildFrom(peerArtifact)
245 .withId(localArtifact.getArtifactId())
246 .withUser(getUserId(this.sub))
250 private void putArtifact(String theSolutionId, String theRevisionId, Artifact theArtifact,
251 ServiceContext theContext) throws ServiceException {
253 assert(getCDSClient(theContext) != null);
256 if (theArtifact.getCreated() == Instant.MIN) {
257 getCDSClient(theContext).createArtifact(theArtifact);
258 getCDSClient(theContext).addSolutionRevisionArtifact(theSolutionId, theRevisionId, theArtifact.getArtifactId());
259 log.info("Local artifact created: {}", theArtifact);
262 getCDSClient(theContext).updateArtifact(theArtifact);
263 log.info("Local artifact updated: {}", theArtifact);
267 catch (HttpStatusCodeException restx) {
268 log.error("Artifact CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
269 throw new ServiceException("Artifact CDS call failed.", restx);
271 catch (Exception x) {
272 log.error("Artifact unexpected failure", x);
273 throw new ServiceException("Artifact CDS call failed.", x);
277 private Document copyDocument(Document peerDocument) {
278 return Document.buildFrom(peerDocument)
279 .withUser(getUserId(this.sub))
280 .withCreated(TimestampedEntity.ORIGIN)
281 .withModified(TimestampedEntity.ORIGIN)
285 private Document copyDocument(Document peerDocument, Document localDocument) {
286 return Document.buildFrom(peerDocument)
287 .withId(localDocument.getDocumentId())
288 .withUser(getUserId(this.sub))
292 private void putDocument(String theSolutionId, String theRevisionId, Document theDocument,
293 ServiceContext theContext) throws ServiceException {
296 if (theDocument.getCreated() == Instant.MIN) {
297 getCDSClient(theContext).createDocument(theDocument);
298 getCDSClient(theContext).addSolutionRevisionDocument(theRevisionId, AccessTypeCode.PB.name(), theDocument.getDocumentId());
299 log.info("Local document created: {}", theDocument);
302 getCDSClient(theContext).updateDocument(theDocument);
303 log.info("Local document updated: {}", theDocument);
306 catch (HttpStatusCodeException restx) {
307 log.error("Document CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
308 throw new ServiceException("Document CDS call failed.", restx);
310 catch (Exception x) {
311 log.error("Document handling unexpected failure", x);
312 throw new ServiceException("Document handling unexpected failure", x);
316 private MLPRevisionDescription copyRevisionDescription(MLPRevisionDescription peerDescription) {
317 MLPRevisionDescription localDescription = new MLPRevisionDescription(peerDescription);
318 localDescription.setCreated(TimestampedEntity.ORIGIN);
319 localDescription.setModified(TimestampedEntity.ORIGIN);
320 return localDescription;
323 private MLPRevisionDescription copyRevisionDescription(MLPRevisionDescription peerDescription, MLPRevisionDescription localDescription) {
324 localDescription.setDescription(peerDescription.getDescription());
325 return localDescription;
328 private void putRevisionDescription(MLPRevisionDescription theDescription,ServiceContext theContext) throws ServiceException {
331 if (theDescription.getCreated() == Instant.MIN) {
332 getCDSClient(theContext).createRevisionDescription(theDescription);
333 log.info("Local description created: {}", theDescription);
336 getCDSClient(theContext).updateRevisionDescription(theDescription);
339 catch (HttpStatusCodeException restx) {
340 log.error("Revision description CDS call failed. CDS message is " + restx.getResponseBodyAsString(), restx);
341 throw new ServiceException("Revision description CDS call failed.", restx);
343 catch (Exception x) {
344 log.error("Revision description handling unexpected failure", x);
345 throw new ServiceException("Revision description handling unexpected failure", x);
349 private boolean hasChanged(Artifact thePeerArtifact, Artifact theLocalArtifact) {
350 if (thePeerArtifact.getVersion() != null && theLocalArtifact.getVersion() != null) {
351 return !thePeerArtifact.getVersion().equals(theLocalArtifact.getVersion());
354 if (thePeerArtifact.getSize() != null && theLocalArtifact.getSize() != null) {
355 return !thePeerArtifact.getSize().equals(theLocalArtifact.getSize());
361 private boolean hasChanged(Document thePeerDoc, Document theLocalDoc) {
362 if (thePeerDoc.getVersion() != null && theLocalDoc.getVersion() != null) {
363 return !thePeerDoc.getVersion().equals(theLocalDoc.getVersion());
366 if (thePeerDoc.getSize() != null && theLocalDoc.getSize() != null) {
367 return !thePeerDoc.getSize().equals(theLocalDoc.getSize());
373 private Solution copySolution(Solution thePeerSolution) {
374 return Solution.buildFrom(thePeerSolution)
375 .withCreated(TimestampedEntity.ORIGIN)
376 .withModified(TimestampedEntity.ORIGIN)
377 .withUser(getUserId(this.sub))
378 .withSource(this.peer.getPeerId())
383 private Solution copySolution(Solution thePeerSolution, Solution theLocalSolution) {
384 String newUserId = getUserId(this.sub),
385 newSourceId = this.peer.getPeerId();
387 //some basic warnings
388 if (!theLocalSolution.getUserId().equals(newUserId)) {
389 // is this solution being updated as part of different/new subscription?
390 log.warn("Updating solution {} triggers a user change", theLocalSolution.getSolutionId());
391 //but make the change anyway
392 theLocalSolution.setUserId(newUserId);
395 if (theLocalSolution.getSourceId() == null) {
396 //this is a local solution that made its way back
397 log.info("Solution {} was originally provisioned locally, avoiding user update", theLocalSolution.getSolutionId());
400 if (!theLocalSolution.getSourceId().equals(newSourceId)) {
401 // we will see this if a solution is available in more than one peer
402 log.warn("Solution {} triggers a source change", theLocalSolution.getSolutionId());
403 //but make the change anyway
404 theLocalSolution.setSourceId(newSourceId);
408 //tags, keep only the delta
409 Set<MLPTag> tags = thePeerSolution.getTags();
410 tags.removeAll(theLocalSolution.getTags());
411 theLocalSolution.setTags(tags);
413 return theLocalSolution;
416 private boolean hasChanged(Solution thePeerSolution, Solution theLocalSolution) {
417 if (!theLocalSolution.getTags().containsAll(thePeerSolution.getTags()))
424 * Here comes the core process of updating a local solution's related
425 * information with what is available from a peer.
428 * the local solution who's related information (revisions and
429 * artifacts) we are trying to sync
430 * @param thePeerClient
433 * the context in which we perform the catalog operations
434 * @return true if mapping was succesful, false otherwise
436 * any error related to CDS and peer interaction
438 protected boolean mapSolution(MLPSolution theSolution, FederationClient thePeerClient, ServiceContext theContext) throws Exception {
440 boolean isComplete = true,
441 isSolutionNew = false,
442 hasSolutionChanged = false;
444 Solution localSolution = null,
447 //retrieve the full representation from the peer
448 JsonResponse<MLPSolution> peerSolutionResponse = null;
450 peerSolutionResponse = thePeerClient.getSolution(theSolution.getSolutionId());
452 catch (FederationException fx) {
453 log.warn("Failed to retrieve peer solution details for " + theSolution, fx);
457 peerSolution = (Solution)peerSolutionResponse.getContent();
458 if (peerSolution == null) {
459 log.warn("No solution details available for {} in {}", theSolution, peerSolutionResponse);
463 localSolution = catalog.getSolution(peerSolution.getSolutionId());
464 if (localSolution == null) {
465 localSolution = catalog.putSolution(copySolution(peerSolution), theContext);
466 isSolutionNew = true;
469 hasSolutionChanged = hasChanged(peerSolution, localSolution);
472 List<MLPSolutionRevision> peerRevisions = (List)peerSolution.getRevisions();
473 Collections.sort(peerRevisions, (arev, brev) -> arev.getModified().compareTo(brev.getModified()));
475 // this should not happen as any solution should have at least one
476 // revision (but that's an assumption on how on-boarding works)
477 if (peerRevisions == null || peerRevisions.size() == 0) {
478 log.warn("No peer revisions were retrieved");
482 // check if we have locally the latest revision available on the peer
483 List<MLPSolutionRevision> catalogRevisions = (List)localSolution.getRevisions();
484 final List<MLPSolutionRevision> localRevisions = catalogRevisions == null ? Collections.EMPTY_LIST : catalogRevisions;
486 // map peer revisions to local ones; new peer revisions have a null mapping
487 Map<MLPSolutionRevision, MLPSolutionRevision> peerToLocalRevisions =
488 new LinkedHashMap<MLPSolutionRevision, MLPSolutionRevision>();
489 peerRevisions.forEach(peerRevision -> peerToLocalRevisions.put(peerRevision,
490 localRevisions.stream()
491 .filter(localRevision -> localRevision.getRevisionId().equals(peerRevision.getRevisionId()))
492 .findFirst().orElse(null)));
494 for (Map.Entry<MLPSolutionRevision, MLPSolutionRevision> revisionEntry : peerToLocalRevisions.entrySet()) {
495 MLPSolutionRevision peerRevision = revisionEntry.getKey(), localRevision = revisionEntry.getValue();
497 boolean isRevisionNew = false,
498 hasRevisionChanged = false;
500 //revision related information (artifacts/documents/description/..) is now embedded in the revision details
501 //federation api call so one call is all is needed
502 JsonResponse<MLPSolutionRevision> peerRevisionResponse = null;
504 peerRevisionResponse = thePeerClient.getSolutionRevision(peerSolution.getSolutionId(), peerRevision.getRevisionId());
506 catch (FederationException fx) {
507 isComplete = false; //try the next revision but mark the overall processing as incomplete
511 peerRevision = peerRevisionResponse.getContent();
512 if (peerRevision == null) {
513 isComplete = false; //try the next revision but mark the overall processing as incomplete
517 if (localRevision == null) {
519 localRevision = catalog.putSolutionRevision(
520 SolutionRevision.buildFrom(peerRevision)
521 .withCreated(TimestampedEntity.ORIGIN)
522 .withModified(TimestampedEntity.ORIGIN)
523 .withUser(getUserId(this.sub))
524 .withSource(this.peer.getPeerId())
525 .withAccessTypeCode(this.sub.getAccessType())
526 .build(), theContext);
528 catch (ServiceException sx) {
529 log.error("Failed to put revision " + theSolution.getSolutionId() + "/" + peerRevision.getRevisionId() + " into catalog", sx);
530 isComplete = false; //try procecessing the next revision but mark the processing as incomplete
533 isRevisionNew = true;
536 List<Artifact> peerArtifacts = (List)((SolutionRevision)peerRevision).getArtifacts();
537 List<Document> peerDocuments = (List)((SolutionRevision)peerRevision).getDocuments();
539 List<Artifact> catalogArtifacts = (List)((SolutionRevision)localRevision).getArtifacts();
540 List<Document> catalogDocuments = (List)((SolutionRevision)localRevision).getDocuments();
542 final List<Artifact> localArtifacts = catalogArtifacts;
544 // TODO: track deleted artifacts
545 Map<Artifact, Artifact> peerToLocalArtifacts = new HashMap<Artifact, Artifact>();
546 peerArtifacts.forEach(peerArtifact -> peerToLocalArtifacts.put(peerArtifact, localArtifacts.stream()
547 .filter(localArtifact -> localArtifact.getArtifactId().equals(peerArtifact.getArtifactId()))
548 .findFirst().orElse(null)));
550 for (Map.Entry<Artifact, Artifact> artifactEntry : peerToLocalArtifacts.entrySet()) {
551 Artifact peerArtifact = artifactEntry.getKey(),
552 localArtifact = artifactEntry.getValue();
553 boolean doCatalog = false;
555 log.info("Processing peer artifact {} against local artifact {}", peerArtifact, localArtifact);
557 if (localArtifact == null) {
558 localArtifact = copyArtifact(peerArtifact);
562 if (hasChanged(peerArtifact, localArtifact)) {
563 // update local artifact
564 localArtifact = copyArtifact(peerArtifact, localArtifact);
569 boolean doContent = doCatalog &&
570 (peerArtifact.getUri() != null) &&
571 (SubscriptionScope.Full == SubscriptionScope.forCode(this.sub.getScopeType()));
573 log.info("Processing content for artifact {}", peerArtifact);
574 // TODO: we are trying to access the artifact by its identifier which
575 // is fine in the common case but the uri specified in the artifact
576 // data is the right approach (as it does not rely on the E5 definition).
577 Resource artifactContent = null;
579 artifactContent = thePeerClient.getArtifactContent(
580 peerSolution.getSolutionId(), peerRevision.getRevisionId(), peerArtifact.getArtifactId());
581 log.info("Received {} bytes of artifact content", artifactContent.contentLength());
583 catch (FederationException x) {
584 log.error("Failed to retrieve acumos artifact content", x);
585 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
589 if (artifactContent != null) {
591 content.putArtifactContent(
592 localSolution.getSolutionId(), localRevision.getRevisionId(), localArtifact, artifactContent);
595 catch (ServiceException sx) {
596 log.error("Failed to store artifact content to local repo", sx);
597 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
601 if (artifactContent instanceof Closeable) {
602 ((Closeable)artifactContent).close();
610 putArtifact(localSolution.getSolutionId(), localRevision.getRevisionId(), localArtifact, theContext);
612 catch (ServiceException sx) {
613 log.error("Artifact processing failed.", sx);
616 hasRevisionChanged = true;
618 } //end map artifacts loop
621 final List<Document> localDocuments = catalogDocuments;
623 // TODO: track deleted documents
624 Map<Document, Document> peerToLocalDocuments = new HashMap<Document, Document>();
625 peerDocuments.forEach(peerDocument -> peerToLocalDocuments.put(peerDocument, localDocuments.stream()
626 .filter(localDocument -> localDocument.getDocumentId().equals(peerDocument.getDocumentId()))
627 .findFirst().orElse(null)));
629 for (Map.Entry<Document, Document> documentEntry : peerToLocalDocuments.entrySet()) {
630 Document peerDocument = documentEntry.getKey(),
631 localDocument = documentEntry.getValue();
632 boolean doCatalog = false;
634 log.info("Processing peer document {} against local version {}", peerDocument, localDocument);
635 if (localDocument == null) {
636 localDocument = copyDocument(peerDocument);
640 //version strings are not standard so comparing them is not necessarly safe
641 if (hasChanged(peerDocument, localDocument)) {
643 localDocument = copyDocument(peerDocument, localDocument);
648 boolean doContent = doCatalog &&
649 (peerDocument.getUri() != null) &&
650 (SubscriptionScope.Full == SubscriptionScope.forCode(this.sub.getScopeType()));
652 log.info("Processing content for document {}", peerDocument);
653 // TODO: we are trying to access the document by its identifier which
654 // is fine in the common case but the uri specified in the document
655 // data is a more flexible approach.
656 Resource documentContent = null;
658 documentContent = thePeerClient.getDocumentContent(
659 peerSolution.getSolutionId(), localRevision.getRevisionId(), peerDocument.getDocumentId());
660 log.info("Received {} bytes of document content", documentContent.contentLength());
662 catch (FederationException x) {
663 log.error("Failed to retrieve acumos document content", x);
664 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
668 if (documentContent != null) {
670 content.putDocumentContent(
671 localSolution.getSolutionId(), localRevision.getRevisionId(), localDocument, documentContent);
674 catch (ServiceException sx) {
675 log.error("Failed to store document content to local repo", sx);
676 doCatalog = this.sub.getSubscriptionOptions().alwaysUpdateCatalog();
684 putDocument(localSolution.getSolutionId(), localRevision.getRevisionId(), localDocument, theContext);
686 catch (ServiceException sx) {
687 log.error("Document processing failed", sx);
690 hasRevisionChanged = true;
693 } // end map documents loop
695 MLPRevisionDescription localDescription = ((SolutionRevision)localRevision).getRevisionDescription();
696 MLPRevisionDescription peerDescription = ((SolutionRevision)peerRevision).getRevisionDescription();
698 if (peerDescription != null) {
699 boolean doCatalog = false;
701 if (localDescription == null) {
702 localDescription = copyRevisionDescription(peerDescription);
706 //is this a good enough test ?? it implies time sync ..
707 if (peerDescription.getModified().isAfter(localDescription.getModified())) {
708 localDescription = copyRevisionDescription(peerDescription, localDescription);
715 putRevisionDescription(localDescription, theContext);
717 catch (ServiceException sx) {
718 log.error("Description processing failed", sx);
721 hasRevisionChanged = true;
723 } //end revision processing
725 if (!isRevisionNew && hasRevisionChanged) {
727 //we do not actually update any properties, just give CDS a chance to update the timestamps as to mark it as updated.
728 catalog.putSolutionRevision(SolutionRevision.buildFrom(localRevision).build(),
731 catch (ServiceException sx) {
732 log.error("Failed to update local revision", sx);
737 hasSolutionChanged |= (isRevisionNew || hasRevisionChanged);
738 } //end revisions processing
740 if (!isSolutionNew && hasSolutionChanged) {
742 catalog.putSolution(copySolution(peerSolution, localSolution), theContext);
744 catch (ServiceException sx) {
745 log.error("Failed to update local solution", sx);