Do not provide SourceIdentifiers from extensions
[yangtools.git] / yang / yang-repo-api / src / main / java / org / opendaylight / yangtools / yang / model / repo / api / YinDomSchemaSource.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.model.repo.api;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YIN_MODULE;
13 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.MODULE;
14 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.REVISION;
15 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.SUBMODULE;
16
17 import com.google.common.base.MoreObjects;
18 import com.google.common.base.MoreObjects.ToStringHelper;
19 import java.util.Optional;
20 import javax.xml.transform.Source;
21 import javax.xml.transform.TransformerException;
22 import javax.xml.transform.TransformerFactory;
23 import javax.xml.transform.dom.DOMResult;
24 import javax.xml.transform.dom.DOMSource;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.common.YangConstants;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.w3c.dom.Attr;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.NodeList;
35
36 /**
37  * Utility {@link YinXmlSchemaSource} exposing a W3C {@link DOMSource} representation of YIN model.
38  */
39 public abstract class YinDomSchemaSource implements YinXmlSchemaSource {
40     private static final Logger LOG = LoggerFactory.getLogger(YinDomSchemaSource.class);
41     private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
42     private static final QName REVISION_STMT = REVISION.getStatementName();
43     private static final String MODULE_ARG = MODULE.getArgumentDefinition().orElseThrow()
44         .argumentName().getLocalName();
45     private static final String REVISION_ARG = REVISION.getArgumentDefinition().orElseThrow()
46         .argumentName().getLocalName();
47
48     YinDomSchemaSource() {
49         // Prevent outside instantiation
50     }
51
52     /**
53      * Create a new {@link YinDomSchemaSource} using an identifier and a source.
54      *
55      * @param identifier Schema source identifier
56      * @param source W3C DOM source
57      * @param symbolicName Source symbolic name
58      * @return A new {@link YinDomSchemaSource} instance.
59      */
60     public static @NonNull YinDomSchemaSource create(final @NonNull SourceIdentifier identifier,
61             final @NonNull DOMSource source, final @Nullable String symbolicName) {
62
63         final Node root = source.getNode().getFirstChild();
64         final String rootNs = root.getNamespaceURI();
65         if (rootNs == null) {
66             // Let whoever is using this deal with this
67             return new Simple(identifier, source, symbolicName);
68         }
69
70         final QName qname = QName.create(rootNs, root.getLocalName());
71         checkArgument(RFC6020_YIN_MODULE.equals(qname.getModule()),
72             "Root node namepsace %s does not match %s", rootNs, YangConstants.RFC6020_YIN_NAMESPACE);
73         checkArgument(MODULE.getStatementName().equals(qname)
74             || SUBMODULE.getStatementName().equals(qname), "Root element %s is not a module nor a submodule", qname);
75
76         checkArgument(root instanceof Element, "Root node %s is not an element", root);
77         final Element element = (Element)root;
78
79         final Attr nameAttr = element.getAttributeNode(MODULE_ARG);
80         checkArgument(nameAttr != null, "No %s name argument found in %s", element.getLocalName());
81
82         final NodeList revisions = element.getElementsByTagNameNS(REVISION_STMT.getNamespace().toString(),
83             REVISION_STMT.getLocalName());
84         if (revisions.getLength() == 0) {
85             // FIXME: is module name important (as that may have changed)
86             return new Simple(identifier, source, symbolicName);
87         }
88
89         final Element revisionStmt = (Element) revisions.item(0);
90         final Attr dateAttr = revisionStmt.getAttributeNode(REVISION_ARG);
91         checkArgument(dateAttr != null, "No revision statement argument found in %s", revisionStmt);
92
93         final SourceIdentifier parsedId = new SourceIdentifier(nameAttr.getValue(), dateAttr.getValue());
94         final SourceIdentifier id;
95         if (!parsedId.equals(identifier)) {
96             LOG.debug("Changed identifier from {} to {}", identifier, parsedId);
97             id = parsedId;
98         } else {
99             id = identifier;
100         }
101
102         return new Simple(id, source, symbolicName);
103     }
104
105     /**
106      * Create a {@link YinDomSchemaSource} from a {@link YinXmlSchemaSource}. If the argument is already a
107      * YinDomSchemaSource, this method returns the same instance. The source will be translated on first access,
108      * at which point an {@link IllegalStateException} may be raised.
109      *
110      * @param xmlSchemaSource Backing schema source
111      * @return A {@link YinDomSchemaSource} instance
112      */
113     public static @NonNull YinDomSchemaSource lazyTransform(final YinXmlSchemaSource xmlSchemaSource) {
114         final YinDomSchemaSource cast = castSchemaSource(xmlSchemaSource);
115         return cast != null ? cast : new Transforming(xmlSchemaSource);
116     }
117
118     /**
119      * Create a {@link YinDomSchemaSource} from a {@link YinXmlSchemaSource}. If the argument is already a
120      * YinDomSchemaSource, this method returns the same instance. The source will be translated immediately.
121      *
122      * @param xmlSchemaSource Backing schema source
123      * @return A {@link YinDomSchemaSource} instance
124      * @throws TransformerException when the provided source fails to transform
125      */
126     public static @NonNull YinDomSchemaSource transform(final YinXmlSchemaSource xmlSchemaSource)
127             throws TransformerException {
128         final YinDomSchemaSource cast = castSchemaSource(xmlSchemaSource);
129         return cast != null ? cast :
130             create(xmlSchemaSource.getIdentifier(), transformSource(xmlSchemaSource.getSource()),
131                 xmlSchemaSource.getSymbolicName().orElse(null));
132     }
133
134     @Override
135     public abstract DOMSource getSource();
136
137     @Override
138     public final Class<YinDomSchemaSource> getType() {
139         return YinDomSchemaSource.class;
140     }
141
142     @Override
143     public final String toString() {
144         return addToStringAttributes(MoreObjects.toStringHelper(this).add("identifier", getIdentifier())).toString();
145     }
146
147     /**
148      * Add subclass-specific attributes to the output {@link #toString()} output. Since
149      * subclasses are prevented from overriding {@link #toString()} for consistency
150      * reasons, they can add their specific attributes to the resulting string by attaching
151      * attributes to the supplied {@link ToStringHelper}.
152      *
153      * @param toStringHelper ToStringHelper onto the attributes can be added
154      * @return ToStringHelper supplied as input argument.
155      */
156     protected abstract ToStringHelper addToStringAttributes(ToStringHelper toStringHelper);
157
158     static @NonNull DOMSource transformSource(final Source source) throws TransformerException {
159         final DOMResult result = new DOMResult();
160         TRANSFORMER_FACTORY.newTransformer().transform(source, result);
161
162         return new DOMSource(result.getNode(), result.getSystemId());
163     }
164
165     private static @Nullable YinDomSchemaSource castSchemaSource(final YinXmlSchemaSource xmlSchemaSource) {
166         if (xmlSchemaSource instanceof YinDomSchemaSource yinDom) {
167             return yinDom;
168         }
169
170         final Source source = xmlSchemaSource.getSource();
171         if (source instanceof DOMSource dom) {
172             return create(xmlSchemaSource.getIdentifier(), dom, xmlSchemaSource.getSymbolicName().orElse(null));
173         }
174         return null;
175     }
176
177     private static final class Simple extends YinDomSchemaSource {
178         private final @NonNull SourceIdentifier identifier;
179         private final @NonNull DOMSource source;
180         private final String symbolicName;
181
182         Simple(final @NonNull SourceIdentifier identifier, final @NonNull DOMSource source,
183                 final @Nullable String symbolicName) {
184             this.identifier = requireNonNull(identifier);
185             this.source = requireNonNull(source);
186             this.symbolicName = symbolicName;
187         }
188
189         @Override
190         public DOMSource getSource() {
191             return source;
192         }
193
194         @Override
195         public SourceIdentifier getIdentifier() {
196             return identifier;
197         }
198
199         @Override
200         public Optional<String> getSymbolicName() {
201             return Optional.ofNullable(symbolicName);
202         }
203
204         @Override
205         protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
206             return toStringHelper.add("source", source);
207         }
208     }
209
210     private static final class Transforming extends YinDomSchemaSource {
211         private final YinXmlSchemaSource xmlSchemaSource;
212         private volatile DOMSource source;
213
214         Transforming(final YinXmlSchemaSource xmlSchemaSource) {
215             this.xmlSchemaSource = requireNonNull(xmlSchemaSource);
216         }
217
218         @Override
219         public DOMSource getSource() {
220             DOMSource ret = source;
221             if (ret == null) {
222                 synchronized (this) {
223                     ret = source;
224                     if (ret == null) {
225                         try {
226                             ret = transformSource(xmlSchemaSource.getSource());
227                         } catch (TransformerException e) {
228                             throw new IllegalStateException("Failed to transform schema source " + xmlSchemaSource, e);
229                         }
230                         source = ret;
231                     }
232                 }
233             }
234
235             return ret;
236         }
237
238         @Override
239         public SourceIdentifier getIdentifier() {
240             return xmlSchemaSource.getIdentifier();
241         }
242
243         @Override
244         public Optional<String> getSymbolicName() {
245             return xmlSchemaSource.getSymbolicName();
246         }
247
248         @Override
249         protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
250             return toStringHelper.add("xmlSchemaSource", xmlSchemaSource);
251         }
252     }
253 }