View Javadoc

1   /* This file is part of COPAL (COntext Provisioning for All).
2    *
3    * COPAL is a part of SM4All (Smart hoMes for All) project.
4    *
5    * COPAL is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU Lesser General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * COPAL is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public License
16   * along with COPAL. If not, see <http://www.gnu.org/licenses/>.
17   */
18  package at.ac.tuwien.infosys.sm4all.copal.api.event.xml;
19  
20  import java.io.IOException;
21  import java.net.URI;
22  import java.net.URL;
23  import java.text.MessageFormat;
24  import javax.xml.XMLConstants;
25  import javax.xml.parsers.DocumentBuilder;
26  import javax.xml.transform.dom.DOMSource;
27  import javax.xml.validation.Schema;
28  import javax.xml.validation.SchemaFactory;
29  import javax.xml.validation.Validator;
30  import org.apache.log4j.Logger;
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Element;
33  import org.xml.sax.SAXException;
34  import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEventType;
35  import at.ac.tuwien.infosys.sm4all.copal.api.listener.ContextListener;
36  import at.ac.tuwien.infosys.sm4all.copal.api.publisher.ContextPublisher;
37  import at.ac.tuwien.infosys.sm4all.copal.api.util.Constants;
38  
39  /**
40   * Class which defines type for each {@link XMLContextEvent}, i.e. each
41   * {@link XMLContextEvent} must have a corresponding {@link XMLContextEventType}
42   * associated with it. The name of the {@link XMLContextEventType} is also the
43   * name of each {@link XMLContextEvent} associated with this type and is a
44   * globally unique name on which {@link ContextPublisher}s and
45   * {@link ContextListener}s have to agree on and use it to publish and listen on
46   * events respectively. This class is not thread safe.
47   * 
48   * @author sanjin
49   */
50  public class XMLContextEventType extends ContextEventType {
51  
52      private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
53      private static final Logger LOGGER = Logger.getLogger(XMLContextEventType.class);
54  
55      private final String rootElementName;
56      private URI namespaceURI;
57      private URL schemaURL;
58      private Validator validator;
59  
60      /**
61       * Create instance of {@link XMLContextEventType}. The <code>name</code>
62       * must be globally unique name of the event. If <code>name</code> and
63       * <code>rootElementName</code> have same value use the
64       * {@link #XMLContextEventType(String)} constructor instead.
65       * 
66       * @param name the globally unique name of the {@link XMLContextEventType}.
67       * @param rootElementName the local (unqualified) name of event's
68       *        {@link Document} root element.
69       * @throws NullPointerException if specified name or root element name is
70       *         <code>null</code>.
71       * @throws IllegalArgumentException if specified name or root element name
72       *         is an empty or blank string.
73       */
74      public XMLContextEventType(final String name, final String rootElementName) {
75          super(name);
76  
77          if (rootElementName == null)
78              throw new NullPointerException("Root element name cannot be null.");
79          if (rootElementName.trim().isEmpty())
80              throw new IllegalArgumentException(
81                      "Root element name cannot be empty or blank.");
82  
83          this.rootElementName = rootElementName;
84      }
85  
86      /**
87       * Create instance of {@link XMLContextEventType}. The <code>name</code>
88       * must be globally unique name. If <code>name</code> and
89       * <code>rootElementName</code> have different values use the
90       * {@link #XMLContextEventType(String, String)} constructor instead.
91       * 
92       * @param name the globally unique name of the event and the local
93       *        (unqualified) name of XML document root element.
94       * @throws NullPointerException if specified name is <code>null</code>.
95       * @throws IllegalArgumentException if specified name is an empty or blank
96       *         string.
97       */
98      public XMLContextEventType(final String name) {
99          this(name, name);
100     }
101 
102     /**
103      * @return the local (unqualified) name of XML document root element.
104      */
105     public String getRootElementName() {
106         return this.rootElementName;
107     }
108 
109     /**
110      * @return the namespace {@link URI} used by event for its XML elements.
111      */
112     public URI getNamespaceURI() {
113         return this.namespaceURI;
114     }
115 
116     /**
117      * Sets the value of the namespace {@link URI} used by event for its XML
118      * elements.
119      * 
120      * @param namespaceURI the new namespace {@link URI}.
121      */
122     public void setNamespaceURI(final URI namespaceURI) {
123         this.namespaceURI = namespaceURI;
124     }
125 
126     /**
127      * @return if this {@link ContextEventType} has the namespace defined.
128      */
129     public boolean hasNamespace() {
130         return this.namespaceURI != null;
131     }
132 
133     /**
134      * @return the URL where XML Schema can be found that defines the XML
135      *         document structure of this {@link ContextEventType}.
136      */
137     public URL getSchemaURL() {
138         return this.schemaURL;
139     }
140 
141     /**
142      * Sets the XML Schema URL that defines the XML document structure of this
143      * {@link ContextEventType}.
144      * 
145      * @param schemaURL the new XML Schema URL.
146      */
147     public void setSchemaURL(final URL schemaURL) {
148         this.schemaURL = schemaURL;
149         this.validator = null;
150     }
151 
152     /**
153      * @return if this {@link ContextEventType} has the schema defined.
154      */
155     public boolean hasSchema() {
156         return this.schemaURL != null;
157     }
158 
159     /**
160      * Create a {@link Document} for this {@link ContextEventType} using
161      * specified {@link DocumentBuilder} with root element having name equal to
162      * {@link #getRootElementName()} and default namespace set to
163      * {@link #getNamespaceURI()} if there is one.
164      * 
165      * @param builder the {@link DocumentBuilder} used for creating
166      *        {@link Document}.
167      * @return the {@link Document} that can be used as an event for this
168      *         {@link ContextEventType}.
169      * @throws NullPointerException id specified {@link DocumentBuilder} is
170      *         <code>null</code>.
171      */
172     public Document createEvent(final DocumentBuilder builder) {
173         if (builder == null)
174             throw new NullPointerException("Document builder cannot be null.");
175 
176         final Document result = builder.newDocument();
177 
178         final Element rootElement;
179         if (hasNamespace()) {
180             final String namespace = this.namespaceURI.toString();
181             rootElement = result.createElementNS(namespace,
182                     this.rootElementName);
183             rootElement.setAttributeNS(Constants.XML_NAMESPACE_URI,
184                     Constants.XML_NAMESPACE_PREFIX, namespace);
185         } else
186             rootElement = result.createElementNS(null, this.rootElementName);
187 
188         result.appendChild(rootElement);
189 
190         return result;
191     }
192 
193     /**
194      * Checks if specified {@link Document} is valid for this
195      * {@link ContextEventType}.
196      * 
197      * @param document the {@link Document}.
198      * @throws MalformedDocumentException if specified {@link Document} is
199      *         malformed for this {@link ContextEventType}.
200      */
201     public void validate(final Document document)
202             throws MalformedDocumentException {
203         if (document == null)
204             throw new NullPointerException("XML DOM document cannot be null.");
205 
206         if ((this.schemaURL != null) && (this.validator == null))
207             try {
208                 final Schema schema = SCHEMA_FACTORY.newSchema(this.schemaURL);
209                 this.validator = schema.newValidator();
210             } catch (final SAXException ex) {
211                 LOGGER.warn(
212                         MessageFormat.format(
213                                 "Could not read XML Schema from ''{0}'' URL! URL will remain unchanged.",
214                                 this.schemaURL), ex);
215             }
216 
217         if (this.validator != null)
218             try {
219                 this.validator.validate(new DOMSource(document));
220             } catch (final SAXException ex) {
221                 throw new MalformedDocumentException(
222                         "XML Schema validation failed.", ex);
223             } catch (final IOException ex) {
224                 throw new MalformedDocumentException(
225                         "Could not read XML DOM document.", ex);
226             }
227         else {
228             final Element rootElement = document.getDocumentElement();
229             if ((rootElement == null)
230                     || (!this.rootElementName.equals(rootElement.getLocalName())))
231                 throw new MalformedDocumentException(MessageFormat.format(
232                         "Document is missing root element ''{0}''.",
233                         this.rootElementName));
234 
235             if (hasNamespace()) {
236                 if (!this.namespaceURI.toString().equals(
237                         rootElement.getNamespaceURI()))
238                     throw new MalformedDocumentException(MessageFormat.format(
239                             "Root element is not in ''{0}'' namespace.",
240                             this.namespaceURI));
241             } else if (rootElement.getNamespaceURI() != null)
242                 throw new MalformedDocumentException(
243                         "Root element is not in no namespace.");
244         }
245     }
246 }