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