/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang.model.repo.api;
import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YIN_MODULE;
import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.MODULE;
import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.REVISION;
import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.SUBMODULE;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import javax.annotation.Nonnull;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.YangConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Utility {@link YinXmlSchemaSource} exposing a W3C {@link DOMSource} representation of YIN model.
*/
public abstract class YinDomSchemaSource implements YinXmlSchemaSource {
private static final Logger LOG = LoggerFactory.getLogger(YinDomSchemaSource.class);
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
private static final QName REVISION_STMT = REVISION.getStatementName();
YinDomSchemaSource() {
// Prevent outside instantiation
}
/**
* Create a new {@link YinDomSchemaSource} using an identifier and a source.
*
* @param identifier Schema source identifier
* @param source W3C DOM source
* @return A new {@link YinDomSchemaSource} instance.
*/
public static @Nonnull YinDomSchemaSource create(@Nonnull final SourceIdentifier identifier,
final @Nonnull DOMSource source) {
final Node root = source.getNode().getFirstChild();
final String rootNs = root.getNamespaceURI();
if (rootNs == null) {
// Let whoever is using this deal with this
return new Simple(identifier, source);
}
final QName qname = QName.create(rootNs, root.getLocalName());
Preconditions.checkArgument(RFC6020_YIN_MODULE.equals(qname.getModule()),
"Root node namepsace %s does not match %s", rootNs, YangConstants.RFC6020_YIN_NAMESPACE);
Preconditions.checkArgument(MODULE.getStatementName().equals(qname) ||
SUBMODULE.getStatementName().equals(qname), "Root element %s is not a module nor a submodule", qname);
Preconditions.checkArgument(root instanceof Element, "Root node %s is not an element", root);
final Element element = (Element)root;
final Attr nameAttr = element.getAttributeNode(MODULE.getArgumentName().getLocalName());
Preconditions.checkArgument(nameAttr != null, "No %s name argument found in %s", element.getLocalName());
final NodeList revisions = element.getElementsByTagNameNS(REVISION_STMT.getNamespace().toString(),
REVISION_STMT.getLocalName());
if (revisions.getLength() == 0) {
// FIXME: is module name important (as that may have changed)
return new Simple(identifier, source);
}
final Element revisionStmt = (Element) revisions.item(0);
final Attr dateAttr = revisionStmt.getAttributeNode(REVISION.getArgumentName().getLocalName());
Preconditions.checkArgument(dateAttr != null, "No revision statement argument found in %s", revisionStmt);
final SourceIdentifier parsedId = RevisionSourceIdentifier.create(nameAttr.getValue(),
Optional.of(dateAttr.getValue()));
final SourceIdentifier id;
if (!parsedId.equals(identifier)) {
LOG.debug("Changed identifier from {} to {}", identifier, parsedId);
id = parsedId;
} else {
id = identifier;
}
return new Simple(id, source);
}
/**
* Create a {@link YinDomSchemaSource} from a {@link YinXmlSchemaSource}. If the argument is already a
* YinDomSchemaSource, this method returns the same instance. The source will be translated on first access,
* at which point an {@link IllegalStateException} may be raised.
*
* @param xmlSchemaSource Backing schema source
* @return A {@link YinDomSchemaSource} instance
*/
@Nonnull public static YinDomSchemaSource lazyTransform(final YinXmlSchemaSource xmlSchemaSource) {
final YinDomSchemaSource cast = castSchemaSource(xmlSchemaSource);
return cast != null ? cast : new Transforming(xmlSchemaSource);
}
/**
* Create a {@link YinDomSchemaSource} from a {@link YinXmlSchemaSource}. If the argument is already a
* YinDomSchemaSource, this method returns the same instance. The source will be translated immediately.
*
* @param xmlSchemaSource Backing schema source
* @return A {@link YinDomSchemaSource} instance
* @throws TransformerException when the provided source fails to transform
*/
@Nonnull public static YinDomSchemaSource transform(final YinXmlSchemaSource xmlSchemaSource) throws TransformerException {
final YinDomSchemaSource cast = castSchemaSource(xmlSchemaSource);
return cast != null ? cast :
create(xmlSchemaSource.getIdentifier(), transformSource(xmlSchemaSource.getSource()));
}
@Override
@Nonnull public abstract DOMSource getSource();
@Override
public final Class<? extends YinXmlSchemaSource> getType() {
return YinDomSchemaSource.class;
}
@Override
public final String toString() {
return addToStringAttributes(MoreObjects.toStringHelper(this).add("identifier", getIdentifier())).toString();
}
/**
* Add subclass-specific attributes to the output {@link #toString()} output. Since
* subclasses are prevented from overriding {@link #toString()} for consistency
* reasons, they can add their specific attributes to the resulting string by attaching
* attributes to the supplied {@link ToStringHelper}.
*
* @param toStringHelper ToStringHelper onto the attributes can be added
* @return ToStringHelper supplied as input argument.
*/
protected abstract ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper);
static DOMSource transformSource(final Source source) throws TransformerException {
final DOMResult result = new DOMResult();
TRANSFORMER_FACTORY.newTransformer().transform(source, result);
return new DOMSource(result.getNode(), result.getSystemId());
}
private static YinDomSchemaSource castSchemaSource(final YinXmlSchemaSource xmlSchemaSource) {
if (xmlSchemaSource instanceof YinDomSchemaSource) {
return (YinDomSchemaSource) xmlSchemaSource;
}
final Source source = xmlSchemaSource.getSource();
if (source instanceof DOMSource) {
return create(xmlSchemaSource.getIdentifier(), (DOMSource) source);
}
return null;
}
private static final class Simple extends YinDomSchemaSource {
private final SourceIdentifier identifier;
private final DOMSource source;
Simple(@Nonnull final SourceIdentifier identifier, @Nonnull final DOMSource source) {
this.identifier = Preconditions.checkNotNull(identifier);
this.source = Preconditions.checkNotNull(source);
}
@Nonnull
@Override
public DOMSource getSource() {
return source;
}
@Override
public SourceIdentifier getIdentifier() {
return identifier;
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
return toStringHelper.add("source", source);
}
}
private static final class Transforming extends YinDomSchemaSource {
private final YinXmlSchemaSource xmlSchemaSource;
private volatile DOMSource source;
Transforming(final YinXmlSchemaSource xmlSchemaSource) {
this.xmlSchemaSource = Preconditions.checkNotNull(xmlSchemaSource);
}
@Nonnull
@Override
public DOMSource getSource() {
DOMSource ret = source;
if (ret == null) {
synchronized (this) {
ret = source;
if (ret == null) {
try {
ret = transformSource(xmlSchemaSource.getSource());
} catch (TransformerException e) {
throw new IllegalStateException("Failed to transform schema source " + xmlSchemaSource, e);
}
source = ret;
}
}
}
return ret;
}
@Override
public SourceIdentifier getIdentifier() {
return xmlSchemaSource.getIdentifier();
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
return toStringHelper.add("xmlSchemaSource", xmlSchemaSource);
}
}
}