1 /*******************************************************************************
2 * Copyright (c) 2013 University of Stuttgart.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * and the Apache License 2.0 which both accompany this distribution,
6 * and are available at http://www.eclipse.org/legal/epl-v10.html
7 * and http://www.apache.org/licenses/LICENSE-2.0
10 * Oliver Kopp - initial API and implementation
11 *******************************************************************************/
12 package org.eclipse.winery.common;
14 import java.io.ByteArrayOutputStream;
15 import java.io.StringWriter;
16 import java.io.UnsupportedEncodingException;
18 import java.net.URISyntaxException;
19 import java.net.URLDecoder;
20 import java.net.URLEncoder;
21 import java.util.List;
22 import java.util.SortedMap;
23 import java.util.SortedSet;
24 import java.util.TreeMap;
25 import java.util.TreeSet;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
29 import javax.xml.bind.JAXBContext;
30 import javax.xml.bind.JAXBElement;
31 import javax.xml.bind.JAXBException;
32 import javax.xml.bind.Marshaller;
33 import javax.xml.bind.annotation.XmlRootElement;
34 import javax.xml.bind.annotation.XmlSchema;
35 import javax.xml.namespace.QName;
36 import javax.xml.transform.OutputKeys;
37 import javax.xml.transform.Result;
38 import javax.xml.transform.Source;
39 import javax.xml.transform.Transformer;
40 import javax.xml.transform.TransformerConfigurationException;
41 import javax.xml.transform.TransformerException;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.dom.DOMSource;
44 import javax.xml.transform.stream.StreamResult;
46 import org.apache.commons.lang3.StringUtils;
47 import org.apache.taglibs.standard.functions.Functions;
48 import org.eclipse.winery.common.ids.GenericId;
49 import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
50 import org.eclipse.winery.common.ids.definitions.EntityTemplateId;
51 import org.eclipse.winery.common.ids.definitions.EntityTypeId;
52 import org.eclipse.winery.common.ids.definitions.EntityTypeImplementationId;
53 import org.eclipse.winery.common.ids.definitions.PolicyTemplateId;
54 import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
55 import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
56 import org.eclipse.winery.common.ids.definitions.imports.GenericImportId;
57 import org.eclipse.winery.common.ids.definitions.imports.XSDImportId;
58 import org.eclipse.winery.common.ids.elements.TOSCAElementId;
59 import org.eclipse.winery.model.tosca.TEntityType;
60 import org.eclipse.winery.model.tosca.TExtensibleElements;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63 import org.w3c.dom.Element;
67 private static final Logger logger = LoggerFactory.getLogger(Util.class);
69 public static final String FORBIDDEN_CHARACTER_REPLACEMENT = "_";
72 public static String URLdecode(String s) {
74 return URLDecoder.decode(s, "UTF-8");
75 } catch (UnsupportedEncodingException e) {
76 throw new IllegalStateException();
80 public static String URLencode(String s) {
82 return URLEncoder.encode(s, "UTF-8");
83 } catch (UnsupportedEncodingException e) {
84 throw new IllegalStateException();
88 public static String DoubleURLencode(String s) {
89 return Util.URLencode(Util.URLencode(s));
93 * Encodes the namespace and the localname of the given qname, separated by
96 * @return <double encoded namespace>"/"<double encoded localname>
98 public static String DoubleURLencode(QName qname) {
99 String ns = Util.DoubleURLencode(qname.getNamespaceURI());
100 String localName = Util.DoubleURLencode(qname.getLocalPart());
101 return ns + "/" + localName;
104 public static boolean isRelativeURI(String uri) {
108 } catch (URISyntaxException e) {
109 Util.logger.debug(e.getMessage(), e);
113 return !u.isAbsolute();
117 * @param c the element directly nested below a definitions element in XML
119 public static String getURLpathFragmentForCollection(Class<? extends TExtensibleElements> c) {
120 String res = c.getName().toLowerCase();
121 int lastDot = res.lastIndexOf('.');
122 // classname is something like <package>.T<type>. We are only interested
123 // in "<type>". Therefore "+2" from the dot onwards
124 res = res.substring(lastDot + 2);
129 public static String getEverythingBetweenTheLastDotAndBeforeId(Class<? extends GenericId> cls) {
130 String res = cls.getName();
131 // Everything between the last "." and before "Id" is the Type
132 int dotIndex = res.lastIndexOf('.');
133 assert (dotIndex >= 0);
134 return res.substring(dotIndex + 1, res.length() - "Id".length());
137 public static String getTypeForElementId(Class<? extends TOSCAElementId> idClass) {
138 return Util.getEverythingBetweenTheLastDotAndBeforeId(idClass);
142 * @return Singular type name for the given id. E.g., "ServiceTemplateId"
143 * gets "ServiceTemplate"
145 public static String getTypeForComponentId(Class<? extends TOSCAComponentId> idClass) {
146 return Util.getEverythingBetweenTheLastDotAndBeforeId(idClass);
150 * Returns the root path fragment for the given
151 * AbstractComponentIntanceResource
153 * With trailing slash
155 * @return [ComponentName]s/
157 public static String getRootPathFragment(Class<? extends TOSCAComponentId> idClass) {
158 // quick handling of imports special case
159 // in the package naming, all other component instances have a this intermediate location, but not in the URLs
160 // The package handling is in {@link org.eclipse.winery.repository.Utils.getIntermediateLocationStringForType(String, String)}
162 if (GenericImportId.class.isAssignableFrom(idClass)) {
163 // this fires if idClass is a sub class from ImportCollectionId
164 // special treatment for imports
166 if (XSDImportId.class.isAssignableFrom(idClass)) {
167 res = res + "http%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema/";
169 throw new IllegalStateException("Not possible to determine local storage for generic imports class");
171 // we have the complete root path fragment
176 res = res + Util.getTypeForComponentId(idClass);
177 res = res.toLowerCase();
184 * Just calls @link{qname2href}
186 * Introduced because of JSP error
187 * "The method qname2href(String, Class<? extends TExtensibleElements>, QName) in the type Util is not applicable for the arguments (String, Class<TNodeType>, QName, String)"
189 public static String qname2hrefWithName(String repositoryUrl, Class<? extends TExtensibleElements> element, QName qname, String name) {
190 return Util.qname2href(repositoryUrl, element, qname, name);
195 * @param repositoryUrl the URL to the repository
196 * @param element the element directly nested below a definitions element in
198 * @param qname the QName of the element
199 * @param name (optional) if not null, the name to display as text in the
200 * reference. Default: localName of the QName
201 * @return an <code>a</code> HTML element pointing to the given id
203 public static String qname2href(String repositoryUrl, Class<? extends TExtensibleElements> element, QName qname, String name) {
204 if (StringUtils.isEmpty(repositoryUrl)) {
205 throw new IllegalArgumentException("Repository URL must not be empty.");
207 if (element == null) {
208 throw new IllegalArgumentException("Element class must not be null.");
214 String absoluteURL = repositoryUrl + "/" + Util.getURLpathFragmentForCollection(element) + "/" + Util.DoubleURLencode(qname.getNamespaceURI()) + "/" + Util.DoubleURLencode(qname.getLocalPart());
217 // fallback if no name is given
218 name = qname.getLocalPart();
221 name = Functions.escapeXml(name);
223 String res = "<a target=\"_blank\" data-qname=\"" + qname + "\" href=\"" + absoluteURL + "\">" + name + "</a>";
229 * @param repositoryUrl the URL to the repository
230 * @param element the element directly nested below a definitions element in
232 * @param qname the QName of the element
233 * @return an <code>a</code> HTML element pointing to the given id
235 public static String qname2href(String repositoryUrl, Class<? extends TExtensibleElements> element, QName qname) {
236 return Util.qname2href(repositoryUrl, element, qname, null);
240 * Returns a visual rendering of minInstances
242 * @param minInstances the value to render
244 public static String renderMinInstances(Integer minInstances) {
245 if ((minInstances == null) || (minInstances == 1)) {
246 // == null: default value: display nothing -- *never* happens:
247 // the function *always* returns 1 even, if no explicit value is set. Therefore, we also display "" if the default value 1 is set
250 return Integer.toString(minInstances);
255 * Returns a visual rendering of maxInstances
257 * @param maxInstances the value to render
259 public static String renderMaxInstances(String maxInstances) {
260 if ((maxInstances == null) || (maxInstances.equals("1"))) {
261 // default value display nothing
262 // "1" is returned even if no explicit value has been set.
264 } else if (maxInstances.equals("unbounded")) {
267 // maxInstance is a plain integer
274 * @return the local name of a Class representing a TOSCA element
276 private static String getLocalName(@SuppressWarnings("rawtypes") Class clazz) {
277 String localName = clazz.getName();
278 // a class defined within another class is written as superclass$class. E.g., EntityTemplate$Properties
279 // We use the real class name
280 int pos = localName.lastIndexOf('$');
282 pos = localName.lastIndexOf('.');
284 localName = localName.substring(pos + 1);
285 if (localName.equals("TDocumentation")) {
286 // special case for documentation: the local name starts with a lower case letter
287 localName = "documentation";
288 } else if (localName.startsWith("T")) {
289 localName = localName.substring(1);
294 public static <T extends Object> JAXBElement<T> getJAXBElement(Class<T> clazz, T obj) {
295 String namespace = null;
296 XmlRootElement xmlRootElement = clazz.getAnnotation(XmlRootElement.class);
297 if (xmlRootElement != null) {
298 namespace = xmlRootElement.namespace();
299 if ("##default".equals(namespace)) {
300 XmlSchema xmlSchema = clazz.getPackage().getAnnotation(XmlSchema.class);
301 if (xmlSchema != null) {
302 namespace = xmlSchema.namespace();
304 // trigger default handling
309 if (namespace == null) {
310 // fallback non-specified namespaces
311 namespace = org.eclipse.winery.common.constants.Namespaces.TOSCA_NAMESPACE;
313 String localName = Util.getLocalName(clazz);
314 QName qname = new QName(namespace, localName);
315 JAXBElement<T> rootElement = new JAXBElement<T>(qname, clazz, obj);
320 * Method similar to {@link
321 * org.eclipse.winery.repository.Utils.getXMLAsString(Class, Object)}.
325 * <li>XML processing instruction is not included in the header</li>
326 * <li>JAXBcontext is created at each call</li>
329 public static <T extends Object> String getXMLAsString(Class<T> clazz, T obj) throws Exception {
330 // copied from Utils java, but we create an own JAXBcontext here
331 // JAXBSupport cannot be used as this relies on a MockElement, which we do not want to factor out to winery.common
335 // For winery classes, eventually the package+jaxb.index method could be better. See http://stackoverflow.com/a/3628525/873282
337 context = JAXBContext.newInstance(
340 } catch (JAXBException e) {
341 throw new IllegalStateException(e);
344 JAXBElement<T> rootElement = Util.getJAXBElement(clazz, obj);
345 Marshaller m = context.createMarshaller();
346 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
347 m.setProperty(Marshaller.JAXB_FRAGMENT, true);
348 // m.setProperty("com.sun.xml.bind.namespacePrefixMapper", JAXBSupport.prefixMapper);
350 StringWriter w = new StringWriter();
352 m.marshal(rootElement, w);
353 } catch (JAXBException e) {
354 throw new IllegalStateException(e);
356 String res = w.toString();
360 public static String getXMLAsString(Element el) {
361 TransformerFactory tf = TransformerFactory.newInstance();
364 t = tf.newTransformer();
365 } catch (TransformerConfigurationException e) {
366 throw new IllegalStateException("Could not instantiate Transformer", e);
368 t.setOutputProperty(OutputKeys.INDENT, "yes");
369 Source source = new DOMSource(el);
370 ByteArrayOutputStream os = new ByteArrayOutputStream();
371 Result target = new StreamResult(os);
373 t.transform(source, target);
374 } catch (TransformerException e) {
375 Util.logger.debug(e.getMessage(), e);
376 throw new IllegalStateException("Could not transform dom node to string", e);
378 return os.toString();
382 * Determines whether the instance belonging to the given id supports the
383 * "name" attribute. This cannot be done using the super class as the TOSCA
384 * specification treats that differently in the case of EntityTemplates
386 * NOTE: The respective subclasses of AbstractComponentInstanceResource have
387 * to implement {@link org.eclipse.winery.repository.resources.IHasName}
389 * @param id the id to test
390 * @return true if the TOSCA model class belonging to the given id supports
391 * the method "getName()" in addition to "getId()"
393 public static boolean instanceSupportsNameAttribute(Class<? extends TOSCAComponentId> idClass) {
394 if (ServiceTemplateId.class.isAssignableFrom(idClass)) {
396 } else if ((EntityTypeId.class.isAssignableFrom(idClass)) || (EntityTypeImplementationId.class.isAssignableFrom(idClass))) {
397 // name is available, but no id attribute
399 } else if (GenericImportId.class.isAssignableFrom(idClass)) {
402 assert (EntityTemplateId.class.isAssignableFrom(idClass));
403 if (ArtifactTemplateId.class.isAssignableFrom(idClass)) {
405 } else if (PolicyTemplateId.class.isAssignableFrom(idClass)) {
408 throw new IllegalStateException("Unimplemented branch to determine if getName() exists");
413 public static String getLastURIPart(String loc) {
414 int posSlash = loc.lastIndexOf('/');
415 String fileName = loc.substring(posSlash + 1);
420 * Determines a color belonging to the given name
422 public static String getColor(String name) {
423 int hash = name.hashCode();
424 // trim to 3*8=24 bits
425 hash = hash & 0xFFFFFF;
426 // check if color is more than #F0F0F0, i.e., too light
427 if (((hash & 0xF00000) >= 0xF00000) && (((hash & 0x00F000) >= 0x00F000) && ((hash & 0x0000F0) >= 0x0000F0))) {
428 // set one high bit to zero for each channel. That makes the overall color darker
429 hash = hash & 0xEFEFEF;
431 String colorStr = String.format("#%06x", hash);
436 * Determines the name of the CSS class used for relationshipTypes at
437 * nodeTemplateRenderer.tag
439 public static String makeCSSName(String namespace, String localName) {
440 // according to http://stackoverflow.com/a/79022/873282 everything is allowed
441 // However, {namespace}id does NOT work
442 String res = namespace + "_" + localName;
443 res = res.replaceAll("[^\\w\\d_]", "_");
448 * @see {@link org.eclipse.winery.common.Util.makeCSSName(String, String)}
450 public static String makeCSSName(QName qname) {
451 return Util.makeCSSName(qname.getNamespaceURI(), qname.getLocalPart());
454 public static SortedMap<String, SortedSet<String>> convertQNameListToNamespaceToLocalNameList(List<QName> list) {
455 SortedMap<String, SortedSet<String>> res = new TreeMap<>();
456 for (QName qname : list) {
457 SortedSet<String> localNameSet = res.get(qname.getNamespaceURI());
458 if (localNameSet == null) {
459 localNameSet = new TreeSet<>();
460 res.put(qname.getNamespaceURI(), localNameSet);
462 localNameSet.add(qname.getLocalPart());
467 public static String namespaceToJavaPackage(String namespace) {
470 uri = new URI(namespace);
471 } catch (URISyntaxException e) {
472 Util.logger.debug(e.getMessage(), e);
473 return "uri.invalid";
475 StringBuilder sb = new StringBuilder();
477 String host = uri.getHost();
479 Util.addReversed(sb, host, "\\.");
482 String path = uri.getPath();
483 if (!path.equals("")) {
484 if (path.startsWith("/")) {
485 // remove first slash
486 path = path.substring(1);
489 // and then handle the string
490 Util.addAsIs(sb, path, "/");
493 // remove the final dot
494 sb.replace(sb.length() - 1, sb.length(), "");
496 return Util.cleanName(sb.toString());
499 private static String cleanName(String s) {
500 // TODO: Integrate with other name cleaning functions. "." should not be replaced as it is used as separator in the java package name
502 return s.replace(":", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
503 .replace("/", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
504 .replace(" ", Util.FORBIDDEN_CHARACTER_REPLACEMENT)
505 .replace("-", Util.FORBIDDEN_CHARACTER_REPLACEMENT);
513 * <li>http://www.w3.org/TR/REC-xml-names/#NT-NCName</li>
514 * <li>http://www.w3.org/TR/REC-xml/#NT-Name</li>
517 // NameCharRange \u10000-\ueffff is not supported by Java
518 private static final String NCNameStartChar_RegExp = "[A-Z_a-z\u00c0-\u00d6\u00d8\u00f6\u00f8\u02ff\u0370\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]";
519 private static final String NCNameChar_RegExp = Util.NCNameStartChar_RegExp + "|[-\\.0-9\u00B7\u0300-\u036F\u203F-\u2040]";
520 private static final Pattern NCNameStartChar_Pattern = Pattern.compile(Util.NCNameStartChar_RegExp);
521 private static final Pattern NCNameChar_RegExp_Pattern = Pattern.compile(Util.NCNameChar_RegExp);
525 * Removes all non-NCName characters from the given string and returns the
528 * This function should be consistent with
529 * org.eclipse.winery.common.Util.cleanName(String)
531 * TODO: This method seems to be equal to {@link
532 * org.eclipse.winery.repository.Utils.createXMLidAsString(String)}. These
533 * methods should be merged.
536 public static String makeNCName(String text) {
537 if (StringUtils.isEmpty(text)) {
541 StringBuffer res = new StringBuffer();
544 String start = text.substring(0, 1);
545 Matcher m = Util.NCNameStartChar_Pattern.matcher(start);
549 // not a valid character
553 // handle remaining characters;
554 for (int i = 1; i < text.length(); i++) {
555 String s = text.substring(i, i + 1);
556 m = Util.NCNameChar_RegExp_Pattern.matcher(s);
560 // not a valid character
565 return res.toString();
568 private static void addAsIs(StringBuilder sb, String s, String separator) {
572 String[] split = s.split(separator);
573 for (int i = 0; i < split.length; i++) {
579 private static void addReversed(StringBuilder sb, String s, String separator) {
580 String[] split = s.split(separator);
581 for (int i = split.length - 1; i >= 0; i--) {
588 * Bridge to client.getType(). Just calls client getType(), used by
591 * We suppress compiler warnings as JSP 2.0 do not offer support for
592 * generics, but we're using JSP 2.0...
594 * @param client the repository client to use
595 * @param qname the QName to resolve
596 * @param clazz the class the QName is describing
597 * @return {@inheritDoc}
599 @SuppressWarnings({"rawtypes", "unchecked"})
600 public static org.eclipse.winery.model.tosca.TEntityType getType(org.eclipse.winery.common.interfaces.IWineryRepository client, javax.xml.namespace.QName qname, java.lang.Class clazz) {
601 return client.getType(qname, clazz);