1 package org.onap.sdc.dcae.catalog.asdc;
3 import org.apache.commons.jxpath.JXPathContext;
4 import org.apache.commons.lang3.StringUtils;
5 import org.json.JSONArray;
6 import org.json.JSONObject;
7 import org.onap.sdc.common.onaplog.OnapLoggerDebug;
8 import org.onap.sdc.common.onaplog.OnapLoggerError;
9 import org.onap.sdc.common.onaplog.Enums.LogLevel;
10 import org.onap.sdc.dcae.catalog.commons.Actions;
11 import org.onap.sdc.dcae.catalog.commons.Future;
12 import org.onap.sdc.dcae.catalog.commons.Futures;
13 import org.onap.sdc.dcae.catalog.commons.Recycler;
14 import org.onap.sdc.dcae.checker.*;
15 import org.springframework.beans.factory.annotation.Autowired;
16 import org.springframework.boot.context.properties.ConfigurationProperties;
17 import org.springframework.context.annotation.Scope;
18 import org.springframework.stereotype.Component;
19 import org.springframework.util.Base64Utils;
24 import java.util.function.BiFunction;
25 import java.util.function.Function;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28 import java.util.stream.StreamSupport;
29 import java.util.zip.ZipEntry;
30 import java.util.zip.ZipInputStream;
33 @Component("asdcutils")
35 @ConfigurationProperties(prefix="asdcutils")
36 public class ASDCUtils {
38 private static OnapLoggerError errLogger = OnapLoggerError.getInstance();
39 private static OnapLoggerDebug debugLogger = OnapLoggerDebug.getInstance();
45 private Blueprinter blueprint;
51 public ASDCUtils(URI theASDCURI) {
52 this(theASDCURI, null);
55 public ASDCUtils(URI theASDCURI, URI theBlueprinterURI) {
56 this.asdc = new ASDC();
57 this.asdc.setUri(theASDCURI);
58 if (theBlueprinterURI != null) {
59 this.blueprint = new Blueprinter();
60 this.blueprint.setUri(theBlueprinterURI);
64 public ASDCUtils(ASDC theASDC) {
68 public ASDCUtils(ASDC theASDC, Blueprinter theBlueprinter) {
70 this.blueprint = theBlueprinter;
73 public CloneAssetArtifactsAction cloneAssetArtifacts(ASDC.AssetType theAssetType, UUID theSourceId, UUID theTargetId) {
74 return new CloneAssetArtifactsAction(this.asdc, theAssetType, theSourceId, theTargetId);
77 public static class CloneAssetArtifactsAction extends ASDC.ASDCAction<CloneAssetArtifactsAction, List<JSONObject>> {
79 private ASDC.AssetType assetType;
80 private UUID sourceId, targetId;
82 protected CloneAssetArtifactsAction(ASDC theASDC, ASDC.AssetType theAssetType, UUID theSourceId, UUID theTargetId) {
83 theASDC.super(new JSONObject());
84 this.assetType = theAssetType;
85 this.sourceId = theSourceId;
86 this.targetId = theTargetId;
89 protected CloneAssetArtifactsAction self() {
93 public CloneAssetArtifactsAction withLabel(String theLabel) {
94 return with("artifactLabel", theLabel);
97 protected String[] mandatoryInfoEntries() {
98 return new String[] {};
101 public Future<List<JSONObject>> execute() {
104 final Actions.Sequence<JSONObject> sequencer = new Actions.Sequence<JSONObject>();
106 new Actions.Sequence().add(super.asdc().getAssetArchiveAction(this.assetType, this.sourceId)).add(super.asdc().getAssetAction(this.assetType, this.sourceId, JSONObject.class)).execute().setHandler(assetFuture -> {
107 debugLogger.log(LogLevel.DEBUG, this.getClass().getName(), "*** {}", assetFuture.result());
108 processArtifacts((List) assetFuture.result(), (JSONObject theInfo, byte[] theData) -> {
109 theInfo.remove("artifactChecksum");
110 theInfo.remove("artifactUUID");
111 theInfo.remove("artifactVersion");
112 theInfo.remove("artifactURL");
113 theInfo.put("description", theInfo.remove("artifactDescription"));
114 theInfo.put("payloadData", Base64Utils.encodeToString(theData));
116 }, null).forEach(artifactInfo -> sequencer.add(super.asdc().createAssetArtifact(this.assetType, this.targetId).withInfo(ASDC.merge(artifactInfo, this.info)).withOperator(this.operatorId)));
120 return sequencer.future();
125 private static JSONObject lookupArtifactInfo(JSONArray theArtifacts, String theName) {
127 for (int i = 0; theArtifacts != null && i < theArtifacts.length(); i++) {
128 JSONObject artifactInfo = theArtifacts.getJSONObject(i);
129 if (theName.equals(artifactInfo.getString("artifactName"))) {
130 debugLogger.log(LogLevel.DEBUG, ASDCUtils.class.getName(), "Found artifact info {}", artifactInfo);
138 private static byte[] extractArtifactData(InputStream theEntryStream) throws IOException {
139 ByteArrayOutputStream baos = new ByteArrayOutputStream();
141 byte[] buff = new byte[4096];
143 while ((cnt = theEntryStream.read(buff)) != -1) {
144 baos.write(buff, 0, cnt);
149 return baos.toByteArray();
153 * Recycle a cdump, fetch all relevant ASDC artifacts, interact with Shu's toscalib service in order to generate
154 * a blueprint. No 'Action' object here as there is nothig to set up.
156 public Future<Future<String>> buildBlueprint(Reader theCdump) {
158 final Recycler recycler = new Recycler();
159 Object template = null;
162 template = recycler.recycle(theCdump);
164 } catch (Exception x) {
165 return Futures.failedFuture(x);
168 JXPathContext jxroot = JXPathContext.newContext(template);
169 jxroot.setLenient(true);
171 //based on the output of ASDCCatalog the node description will contain the UUID of the resource declaring it
172 List uuids = (List) StreamSupport.stream(Spliterators.spliteratorUnknownSize(jxroot.iterate("topology_template/node_templates/*/description"), 16), false).distinct().filter(desc -> desc != null)
173 //the desc contains the full URI and the resource uuid is the 5th path element
174 .map(desc -> desc.toString().split("/")[5]).collect(Collectors.toList());
176 //prepare fetching all archives/resource details
177 final Futures.Accumulator accumulator = new Futures.Accumulator();
178 uuids.stream().forEach(uuid -> {
179 UUID rid = UUID.fromString((String) uuid);
180 accumulator.add(this.asdc.getAssetArchive(ASDC.AssetType.resource, rid));
181 accumulator.add(this.asdc.getAsset(ASDC.AssetType.resource, rid, JSONObject.class));
184 final byte[] templateData = recycler.toString(template).getBytes(/*"UTF-8"*/);
185 //retrieve all resource archive+details, prepare blueprint service request and send its request
186 return Futures.advance(accumulator.accumulate(), (List theArchives) -> {
187 Blueprinter.BlueprintAction action = blueprint.generateBlueprint();
188 processArtifacts(theArchives, (JSONObject theInfo, byte[] theData) -> new JSONObject().put(theInfo.getString("artifactName").split("\\.")[0], Base64Utils.encodeToString(theData)),
189 (Stream<JSONObject> theAssetArtifacts) -> theAssetArtifacts.reduce(new JSONObject(), ASDC::merge)).forEach(artifactInfo -> action.withModelInfo(artifactInfo));
191 return action.withTemplateData(templateData).execute();
195 public Future<Future<String>> buildBlueprintViaToscaLab(Reader theCdump) {
196 return processCdump(theCdump, (theTemplate, theArchives) -> {
197 Blueprinter.BlueprintAction action = blueprint.generateBlueprint();
198 processArtifacts(theArchives, (JSONObject theInfo, byte[] theData) -> new JSONObject().put(theInfo.getString("artifactName").split("\\.")[0], Base64Utils.encodeToString(theData)),
199 (Stream<JSONObject> theAssetArtifacts) -> theAssetArtifacts.reduce(new JSONObject(), ASDC::merge)).forEach(artifactInfo -> action.withModelInfo(artifactInfo));
201 return action.withTemplateData(Recycler.toString(theTemplate).getBytes()).execute();
206 private static class Tracker implements TargetLocator {
208 private static enum Position {
209 SCHEMA, TEMPLATE, TRANSLATE;
212 private static final int Positions = Position.values().length;
214 private List<Target> tgts = new ArrayList<Target>(3);
220 public boolean addSearchPath(URI theURI) {
224 public boolean addSearchPath(String thePath) {
228 public Iterable<URI> searchPaths() {
229 return Collections.emptyList();
232 protected int position(String... theKeys) {
233 for (String key : theKeys) {
234 if ("schema".equals(key)) {
235 return Position.SCHEMA.ordinal();
237 if ("template".equals(key)) {
238 return Position.TEMPLATE.ordinal();
240 if ("translate".equals(key)) {
241 return Position.TRANSLATE.ordinal();
247 public Target resolve(String theName) {
248 for (Target tgt : tgts) {
249 if (tgt != null && tgt.getName().equals(theName)) {
256 public void track(JSONObject theInfo, final byte[] theData) {
257 String uri = theInfo.getString("artifactURL").split("/")[5];
258 String name = theInfo.getString("artifactName"), desc = theInfo.getString("artifactDescription"), label = theInfo.getString("artifactLabel");
259 int pos = position(desc, label);
261 debugLogger.log(LogLevel.DEBUG, ASDCUtils.class.getName(), "Tracking {} at {}, {}", name, pos, theInfo.optString("artifactURL"));
264 tgts.set(pos, new Target(name, URI.create("asdc:" + uri + "/" + name)) {
266 public Reader open(){
267 return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(theData)));
273 public boolean hasSchema() {
274 return tgts.get(Position.SCHEMA.ordinal()) != null;
277 public Target schema() {
278 return tgts.get(Position.SCHEMA.ordinal());
281 public boolean hasTemplate() {
282 return tgts.get(Position.TEMPLATE.ordinal()) != null;
285 public Target template() {
286 return tgts.get(Position.TEMPLATE.ordinal());
289 public boolean hasTranslation() {
290 return tgts.get(Position.TRANSLATE.ordinal()) != null;
293 public Target translation() {
294 return tgts.get(Position.TRANSLATE.ordinal());
297 public void clear() {
298 if (tgts.isEmpty()) {
299 for (int i = 0; i < Positions; i++) {
303 Collections.fill(tgts, null);
308 private Checker buildChecker() {
310 return new Checker();
311 } catch (CheckerException cx) {
312 errLogger.log(LogLevel.ERROR, this.getClass().getName(), "CheckerException while creating Checker {}", cx);
317 public Future<Catalog> buildCatalog(Reader theCdump) {
320 //the purpose of the tracking is to be able to resolve import references within the 'space' of an
322 //processing order is important too so we 'order the targets: schema, template, translation
324 final Tracker tracker = new Tracker();
325 final Catalog catalog = Checker.buildCatalog();
327 return processCdump(theCdump, (theTemplate, theArchives) -> {
329 final Checker checker = buildChecker();
330 if (checker == null) {
333 checker.setTargetLocator(tracker);
335 processArtifacts(theArchives, (JSONObject theInfo, byte[] theData) -> {
336 tracker.track(theInfo, theData);
337 return (Catalog) null;
339 // aggregation: this is where the actual processing takes place now that
340 // we have all the targets
341 (Stream<Catalog> theAssetArtifacts) -> {
342 //the stream is full of nulls, ignore it, work with the tracker
345 if (tracker.hasSchema()) {
346 checker.check(tracker.schema(), catalog);
348 if (tracker.hasTemplate()) {
349 checker.check(tracker.template(), catalog);
351 if (tracker.hasTranslation()) {
352 checker.check(tracker.translation(), catalog);
354 } catch (CheckerException cx) {
355 //got to do better than this
356 errLogger.log(LogLevel.ERROR, ASDC.class.getName(),"CheckerException while checking catalog:{}", cx);
360 return checker.catalog();
363 Target cdump = new Target("cdump", URI.create("asdc:cdump"));
364 cdump.setTarget(theTemplate);
366 validateCatalog(catalog, checker, cdump);
372 private void validateCatalog(Catalog catalog, Checker checker, Target cdump) {
374 checker.validate(cdump, catalog);
375 } catch (CheckerException cx) {
376 errLogger.log(LogLevel.ERROR, ASDC.class.getName(),"CheckerException while building catalog:{}", cx);
380 /* The common process of recycling, retrieving all related artifacts and then doing 'something' */
381 private <T> Future<T> processCdump(Reader theCdump, BiFunction<Object, List, T> theProcessor) {
383 final Recycler recycler = new Recycler();
384 Object template = null;
386 template = recycler.recycle(theCdump);
388 } catch (Exception x) {
389 return Futures.failedFuture(x);
392 JXPathContext jxroot = JXPathContext.newContext(template);
393 jxroot.setLenient(true);
395 //based on the output of ASDCCatalog the node description will contain the UUID of the resource declaring it
396 //the desc contains the full URI and the resource uuid is the 5th path element
397 List uuids = (List) StreamSupport.stream(Spliterators.spliteratorUnknownSize(jxroot.iterate("topology_template/node_templates/*/description"), 16), false).distinct().filter(desc -> desc != null)
398 .map(desc -> desc.toString().split("/")[5]).collect(Collectors.toList());
400 //serialized fetch version
401 final Actions.Sequence sequencer = new Actions.Sequence();
402 uuids.stream().forEach(uuid -> {
403 UUID rid = UUID.fromString((String) uuid);
404 sequencer.add(this.asdc.getAssetArchiveAction(ASDC.AssetType.resource, rid));
405 sequencer.add(this.asdc.getAssetAction(ASDC.AssetType.resource, rid, JSONObject.class));
408 final Object tmpl = template;
409 return Futures.advance(sequencer.execute(), (List theArchives) -> theProcessor.apply(tmpl, theArchives));
412 private static <T> Stream<T> processArtifacts(List theArtifactData, BiFunction<JSONObject, byte[], T> theProcessor, Function<Stream<T>, T> theAggregator) {
414 Stream.Builder<T> assetBuilder = Stream.builder();
416 for (int i = 0; i < theArtifactData.size(); i = i + 2) { //cute old style loop
418 JSONObject assetInfo = (JSONObject) theArtifactData.get(i + 1);
419 byte[] assetData = (byte[]) theArtifactData.get(i + 0);
421 JSONArray artifacts = assetInfo.optJSONArray("artifacts");
423 Stream.Builder<T> artifactBuilder = Stream.builder();
425 try (ZipInputStream zipper = new ZipInputStream(new ByteArrayInputStream(assetData))){
426 //we process the artifacts in the order they are stored in the archive .. fugly
427 for (ZipEntry zipped = zipper.getNextEntry(); zipped != null; zipped = zipper.getNextEntry()) {
428 JSONObject artifactInfo = lookupArtifactInfo(artifacts, StringUtils.substringAfterLast(zipped.getName(), "/"));
429 if (artifactInfo != null) {
430 artifactBuilder.add(theProcessor.apply(artifactInfo, extractArtifactData(zipper)));
434 } catch (IOException iox) {
435 errLogger.log(LogLevel.ERROR, ASDC.class.getName(), "IOException: {}", iox);
439 if (theAggregator != null) {
440 assetBuilder.add(theAggregator.apply(artifactBuilder.build()));
442 artifactBuilder.build().forEach(entry -> assetBuilder.add(entry));
446 return assetBuilder.build();