Containerization feature of SO
[so.git] / common / src / main / java / org / onap / so / client / aai / AAITransactionalClient.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License 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=========================================================
19  */
20
21 package org.onap.so.client.aai;
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Optional;
30
31 import javax.ws.rs.NotFoundException;
32 import javax.ws.rs.core.GenericType;
33 import javax.ws.rs.core.Response;
34
35 import org.onap.aai.domain.yang.Relationship;
36 import org.onap.so.client.RestClient;
37 import org.onap.so.client.aai.entities.AAIError;
38 import org.onap.so.client.aai.entities.bulkprocess.OperationBody;
39 import org.onap.so.client.aai.entities.bulkprocess.Transaction;
40 import org.onap.so.client.aai.entities.bulkprocess.Transactions;
41 import org.onap.so.client.aai.entities.uri.AAIResourceUri;
42 import org.onap.so.client.aai.entities.uri.AAIUri;
43 import org.onap.so.client.aai.entities.uri.AAIUriFactory;
44 import org.onap.so.client.graphinventory.exceptions.BulkProcessFailed;
45 import org.onap.so.jsonpath.JsonPathUtil;
46
47 import com.fasterxml.jackson.core.type.TypeReference;
48 import com.fasterxml.jackson.databind.ObjectMapper;
49 import com.google.common.base.Joiner;
50
51 public class AAITransactionalClient extends AAIClient {
52
53         private final Transactions transactions;
54         private Transaction currentTransaction;
55         private final AAIVersion version;
56         private int actionCount = 0;
57         protected AAITransactionalClient(AAIVersion version) {
58                 super();
59                 this.version = version;
60                 this.transactions = new Transactions();
61                 startTransaction();
62         }
63         
64         private void startTransaction() {
65                 Transaction transaction = new Transaction();
66                 transactions.getTransactions().add(transaction);
67                 currentTransaction = transaction;
68         }
69         
70         /**
71          * adds an additional transaction and closes the previous transaction
72          * 
73          * @return AAITransactionalClient
74          */
75         public AAITransactionalClient beginNewTransaction() {
76                 startTransaction();
77                 return this;
78         }
79         
80         /**
81          * creates a new object in A&AI
82          * 
83          * @param obj - can be any object which will marshal into a valid A&AI payload
84          * @param uri
85          * @return
86          */
87         public AAITransactionalClient create(AAIResourceUri uri, Object obj) {
88                 currentTransaction.getPut().add(new OperationBody().withUri(uri.build().toString()).withBody(obj));
89                 incrementActionAmount();
90                 return this;
91         }
92         
93         /**
94          * creates a new object in A&AI with no payload body
95          * 
96          * @param uri
97          * @return
98          */
99         public AAITransactionalClient createEmpty(AAIResourceUri uri) {
100                 currentTransaction.getPut().add(new OperationBody().withUri(uri.build().toString()).withBody(new HashMap<String, String>()));
101                 incrementActionAmount();
102                 return this;
103         }
104         
105         /**
106          * Adds a relationship between two objects in A&AI 
107          * @param uriA
108          * @param uriB
109          * @return
110          */
111         public AAITransactionalClient connect(AAIResourceUri uriA, AAIResourceUri uriB) {
112                 AAIResourceUri uriAClone = uriA.clone();
113                 currentTransaction.getPut().add(new OperationBody().withUri(uriAClone.relationshipAPI().build().toString()).withBody(this.buildRelationship(uriB)));
114                 incrementActionAmount();
115                 return this;
116         }
117         
118         /**
119          * relationship between multiple objects in A&AI - connects A to all objects specified in list
120          * 
121          * @param uriA
122          * @param uris
123          * @return
124          */
125         public AAITransactionalClient connect(AAIResourceUri uriA, List<AAIResourceUri> uris) {
126                 for (AAIResourceUri uri : uris) {
127                         this.connect(uriA, uri);
128                 }
129                 return this;
130         }
131         
132         /**
133          * Removes relationship from two objects in A&AI
134          * 
135          * @param uriA
136          * @param uriB
137          * @return
138          */
139         public AAITransactionalClient disconnect(AAIResourceUri uriA, AAIResourceUri uriB) {
140                 AAIResourceUri uriAClone = uriA.clone();
141                 currentTransaction.getDelete().add(new OperationBody().withUri(uriAClone.relationshipAPI().build().toString()).withBody(this.buildRelationship(uriB)));
142                 incrementActionAmount();
143                 return this;
144         }
145         
146         /**
147          * Removes relationship from multiple objects - disconnects A from all objects specified in list
148          * @param uriA
149          * @param uris
150          * @return
151          */
152         public AAITransactionalClient disconnect(AAIResourceUri uriA, List<AAIResourceUri> uris) {
153                 for (AAIResourceUri uri : uris) {
154                         this.disconnect(uriA, uri);
155                 }
156                 return this;
157         }
158         /**
159          * Deletes object from A&AI. Automatically handles resource-version.
160          * 
161          * @param uri
162          * @return
163          */
164         public AAITransactionalClient delete(AAIResourceUri uri) {
165                 AAIResourcesClient client = new AAIResourcesClient();
166                 AAIResourceUri clone = uri.clone();
167                 Map<String, Object> result = client.get(new GenericType<Map<String, Object>>(){}, clone)
168                                 .orElseThrow(() -> new NotFoundException(clone.build() + " does not exist in A&AI"));
169                 String resourceVersion = (String) result.get("resource-version");
170                 currentTransaction.getDelete().add(new OperationBody().withUri(clone.resourceVersion(resourceVersion).build().toString()).withBody(""));
171                 incrementActionAmount();
172                 return this;
173         }
174         
175         /**
176          * @param obj - can be any object which will marshal into a valid A&AI payload
177          * @param uri
178          * @return
179          */
180         public AAITransactionalClient update(AAIResourceUri uri, Object obj) {
181                 currentTransaction.getPatch().add(new OperationBody().withUri(uri.build().toString()).withBody(obj));
182                 incrementActionAmount();
183                 return this;
184         }
185         
186         private void incrementActionAmount() {
187                 actionCount++;
188         }
189         /**
190          * Executes all created transactions in A&AI
191          * @throws BulkProcessFailed 
192          */
193         public void execute() throws BulkProcessFailed {
194                 RestClient client = this.createClient(AAIUriFactory.createResourceUri(AAIObjectType.BULK_PROCESS));
195                 try {
196                         Response response = client.put(this.transactions);
197                         if (response.hasEntity()) {
198                                 final Optional<String> errorMessage = this.locateErrorMessages(response.readEntity(String.class));
199                                 if (errorMessage.isPresent()) {
200                                         throw new BulkProcessFailed("One or more transactions failed in A&AI. Check logs for payloads.\nMessages:\n" + errorMessage.get());
201                                 }
202                         } else {
203                                 throw new BulkProcessFailed("Transactions acccepted by A&AI, but there was no response. Unsure of result.");
204                         }
205                 } finally {
206                         this.transactions.getTransactions().clear();
207                         this.currentTransaction = null;
208                         this.actionCount = 0;
209                 }
210         }
211         
212         protected Optional<String> locateErrorMessages(String response) {
213                 final List<String> errorMessages = new ArrayList<>();
214                 final List<String> results = JsonPathUtil.getInstance().locateResultList(response, "$..body");
215                 final ObjectMapper mapper = new ObjectMapper();
216                 if (!results.isEmpty()) {
217                         List<Map<String, Object>> parsed = new ArrayList<>();
218                         try {
219                                 for (String result : results) {
220                                         parsed.add(mapper.readValue(result, new TypeReference<Map<String, Object>>(){}));
221                                 }
222                         } catch (IOException e) {
223                                 logger.error("could not map json", e);
224                         }
225                         for (Map<String, Object> map : parsed) {
226                                 for (Entry<String, Object> entry : map.entrySet()) {
227                                         if (!entry.getKey().matches("2\\d\\d")) {
228                                                 AAIError error;
229                                                 try {
230                                                         error = mapper.readValue(entry.getValue().toString(), AAIError.class);
231                                                 } catch (IOException e) {
232                                                         logger.error("could not parse error object from A&AI", e);
233                                                         error = new AAIError();
234                                                 }
235                                                 AAIErrorFormatter formatter = new AAIErrorFormatter(error);
236                                                 String outputMessage = formatter.getMessage();
237                                                 logger.error("part of a bulk action failed in A&AI: " + entry.getValue());
238                                                 errorMessages.add(outputMessage);
239                                         }
240                                 }
241                         }
242                 }
243                 
244                 if (!errorMessages.isEmpty()) {
245                         return Optional.of(Joiner.on("\n").join(errorMessages));
246                 } else {
247                         return Optional.empty();
248                 }
249         }
250         private Relationship buildRelationship(AAIUri uri) {
251                 final Relationship result = new Relationship();
252                 result.setRelatedLink(uri.build().toString());
253                 return result;
254         }
255
256         @Override
257         protected AAIVersion getVersion() {
258                 return this.version;
259         }
260         
261         protected Transactions getTransactions() {
262                 return this.transactions;
263         }
264 }