3 * THIS FILE CONTAINS PROPRIETARY INFORMATION OF
4 * AT&T AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN
5 * ACCORDANCE WITH APPLICABLE AGREEMENTS.
7 * Copyright (c) 2015 AT&T Knowledge Ventures
8 * Unpublished and Not for Publication
11 package org.onap.sdc.dcae.catalog.engine;
13 * THIS FILE CONTAINS PROPRIETARY INFORMATION OF
14 * AT&T AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN
15 * ACCORDANCE WITH APPLICABLE AGREEMENTS.
17 * Copyright (c) 2015 AT&T Knowledge Ventures
18 * Unpublished and Not for Publication
22 import java.util.HashMap;
23 import java.util.List;
26 import javax.annotation.PostConstruct;
27 import javax.annotation.PreDestroy;
29 import static org.onap.sdc.dcae.catalog.Catalog.*;
32 import java.net.URISyntaxException;
34 import org.json.JSONObject;
35 import org.onap.sdc.common.onaplog.OnapLoggerDebug;
36 import org.onap.sdc.common.onaplog.OnapLoggerError;
37 import org.onap.sdc.common.onaplog.Enums.LogLevel;
38 import org.onap.sdc.dcae.catalog.Catalog;
39 import org.onap.sdc.dcae.catalog.asdc.ASDCCatalog;
40 import org.onap.sdc.dcae.catalog.commons.Future;
41 import org.onap.sdc.dcae.catalog.commons.FutureHandler;
42 import org.onap.sdc.dcae.composition.util.DcaeBeConstants;
43 import org.onap.sdc.dcae.composition.util.SystemProperties;
44 import org.json.JSONArray;
45 import org.springframework.web.bind.annotation.RequestMapping;
46 import org.springframework.web.bind.annotation.PathVariable;
47 import org.springframework.web.bind.annotation.RequestBody;
48 import org.springframework.web.bind.annotation.RequestMethod;
49 import org.springframework.web.bind.annotation.RestController;
51 import org.springframework.web.context.request.async.DeferredResult;
52 import org.springframework.beans.factory.annotation.Autowired;
53 import org.springframework.boot.context.properties.ConfigurationProperties;
55 import org.springframework.web.bind.annotation.CrossOrigin;
60 * "id": optional request uuid,
61 * "timestamp": optional request timestamp,
62 * "catalog": optional catalog uri,
63 * "timeout": optional timeout - default 0 no time limit
71 * If a non-2xx reponse is provided and error occured at catalog engine processing level.
72 * If error has occured in data retrieval then the response error object is not empty.
76 * /elements : roots of the catalog; request body is optional but can specify a label under 'startingLabel'
77 * response contains items under 'data/elements'
78 * /{itemId}/elements : catalog descendants of the given item, possibly a mix of folders and items
79 * response contains items under 'data/elements'
80 * /lookup.by.name : lookup catalog entries by name.
81 The request body must contain a 'selector' entry with a 'name' criteria
82 * response contains items under 'data/elements'
83 * Example: '{"id":"5d0c1cf4-11aa-11e6-a148-3e1d05defe78","selector":{"name":"Firewall"}}'
84 * /lookup.by.annotation
85 The request body must contain a 'annotation' entry and it can have a 'selector' entry
86 * with a multiple annotation property criteria
87 * response contains items under 'data/elements'
88 * /lookup.by.model.property.value :
89 * The request must contain a "selector" entry as a JSONObject containing the selection criteria
90 * (property name with values) and desired output properties (null values). Example:
91 * "selector":{"att-part-number":"L-CSR-50M-APP-3Y",
92 * "management-option":"ATT",
94 * "vendor-model":null}
95 * response contains items under 'data/elements'
96 * /referents : provides generic recommendations
97 * response contains items under 'data/elements'
98 * /{itemId}/referents : provides recommendations for the given item
99 * response contains items under 'data/elements'
100 * /{itemId}/model : retrieves the TOSCA model for the item with the given id
101 * response under 'data/model'
107 //@RequestMapping(value="/catalog",method=RequestMethod.POST)
108 @CrossOrigin(origins="*")
109 //@ConfigurationProperties(prefix="catalogController")
110 public class CatalogController {
112 private static OnapLoggerError errLogger = OnapLoggerError.getInstance();
113 private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
117 private SystemProperties systemProperties;
120 private boolean enableCORS = false;
121 private URI defaultCatalog;
122 private static Map<URI, Catalog> catalogs = new HashMap<URI, Catalog>();
125 public void setDefaultCatalog(URI theUri) {
126 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "set default catalog at {}", theUri);
127 this.defaultCatalog = theUri;
130 public void setEnableCORS(boolean doEnable) {
131 this.enableCORS = doEnable;
134 // @RequestMapping(value="/elements",method={RequestMethod.POST, RequestMethod.GET}, produces = "application/json")
135 // public DeferredResult<CatalogResponse> items(@RequestBody(required=false) ItemsRequest theRequest) {
137 // final ItemsRequest request = (theRequest == null) ? ItemsRequest.EMPTY_REQUEST : theRequest;
139 // Catalog catalog = getCatalog(request.getCatalog());
140 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
142 // catalog.rootsByLabel(request.getStartingLabel())
145 // new CatalogHandler<Folders>(request, result) {
146 // public CatalogResponse handleData(Folders theFolders) {
147 // JSONArray ja = new JSONArray();
148 // if (theFolders != null) {
149 // for (Folder folder : theFolders) {
150 // ja.put(patchData(catalog, folder.data()));
153 // CatalogResponse response = new CatalogResponse(this.request);
155 // .put("elements", ja);
162 // @RequestMapping(value="/{theItemId}/elements",method={RequestMethod.POST,RequestMethod.GET}, produces = "application/json")
163 // public DeferredResult<CatalogResponse> items(@RequestBody(required=false) ItemsRequest theRequest, @PathVariable String theItemId) {
165 // final ItemsRequest request = (theRequest == null) ? ItemsRequest.EMPTY_REQUEST : theRequest;
167 // Catalog catalog = getCatalog(request.getCatalog());
168 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
171 //// .fetchFolderByItemId(theItemId)
172 // .folder(theItemId)
174 // .withPartAnnotations()
176 // .withItemAnnotations()
180 // new CatalogHandler<Folder>(request, result) {
181 // public CatalogResponse handleData(Folder theFolder) {
182 // CatalogResponse response = new CatalogResponse(this.request);
183 // if (theFolder == null) {
188 // Elements folders = theFolder.elements("parts",Folders.class);
189 // if (folders != null) {
190 // for (Object folder: folders) {
191 // patchData(catalog, ((Element)folder).data());
192 // //lots of ephemere proxies created here ..
193 // Elements annotations =
194 // ((Element)folder).elements("annotations", Annotations.class);
195 // if (annotations != null) {
196 // for (Object a: annotations) {
197 // patchData(catalog, ((Annotation)a).data());
202 // Elements items = theFolder.elements("items",Items.class);
203 // if (items != null) {
204 // for (Object i: items) {
205 // patchData(catalog, ((Element)i).data());
206 // //lots of ephemere proxies created here ..
207 // Elements annotations =
208 // ((Element)i).elements("annotations", Annotations.class);
209 // if (annotations != null) {
210 // for (Object a: annotations){
211 // patchData(catalog, ((Annotation)a).data());
217 // catch(Exception x) {
218 //x.printStackTrace();
219 // return new CatalogError(this.request, "", x);
223 // .put("element", theFolder.data());
231 // @RequestMapping(value="/lookup.by.name",method=RequestMethod.POST, produces = "application/json")
232 // public DeferredResult<CatalogResponse> elementsByName(@RequestBody ElementsLookup theRequest) {
234 // Catalog catalog = getCatalog(theRequest.getCatalog());
235 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(theRequest.getTimeout());
238 // .lookup(new JSONObject(theRequest.getSelector()))
240 // new CatalogHandler<Mixels>(theRequest, result) {
241 // public CatalogResponse handleData(Mixels theElems) {
242 // JSONArray ja = new JSONArray();
243 // if (theElems != null) {
244 // for (Object elem : theElems) {
245 // ja.put(patchData(catalog, ((Element)elem).data()));
248 // CatalogResponse response = new CatalogResponse(theRequest);
250 // .put("elements", ja);
258 // @RequestMapping(value="/lookup.by.annotation",method=RequestMethod.POST, produces = "application/json")
259 // public DeferredResult<CatalogResponse> elementsByAnnotation(@RequestBody ElementsLookup theRequest) {
261 // Catalog catalog = getCatalog(theRequest.getCatalog());
262 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(theRequest.getTimeout());
265 // .lookup(theRequest.getAnnotation(),
266 // new JSONObject(theRequest.getSelector()))
268 // new CatalogHandler<Mixels>(theRequest, result) {
269 // public CatalogResponse handleData(Mixels theElems) {
270 // JSONArray ja = new JSONArray();
271 // if (theElems != null) {
272 // for (Object elem : theElems) {
273 // ja.put(patchData(catalog, ((Element)elem).data()));
276 // CatalogResponse response = new CatalogResponse(this.request);
278 // .put("elements", ja);
287 * NeoCatalog specific
289 @RequestMapping(value="/lookup.by.model.property.value",method=RequestMethod.POST, produces = "application/json")
290 public DeferredResult<CatalogResponse> elementsByModelPropertyValue(@RequestBody ElementsLookup theRequest) {
292 DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(theRequest.getTimeout());
294 NeoCatalog catalog = asNeo(getCatalog(theRequest.getCatalog()));
295 if (catalog == null) {
296 result.setErrorResult(
298 theRequest,"The selected catalog is not capable of handling this request (lookup.by.model.property.value)"));
303 .lookupItemsByToscaNodePropertyValue(theRequest.getJSONSelector())
305 new CatalogHandler<Items>(theRequest, result) {
306 public CatalogResponse handleData(Items theItems) {
307 JSONArray ja = new JSONArray();
308 if (theItems != null) {
309 for (Item item : theItems) {
310 ja.put(patchData(catalog, item.data()));
313 CatalogResponse response = new CatalogResponse(this.request);
315 .put("elements", ja);
324 * This follows the current convention that each item will have a single model
327 // @RequestMapping(value="/{theItemId}/model",method={RequestMethod.POST,RequestMethod.GET}, produces = "application/json")
328 // //public DeferredResult<CatalogResponse> model(@RequestBody ElementRequest theRequest) {
329 // public DeferredResult<CatalogResponse> model(@RequestBody(required=false) ElementRequest theRequest, @PathVariable String theItemId) {
330 // final ElementRequest request = (theRequest == null) ? ElementRequest.EMPTY_REQUEST : theRequest;
332 // Catalog catalog = getCatalog(request.getCatalog());
333 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
336 //// .fetchItemByItemId(/*theRequest.getProductId()*/theItemId)
341 // new CatalogHandler<Item>(request, result) {
342 // public CatalogResponse handleData(Item theItem) {
343 // if (theItem == null) {
344 // return new CatalogError(this.request, "No such item");
346 // Templates models = null;
348 // models = (Templates)theItem.elements("models", Templates.class);
350 // catch (Exception x) {
351 // return new CatalogError(this.request, "Failed to decode templates from result", x);
354 // if (models == null || models.size() == 0) {
355 // return new CatalogError(this.request, "Item has no models");
357 // if (models.size() > 1) {
358 // return new CatalogError(this.request, "Item has more than one model !?");
361 // catalog.template(models.get(0).id())
365 // .withNodeProperties()
366 // .withNodePropertiesAssignments()
367 // .withNodeRequirements()
368 // .withNodeCapabilities()
369 // .withNodeCapabilityProperties()
370 // .withNodeCapabilityPropertyAssignments()
372 // .withPolicyProperties()
373 // .withPolicyPropertiesAssignments()
376 // new CatalogHandler<Template>(this.request, this.result) {
377 // public CatalogResponse handleData(Template theTemplate) {
378 // CatalogResponse response = new CatalogResponse(this.request);
379 // if (theTemplate != null) {
381 // .put("model", patchData(catalog, theTemplate.data()));
387 // catch (Exception x) {
388 // x.printStackTrace();
397 // @RequestMapping(value="/{theItemId}/type/{theTypeName}",method={RequestMethod.POST,RequestMethod.GET}, produces = "application/json")
398 // public DeferredResult<CatalogResponse> model(@RequestBody(required=false) ElementRequest theRequest, @PathVariable String theItemId, @PathVariable String theTypeName) {
399 // final ElementRequest request = (theRequest == null) ? ElementRequest.EMPTY_REQUEST : theRequest;
401 // Catalog catalog = getCatalog(request.getCatalog());
402 // DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
404 // catalog.type(theItemId, theTypeName)
406 // .withCapabilities()
407 // .withRequirements()
410 // new CatalogHandler<Type>(request, result) {
411 // public CatalogResponse handleData(Type theType) {
412 // CatalogResponse response = new CatalogResponse(this.request);
413 // if (theType != null) {
415 // .put("type", patchData(catalog, theType.data()));
425 @RequestMapping(value="/referents",method=RequestMethod.POST, produces = "application/json")
426 public DeferredResult<CatalogResponse> referents(@RequestBody(required=false) ElementRequest theRequest) {
427 final ElementRequest request = (theRequest == null) ? ElementRequest.EMPTY_REQUEST : theRequest;
428 DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
430 NeoCatalog catalog = asNeo(getCatalog(theRequest.getCatalog()));
431 if (catalog == null) {
432 result.setErrorResult(
434 theRequest,"The selected catalog is not capable of handling this request (referents)"));
439 .defaultRecommendations()
441 new CatalogHandler<Mixels>(request, result) {
442 public CatalogResponse handleData(Mixels theElems) {
443 JSONArray ja = new JSONArray();
444 if (theElems != null) {
445 for (Element elem : theElems) {
446 ja.put(patchData(catalog, elem.data()));
449 CatalogResponse response = new CatalogResponse(this.request);
451 .put("elements", ja);
460 /* @RequestMapping(value="/{theItemId}/referents",method=RequestMethod.POST, produces = "application/json")
461 public DeferredResult<CatalogResponse> referents(@RequestBody(required=false) ElementRequest theRequest, @PathVariable String theItemId) {
462 final ElementRequest request = (theRequest == null) ? ElementRequest.EMPTY_REQUEST : theRequest;
463 DeferredResult<CatalogResponse> result = new DeferredResult<CatalogResponse>(request.getTimeout());
465 NeoCatalog catalog = asNeo(getCatalog(theRequest.getCatalog()));
466 if (catalog == null) {
467 result.setErrorResult(
469 theRequest,"The selected catalog is not capable of handling this request (item referents)"));
474 .recommendationsForItemId(theItemId)
476 new CatalogHandler<Mixels>(request, result) {
477 public CatalogResponse handleData(Mixels theElems) {
478 JSONArray ja = new JSONArray();
479 if (theElems != null) {
480 for (Element elem : theElems) {
481 ja.put(patchData(catalog, elem.data()));
484 CatalogResponse response = new CatalogResponse(this.request);
486 .put("elements", ja);
495 public void initCatalog() {
496 // Dump some info and construct our configuration objects
497 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "initCatalog");
499 this.defaultCatalog = URI.create(systemProperties.getProperties().getProperty(DcaeBeConstants.Config.ASDC_CATALOG_URL));
500 // Initialize default catalog connection
501 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "default catalog at {}", this.defaultCatalog);
505 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "CatalogEngine started");
509 public void cleanupCatalog() {
510 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "destroyCatalog");
513 public Catalog getCatalog(URI theCatalogUri) {
514 //TODO: Thread safety! Check catalog is alive!
515 if (theCatalogUri == null)
516 theCatalogUri = this.defaultCatalog;
518 Catalog cat = catalogs.get(theCatalogUri);
519 if (cat == null && theCatalogUri != null) {
520 String scheme = theCatalogUri.getScheme();
521 URI catalogUri = null;
523 catalogUri = new URI(theCatalogUri.getSchemeSpecificPart() + "#" + theCatalogUri.getFragment());
525 catch (URISyntaxException urisx) {
526 throw new IllegalArgumentException("Invalid catalog reference '" + theCatalogUri.getSchemeSpecificPart() + "'");
528 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "Build catalog for {}", catalogUri);
530 if ("asdc".equals(scheme)) {
531 cat = new ASDCCatalog(catalogUri);
537 catalogs.put(theCatalogUri, cat);
542 /* private NeoCatalog asNeo(Catalog theCatalog) {
544 return (NeoCatalog)theCatalog;
546 catch (ClassCastException ccx) {
551 public JSONObject patchData(Catalog theCat, JSONObject theData) {
552 theData.put("catalog", theCat.getUri());
553 theData.put("catalogId", theData.optLong("id"));
554 theData.put("id", theData.optLong("itemId"));
558 public abstract class CatalogHandler<T> implements FutureHandler<T> {
560 protected DeferredResult result;
561 protected CatalogRequest request;
563 public CatalogHandler(CatalogRequest theRequest, DeferredResult theResult) {
564 this.request = theRequest;
565 this.result = theResult;
568 public abstract CatalogResponse handleData(T theData);
571 public void handle(Future<T> theEvent) {
572 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "handle");
574 if (this.result.isSetOrExpired()) {
575 debugLogger.log(LogLevel.WARN, this.getClass().getName(), "handle, Data is late");
579 if (theEvent.failed()) {
580 this.result.setErrorResult(new CatalogError(this.request, "Catalog API failed", theEvent.cause()));
583 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "handle, got: {}", theEvent.result());
584 CatalogResponse response = handleData(theEvent.result());
585 //a null result allows the handler to pass the processing onto some other async processing stage
586 if (response != null) {
587 if (!this.result.setResult(response)) {
588 this.result.setErrorResult(new CatalogError(this.request, "Catalog API call succesful but late"));