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