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