1  /*
  2   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3   *
  4   * Copyright 2014 Mike Becker. All rights reserved.
  5   *
  6   * Redistribution and use in source and binary forms, with or without
  7   * modification, are permitted provided that the following conditions are met:
  8   *
  9   *   1. Redistributions of source code must retain the above copyright
 10   *      notice, this list of conditions and the following disclaimer.
 11   *
 12   *   2. Redistributions in binary form must reproduce the above copyright
 13   *      notice, this list of conditions and the following disclaimer in the
 14   *      documentation and/or other materials provided with the distribution.
 15   *
 16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 26   * POSSIBILITY OF SUCH DAMAGE.
 27   *
 28   */
 29  
 30  package de.uapcore.sigred.doc.base;
 31  
 32  import de.uapcore.sigred.doc.Resources;
 33  import de.uapcore.sigrapi.impl.Digraph;
 34  import de.uapcore.sigrapi.impl.Graph;
 35  import de.uapcore.sigrapi.IGraph;
 36  import java.io.IOException;
 37  import java.io.InputStream;
 38  import java.io.OutputStream;
 39  import java.util.concurrent.atomic.AtomicBoolean;
 40  import java.util.concurrent.atomic.AtomicReference;
 41  import org.apache.xerces.impl.Constants;
 42  import org.dom4j.Document;
 43  import org.dom4j.DocumentException;
 44  import org.dom4j.DocumentHelper;
 45  import org.dom4j.Element;
 46  import org.dom4j.Namespace;
 47  import org.dom4j.QName;
 48  import org.dom4j.io.OutputFormat;
 49  import org.dom4j.io.SAXReader;
 50  import org.dom4j.io.XMLWriter;
 51  import org.xml.sax.ErrorHandler;
 52  import org.xml.sax.SAXException;
 53  import org.xml.sax.SAXParseException;
 54  
 55  public abstract class AbstractGraphDocument<T extends IGraph>
 56          extends FileBackedDocument {
 57      
 58      protected static final Namespace NAMESPACE = Namespace.get("sigred",
 59          "http://develop.uap-core.de/sigred/");
 60      
 61      private static final
 62          QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
 63      private static final
 64          QName TAG_GRAPH = QName.get("graph", NAMESPACE);
 65      private static final
 66          QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
 67      private static final
 68          QName TAG_METADATA = QName.get("metadata", NAMESPACE);
 69      
 70      protected final T graph;
 71      
 72      private final GraphDocumentMetadata metadata;
 73      
 74      public AbstractGraphDocument(Class<T> graphType) {
 75          T g;
 76          try {
 77              g = graphType.newInstance();
 78          } catch (ReflectiveOperationException e) {
 79              assert false;
 80              g = null; // for the compiler
 81          }
 82          graph = g;
 83          metadata = new GraphDocumentMetadata();
 84      }
 85  
 86      public T getGraph() {
 87          return graph;
 88      }
 89      
 90      public GraphDocumentMetadata getMetadata() {
 91          return metadata;
 92      }
 93  
 94      protected abstract void writeGraph(Element rootNode) throws IOException;
 95      protected abstract void readGraph(Element rootNode) throws IOException;
 96  
 97      @Override
 98      public void writeTo(OutputStream out) throws IOException {
 99          Document doc = DocumentHelper.createDocument();
100  
101          Element rootNode = doc.addElement(TAG_GRAPHDOC);
102  
103          Element metadataNode = rootNode.addElement(TAG_METADATA);
104  
105          metadata.write(metadataNode);
106  
107          if (graph instanceof Graph) {
108              writeGraph(rootNode.addElement(TAG_GRAPH));
109          } else if (graph instanceof Digraph) {
110              writeGraph(rootNode.addElement(TAG_DIGRAPH));
111          } else {
112              throw new IOException("unsupported graph type");
113          }
114  
115          XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
116          writer.write(doc);
117          writer.flush();
118      }
119  
120      @Override
121      public void readFrom(InputStream in) throws IOException {
122          try {
123              SAXReader reader = new SAXReader(true);
124              reader.setStripWhitespaceText(true);
125              
126              reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
127                  Constants.SCHEMA_VALIDATION_FEATURE, true);
128              reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
129                  Constants.SCHEMA_LOCATION, String.format("%s %s",
130                      NAMESPACE.getURI(), Resources.class.getResource(
131                          "graph-document.xsd").toExternalForm()));
132              
133              final AtomicBoolean passed = new AtomicBoolean(true);
134              final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
135              // TODO: we should do more detailed error handling here
136              reader.setErrorHandler(new ErrorHandler() {
137                  @Override
138                  public void warning(SAXParseException exception) throws SAXException {
139                  }
140  
141                  @Override
142                  public void error(SAXParseException exception) throws SAXException {
143                      xmlerror.set(exception);
144                      passed.set(false);
145                  }
146  
147                  @Override
148                  public void fatalError(SAXParseException exception) throws SAXException {
149                      xmlerror.set(exception);
150                      passed.set(false);
151                  }
152                  
153              });
154              Document doc = reader.read(in);
155              if (!passed.get()) {
156                  // TODO: provide details (maybe via separate error object?)
157                  throw xmlerror.get();
158              }
159              
160              doc.normalize();
161              
162              Element root = doc.getRootElement();
163              metadata.read(root.element(TAG_METADATA));
164              
165              if (graph instanceof Graph) {
166                  readGraph(root.element(TAG_GRAPH));
167              } else if (graph instanceof Digraph) {
168                  readGraph(root.element(TAG_DIGRAPH));
169              } else {
170                  throw new IOException("unsupported graph type");
171              }
172          } catch (DocumentException | SAXException ex) {
173              throw new IOException(ex);
174          }
175      }
176  }