2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
20 package org.onap.aai.util.genxsd;
22 import java.io.BufferedWriter;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.nio.charset.Charset;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.LinkedHashSet;
33 import java.util.SortedSet;
34 import java.util.StringTokenizer;
35 import java.util.TreeMap;
36 import java.util.TreeSet;
37 import java.util.Vector;
39 import javax.xml.parsers.ParserConfigurationException;
41 import org.apache.commons.lang3.StringUtils;
42 import org.onap.aai.edges.EdgeIngestor;
43 import org.onap.aai.edges.EdgeRule;
44 import org.onap.aai.edges.EdgeRuleQuery;
45 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
46 import org.onap.aai.exceptions.AAIException;
47 import org.onap.aai.setup.SchemaVersion;
48 import org.onap.aai.setup.SchemaVersions;
49 import org.onap.aai.nodes.NodeIngestor;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52 import org.w3c.dom.Element;
53 import org.w3c.dom.NodeList;
54 import org.xml.sax.SAXException;
55 import com.google.common.collect.Multimap;
58 public class YAMLfromOXM extends OxmFileProcessor {
59 private static final Logger logger = LoggerFactory.getLogger("YAMLfromOXM.class");
60 // private static StringBuffer totalPathSbAccumulator = new StringBuffer();
61 private static final String root = "../aai-schema/src/main/resources";
62 private static final String autoGenRoot = "aai-schema/src/main/resources";
63 private static final String generateTypeYAML = "yaml";
64 private static final String normalStartDir = "aai-core";
65 private static final String yaml_dir = (((System.getProperty("user.dir") != null) && (!System.getProperty("user.dir").contains(normalStartDir))) ? autoGenRoot : root) + "/aai_swagger_yaml";
66 private StringBuilder inventoryDefSb = null;
69 private String basePath;
71 public YAMLfromOXM(String basePath, SchemaVersions schemaVersions, NodeIngestor ni, EdgeIngestor ei){
72 super(schemaVersions, ni,ei);
73 this.basePath = basePath;
75 public void setOxmVersion(File oxmFile, SchemaVersion v) {
76 super.setOxmVersion(oxmFile, v);
78 public void setXmlVersion(String xml, SchemaVersion v){
79 super.setXmlVersion(xml, v);
82 public void setVersion(SchemaVersion v) {
87 public String getDocumentHeader() {
88 StringBuffer sb = new StringBuffer();
89 sb.append("swagger: \"2.0\"\ninfo:\n ");
90 sb.append("description: |");
91 if ( versionSupportsSwaggerDiff(v.toString())) {
92 sb.append("\n\n [Differences versus the previous schema version](" + "apidocs/aai_swagger_" + v.toString() + ".diff)");
94 sb.append("\n\n Copyright © 2017-18 AT&T Intellectual Property. All rights reserved.\n\n Licensed under the Creative Commons License, Attribution 4.0 Intl. (the "License"); you may not use this documentation except in compliance with the License.\n\n You may obtain a copy of the License at\n\n (https://creativecommons.org/licenses/by/4.0/)\n\n Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n\n This document is best viewed with Firefox or Chrome. Nodes can be found by appending /#/definitions/node-type-to-find to the path to this document. Edge definitions can be found with the node definitions.\n version: \"" + v.toString() +"\"\n");
95 sb.append(" title: Active and Available Inventory REST API\n");
96 sb.append(" license:\n name: Apache 2.0\n url: http://www.apache.org/licenses/LICENSE-2.0.html\n");
97 sb.append(" contact:\n name:\n url:\n email:\n");
98 sb.append("host:\nbasePath: " + basePath + "/" + v.toString() + "\n");
99 sb.append("schemes:\n - https\npaths:\n");
100 return sb.toString();
103 protected void init() throws ParserConfigurationException, SAXException, IOException, AAIException, FileNotFoundException, EdgeRuleNotFoundException {
108 public String process() throws ParserConfigurationException, SAXException, IOException, AAIException, FileNotFoundException, EdgeRuleNotFoundException {
109 StringBuffer sb = new StringBuffer();
110 StringBuffer pathSb = new StringBuffer();
113 } catch(Exception e) {
114 logger.error( "Error initializing " + this.getClass());
117 pathSb.append(getDocumentHeader());
118 StringBuffer definitionsSb = new StringBuffer();
119 for ( int i = 0; i < javaTypeNodes.getLength(); ++ i ) {
120 XSDElement javaTypeElement = new XSDElement((Element)javaTypeNodes.item(i));
121 logger.debug("External: "+javaTypeElement.getAttribute("name"));
122 String javaTypeName = javaTypeElement.name();
123 if ( javaTypeName == null ) {
124 String msg = "Invalid OXM file: <java-type> has no name attribute in " + oxmFile;
126 throw new AAIException(msg);
128 namespaceFilter.add(getXmlRootElementName(javaTypeName));
129 //Skip any type that has already been processed(recursion could be the reason)
130 if ( generatedJavaType.containsKey(getXmlRootElementName(javaTypeName)) ) {
133 processJavaTypeElementSwagger( javaTypeName, javaTypeElement, pathSb,
134 definitionsSb, null, null, null, null, null, null);
138 sb.append(appendDefinitions());
139 PutRelationPathSet prp = new PutRelationPathSet(v);
140 prp.generateRelations(ei);
141 return sb.toString();
144 public String appendDefinitions() {
145 return appendDefinitions(null);
148 public String appendDefinitions(Set<String> namespaceFilter) {
150 if ( inventoryDefSb != null ) {
151 javaTypeDefinitions.put("inventory", inventoryDefSb.toString());
153 StringBuffer sb = new StringBuffer("definitions:\n");
154 Map<String, String> sortedJavaTypeDefinitions = new TreeMap<String, String>(javaTypeDefinitions);
155 for (Map.Entry<String, String> entry : sortedJavaTypeDefinitions.entrySet()) {
156 // logger.info("Key: "+entry.getKey()+"Value: "+ entry.getValue());
157 if(namespaceFilter != null && entry.getKey().matches("service-capabilities")) {
158 for(String tally : namespaceFilter) { logger.debug("Marker: "+tally);}
160 if(namespaceFilter != null && (! namespaceFilter.contains(entry.getKey()))) {
163 logger.debug("Key: "+entry.getKey()+"Test: "+ (entry.getKey() == "relationship"));
164 if(entry.getKey().matches("relationship")) {
165 String jb=entry.getValue();
166 logger.debug("Value: "+jb);
167 int ndx=jb.indexOf("related-to-property:");
169 jb=jb.substring(0, ndx);
170 jb=jb.replaceAll(" +$", "");
172 logger.debug("Value-after: "+jb);
176 sb.append(entry.getValue());
179 sb.append("patchDefinitions:\n");
180 for (Map.Entry<String, String> entry : sortedJavaTypeDefinitions.entrySet()) {
181 if(namespaceFilter != null && (! namespaceFilter.contains(entry.getKey()))) {
184 String jb=entry.getValue().replaceAll("/definitions/", "/patchDefinitions/");
185 int ndx=jb.indexOf("relationship-list:");
187 jb=jb.substring(0, ndx);
188 jb=jb.replaceAll(" +$", "");
190 int ndx1=jb.indexOf("resource-version:");
191 logger.debug("Key: "+entry.getKey()+" index: " + ndx1);
192 logger.debug("Value: "+jb);
194 jb=jb.substring(0, ndx1);
195 jb=jb.replaceAll(" +$", "");
197 logger.debug("Value-after: "+jb);
201 sb.append("getDefinitions:\n");
202 for (Map.Entry<String, String> entry : sortedJavaTypeDefinitions.entrySet()) {
203 if(namespaceFilter != null && (! namespaceFilter.contains(entry.getKey()))) {
206 String jb=entry.getValue().replaceAll("/definitions/", "/getDefinitions/");
209 return sb.toString();
212 private String processJavaTypeElementSwagger( String javaTypeName, Element javaTypeElement,
213 StringBuffer pathSb, StringBuffer definitionsSb, String path, String tag, String opId,
214 String getItemName, StringBuffer pathParams, String validEdges) {
216 String xmlRootElementName = getXMLRootElementName(javaTypeElement);
217 StringBuilder definitionsLocalSb = new StringBuilder(256);
219 String useTag = null;
220 String useOpId = null;
221 logger.debug("tag="+tag);
225 case "ServiceDesignAndCreation":
227 case "LicenseManagement":
228 case "CloudInfrastructure":
232 logger.debug("javaTypeName="+javaTypeName);
237 if ( !javaTypeName.equals("Inventory") ) {
238 if ( javaTypeName.equals("AaiInternal"))
241 useOpId = javaTypeName;
243 useOpId = opId + javaTypeName;
245 useTag = javaTypeName;
247 path = xmlRootElementName.equals("inventory") ? "" : (path == null) ? "/" + xmlRootElementName : path + "/" + xmlRootElementName;
248 XSDJavaType javaType = new XSDJavaType(javaTypeElement);
249 if ( getItemName != null) {
250 if ( getItemName.equals("array") )
251 return javaType.getArrayType();
253 return javaType.getItemName();
256 NodeList parentNodes = javaTypeElement.getElementsByTagName("java-attributes");
257 if ( parentNodes.getLength() == 0 ) {
258 logger.debug( "no java-attributes for java-type " + javaTypeName);
262 String pathDescriptionProperty = javaType.getPathDescriptionProperty();
263 String container = javaType.getContainerProperty();
264 Vector<String> indexedProps = javaType.getIndexedProps();
265 Vector<String> containerProps = new Vector<String>();
266 if(container != null) {
267 logger.debug("javaTypeName " + javaTypeName + " container:" + container +" indexedProps:"+indexedProps);
270 Element parentElement = (Element)parentNodes.item(0);
271 NodeList xmlElementNodes = parentElement.getElementsByTagName("xml-element");
273 StringBuffer sbParameters = new StringBuffer();
274 StringBuffer sbRequired = new StringBuffer();
277 StringBuffer sbProperties = new StringBuffer();
279 if ( appliedPaths.containsKey(path))
282 StringTokenizer st = new StringTokenizer(path, "/");
283 logger.debug("path: " + path + " st? " + st.toString());
284 if ( st.countTokens() > 1 && getItemName == null ) {
285 logger.debug("appliedPaths: " + appliedPaths + " containsKey? " + appliedPaths.containsKey(path));
286 appliedPaths.put(path, xmlRootElementName);
289 Vector<String> addTypeV = null;
290 for ( int i = 0; i < xmlElementNodes.getLength(); ++i ) {
291 XSDElement xmlElementElement = new XSDElement((Element)xmlElementNodes.item(i));
292 if ( !xmlElementElement.getParentNode().isSameNode(parentElement))
294 String elementDescription=xmlElementElement.getPathDescriptionProperty();
295 if(getItemName == null) {
296 addTypeV = xmlElementElement.getAddTypes(v.toString());
298 if ( "true".equals(xmlElementElement.getAttribute("xml-key"))) {
299 path += "/{" + xmlElementElement.getAttribute("name") + "}";
301 logger.debug("path: " + path);
302 logger.debug( "xmlElementElement.getAttribute(required):"+xmlElementElement.getAttribute("required") );
304 if ( ("true").equals(xmlElementElement.getAttribute("required"))) {
305 if ( requiredCnt == 0 )
306 sbRequired.append(" required:\n");
308 if ( addTypeV == null || addTypeV.isEmpty()) {
309 sbRequired.append(" - " + xmlElementElement.getAttribute("name") + "\n");
311 for ( int k = 0; k < addTypeV.size(); ++k ) {
312 sbRequired.append(" - " + getXmlRootElementName(addTypeV.elementAt(k)) + ":\n");
317 if ( "true".equals(xmlElementElement.getAttribute("xml-key")) ) {
318 sbParameters.append(xmlElementElement.getPathParamYAML(elementDescription));
320 if ( indexedProps != null
321 && indexedProps.contains(xmlElementElement.getAttribute("name") ) ) {
322 containerProps.add(xmlElementElement.getQueryParamYAML());
323 GetOperation.addContainerProps(container, containerProps);
325 if ( xmlElementElement.isStandardType()) {
326 sbProperties.append(xmlElementElement.getTypePropertyYAML());
330 StringBuffer newPathParams = new StringBuffer((pathParams == null ? "" : pathParams.toString())+sbParameters.toString());
331 for ( int k = 0; addTypeV != null && k < addTypeV.size(); ++k ) {
332 String addType = addTypeV.elementAt(k);
333 namespaceFilter.add(getXmlRootElementName(addType));
334 logger.debug("addType: "+ addType);
336 if ( opId == null || !opId.contains(addType)) {
337 processJavaTypeElementSwagger( addType, getJavaTypeElementSwagger(addType),
338 pathSb, definitionsSb, path, tag == null ? useTag : tag, useOpId, null,
339 newPathParams, validEdges);
341 // need item name of array
342 String itemName = processJavaTypeElementSwagger( addType, getJavaTypeElementSwagger(addType),
343 pathSb, definitionsSb, path, tag == null ? useTag : tag, useOpId,
344 "array", null, null );
346 if ( itemName != null ) {
347 if ( addType.equals("AaiInternal") ) {
348 logger.debug( "addType AaiInternal, skip properties");
350 } else if ( getItemName == null) {
352 sbProperties.append(" " + getXmlRootElementName(addType) + ":\n");
353 sbProperties.append(" type: array\n items:\n");
354 sbProperties.append(" $ref: \"#/definitions/" + (itemName == "" ? "inventory-item-data" : itemName) + "\"\n");
355 if ( StringUtils.isNotEmpty(elementDescription) )
356 sbProperties.append(" description: " + elementDescription + "\n");
359 if ( ("java.util.ArrayList").equals(xmlElementElement.getAttribute("container-type"))) {
360 // need properties for getXmlRootElementName(addType)
361 namespaceFilter.add(getXmlRootElementName(addType));
362 newPathParams = new StringBuffer((pathParams == null ? "" : pathParams.toString())+sbParameters.toString());
363 processJavaTypeElementSwagger( addType, getJavaTypeElementSwagger(addType),
364 pathSb, definitionsSb, path, tag == null ? useTag : tag, useOpId,
365 null, newPathParams, validEdges );
366 sbProperties.append(" " + getXmlRootElementName(addType) + ":\n");
367 sbProperties.append(" type: array\n items: \n");
368 sbProperties.append(" $ref: \"#/definitions/" + getXmlRootElementName(addType) + "\"\n");
369 if ( StringUtils.isNotEmpty(elementDescription) )
370 sbProperties.append(" description: " + elementDescription + "\n");
373 //Make sure certain types added to the filter don't appear
374 if(nodeFilter.contains(getXmlRootElementName(addType))) {
377 sbProperties.append(" " + getXmlRootElementName(addType) + ":\n");
378 sbProperties.append(" type: object\n");
379 sbProperties.append(" $ref: \"#/definitions/" + getXmlRootElementName(addType) + "\"\n");
382 if ( StringUtils.isNotEmpty(elementDescription) )
383 sbProperties.append(" description: " + elementDescription + "\n");
389 if ( sbParameters.toString().length() > 0 ) {
390 if ( pathParams == null )
391 pathParams = new StringBuffer();
392 pathParams.append(sbParameters);
394 GetOperation get = new GetOperation(useOpId, xmlRootElementName, tag, path, pathParams == null ? "" : pathParams.toString());
395 pathSb.append(get.toString());
396 logger.debug("opId vs useOpId:"+opId+" vs "+useOpId+" PathParams="+pathParams);
398 PutOperation put = new PutOperation(useOpId, xmlRootElementName, tag, path, pathParams == null ? "" : pathParams.toString(), this.v);
399 pathSb.append(put.toString());
401 PatchOperation patch = new PatchOperation(useOpId, xmlRootElementName, tag, path, pathParams == null ? "" : pathParams.toString());
402 pathSb.append(patch.toString());
404 DeleteOperation del = new DeleteOperation(useOpId, xmlRootElementName, tag, path, pathParams == null ? "" : pathParams.toString());
405 pathSb.append(del.toString());
406 if ( generatedJavaType.containsKey(xmlRootElementName) ) {
407 logger.debug("xmlRootElementName(1)="+xmlRootElementName);
411 boolean processingInventoryDef = false;
412 if ( xmlRootElementName.equals("inventory")) {
413 // inventory properties for each oxm to be concatenated
414 processingInventoryDef = true;
415 if ( inventoryDefSb == null ) {
416 inventoryDefSb = new StringBuilder();
417 definitionsSb.append(" " + xmlRootElementName + ":\n");
418 definitionsLocalSb.append(" " + xmlRootElementName + ":\n");
419 definitionsLocalSb.append(" properties:\n");
423 definitionsSb.append(" " + xmlRootElementName + ":\n");
424 definitionsLocalSb.append(" " + xmlRootElementName + ":\n");
426 // Collection<EdgeDescription> edges = edgeRuleSet.getEdgeRules(xmlRootElementName );
427 DeleteFootnoteSet footnotes = new DeleteFootnoteSet(xmlRootElementName);
428 StringBuffer sbEdge = new StringBuffer();
429 LinkedHashSet<String> preventDelete = new LinkedHashSet<String>();
431 String nodeCaption = new String(" ###### Related Nodes\n");
433 EdgeRuleQuery q = new EdgeRuleQuery.Builder(xmlRootElementName).version(v).fromOnly().build();
434 Multimap<String, EdgeRule> results = ei.getRules(q);
435 SortedSet<String> ss=new TreeSet<String>(results.keySet());
436 sbEdge.append(nodeCaption);
438 for(String key : ss) {
439 results.get(key).stream().filter((i) -> (i.getFrom().equals(xmlRootElementName) && (! i.isPrivateEdge()))).forEach((i) ->{ logger.info(new String(new StringBuffer(" - TO ").append(i.getTo()).append(i.getDirection().toString()).append(i.getContains())));} );
440 results.get(key).stream().filter((i) -> (i.getFrom().equals(xmlRootElementName) && (! i.isPrivateEdge()))).forEach((i) ->{ sbEdge.append(" - TO "+i.getTo()); EdgeDescription ed = new EdgeDescription(i); String footnote = ed.getAlsoDeleteFootnote(xmlRootElementName); sbEdge.append(ed.getRelationshipDescription("TO", xmlRootElementName)+footnote+"\n"); if(StringUtils.isNotEmpty(footnote)) footnotes.add(footnote);} );
441 results.get(key).stream().filter((i) -> (i.getFrom().equals(xmlRootElementName) && (! i.isPrivateEdge() && i.getPreventDelete().equals("OUT")))).forEach((i) ->{ preventDelete.add(i.getTo().toUpperCase());} );
443 } catch(Exception e) {
444 logger.debug("xmlRootElementName: "+xmlRootElementName+"\n"+e);
447 EdgeRuleQuery q1 = new EdgeRuleQuery.Builder(xmlRootElementName).version(v).toOnly().build();
448 Multimap<String, EdgeRule> results = ei.getRules(q1);
449 SortedSet<String> ss=new TreeSet<String>(results.keySet());
450 sbEdge.append(nodeCaption);
451 for(String key : ss) {
452 results.get(key).stream().filter((i) -> (i.getTo().equals(xmlRootElementName) && (! i.isPrivateEdge()))).forEach((i) ->{ sbEdge.append(" - FROM "+i.getFrom()); EdgeDescription ed = new EdgeDescription(i); String footnote = ed.getAlsoDeleteFootnote(xmlRootElementName); sbEdge.append(ed.getRelationshipDescription("FROM", xmlRootElementName)+footnote+"\n"); if(StringUtils.isNotEmpty(footnote)) footnotes.add(footnote);} );
453 results.get(key).stream().filter((i) -> (i.getTo().equals(xmlRootElementName) && (! i.isPrivateEdge()))).forEach((i) ->{ logger.info(new String(new StringBuffer(" - FROM ").append(i.getFrom()).append(i.getDirection().toString()).append(i.getContains())));} );
454 results.get(key).stream().filter((i) -> (i.getTo().equals(xmlRootElementName) && (! i.isPrivateEdge() && i.getPreventDelete().equals("IN")))).forEach((i) ->{ preventDelete.add(i.getFrom().toUpperCase());} );
456 } catch(Exception e) {
457 logger.debug("xmlRootElementName: "+xmlRootElementName+"\n"+e);
459 if(preventDelete.size() > 0) {
460 prevent = xmlRootElementName.toUpperCase()+" cannot be deleted if related to "+String.join(",",preventDelete);
461 logger.debug(prevent);
464 if(StringUtils.isNotEmpty(prevent)) {
465 footnotes.add(prevent);
467 if(footnotes.footnotes.size() > 0) {
468 sbEdge.append(footnotes.toString());
470 validEdges = sbEdge.toString();
472 // Handle description property. Might have a description OR valid edges OR both OR neither.
473 // Only put a description: tag if there is at least one.
474 if (StringUtils.isNotEmpty(pathDescriptionProperty) || StringUtils.isNotEmpty(validEdges) ) {
475 definitionsSb.append(" description: |\n");
476 definitionsLocalSb.append(" description: |\n");
478 if ( pathDescriptionProperty != null ) {
479 definitionsSb.append(" " + pathDescriptionProperty + "\n" );
480 definitionsLocalSb.append(" " + pathDescriptionProperty + "\n" );
482 definitionsSb.append(validEdges);
483 definitionsLocalSb.append(validEdges);
486 if ( requiredCnt > 0 ) {
487 definitionsSb.append(sbRequired);
488 definitionsLocalSb.append(sbRequired);
491 if ( propertyCnt > 0 ) {
492 definitionsSb.append(" properties:\n");
493 definitionsSb.append(sbProperties);
494 if ( !processingInventoryDef) {
495 definitionsLocalSb.append(" properties:\n");
497 definitionsLocalSb.append(sbProperties);
500 namespaceFilter.add(xmlRootElementName);
501 if ( xmlRootElementName.equals("inventory") ) {
502 //will add to javaTypeDefinitions at end
503 inventoryDefSb.append(definitionsLocalSb.toString());
505 javaTypeDefinitions.put(xmlRootElementName, definitionsLocalSb.toString());
507 } catch (Exception e) {
510 if ( xmlRootElementName.equals("inventory") ) {
511 logger.trace("skip xmlRootElementName(2)="+xmlRootElementName);
514 generatedJavaType.put(xmlRootElementName, null);
516 if( validTag(javaTypeName) && javaTypeName == useTag && tag == null) {
517 String nameSpaceResult = getDocumentHeader()+pathSb.toString()+appendDefinitions(namespaceFilter);
518 writeYAMLfile(javaTypeName, nameSpaceResult);
519 totalPathSbAccumulator.append(pathSb);
520 pathSb.delete(0, pathSb.length());
521 namespaceFilter.clear();
524 logger.trace("xmlRootElementName(2)="+xmlRootElementName);
528 private void writeYAMLfile(String outfileName, String fileContent) {
529 outfileName = (StringUtils.isEmpty(outfileName)) ? "aai_swagger" : outfileName;
530 outfileName = (outfileName.lastIndexOf(File.separator) == -1) ? yaml_dir + File.separator +outfileName+"_" + v.toString() + "." + generateTypeYAML : outfileName;
531 File outfile = new File(outfileName);
532 File parentDir = outfile.getParentFile();
533 if(parentDir != null && ! parentDir.exists())
536 outfile.createNewFile();
537 } catch (IOException e) {
538 logger.error( "Exception creating output file " + outfileName);
541 BufferedWriter bw = null;
543 Charset charset = Charset.forName("UTF-8");
544 Path path = Paths.get(outfileName);
545 bw = Files.newBufferedWriter(path, charset);
546 bw.write(fileContent);
550 } catch ( IOException e) {
551 logger.error( "Exception writing output file " + outfileName);
556 public boolean validTag(String tag) {
562 case "ServiceDesignAndCreation":
564 case "LicenseManagement":
565 case "CloudInfrastructure":