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.helpers;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.text.MessageFormat;
24  import java.util.Arrays;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.Set;
32  import javax.xml.XMLConstants;
33  import javax.xml.namespace.NamespaceContext;
34  import javax.xml.namespace.QName;
35  import javax.xml.parsers.DocumentBuilderFactory;
36  import javax.xml.parsers.ParserConfigurationException;
37  import javax.xml.validation.Schema;
38  import javax.xml.validation.SchemaFactory;
39  import javax.xml.xpath.XPath;
40  import javax.xml.xpath.XPathConstants;
41  import javax.xml.xpath.XPathExpression;
42  import javax.xml.xpath.XPathExpressionException;
43  import javax.xml.xpath.XPathFactory;
44  import org.apache.log4j.Logger;
45  import org.w3c.dom.Document;
46  import org.w3c.dom.Element;
47  import org.w3c.dom.Node;
48  import org.w3c.dom.NodeList;
49  import org.xml.sax.SAXException;
50  import at.ac.tuwien.infosys.sm4all.copal.api.ContextQuery;
51  import at.ac.tuwien.infosys.sm4all.copal.api.ProcessedEventQuery;
52  import at.ac.tuwien.infosys.sm4all.copal.api.RedefinitionOfQueryException;
53  import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEventType;
54  import at.ac.tuwien.infosys.sm4all.copal.api.event.xml.ParsingException;
55  import at.ac.tuwien.infosys.sm4all.copal.api.event.xml.XMLContextEventType;
56  import at.ac.tuwien.infosys.sm4all.copal.api.event.xml.query.XMLProcessedEventQuery;
57  import at.ac.tuwien.infosys.sm4all.copal.service.copal.ContextEventTypeRegistry;
58  import at.ac.tuwien.infosys.sm4all.copal.service.copal.ContextQueryFactory;
59  
60  /**
61   * This class is used to read definitions of {@link ContextEventType}s and
62   * {@link ContextQuery}s from a XML configuration file that can be found in
63   * classpath.
64   * 
65   * @author sanjin
66   */
67  public class Context {
68  
69      /**
70       * The namespace URI used by the XML configuration file.
71       */
72      public static final String COPAL_NAMESPACE_URI = "http://www.sm4all-project.eu/COPAL";
73      private static final Schema CONTEXT_SCHEMA;
74      private static final Logger LOGGER = Logger.getLogger(Context.class);
75      private static final XPath X_PATH = XPathFactory.newInstance().newXPath();
76  
77      static {
78          Schema schema = null;
79          try {
80              final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
81              final URL configurationSchemaURL = Context.class.getClassLoader().getResource(
82                      "Configuration.xsd");
83              if (configurationSchemaURL == null)
84                  LOGGER.warn("Could not find Configuration.xsd! Ignoring.");
85              else
86                  schema = schemaFactory.newSchema(configurationSchemaURL);
87          } catch (final SAXException ex) {
88              LOGGER.warn("Could not parse Configuration.xsd! Ignoring.", ex);
89          }
90          CONTEXT_SCHEMA = schema;
91  
92          X_PATH.setNamespaceContext(new NamespaceContext() {
93  
94              @Override
95              public Iterator<?> getPrefixes(final String namespaceURI) {
96                  throw new UnsupportedOperationException();
97              }
98  
99              @Override
100             public String getPrefix(final String namespaceURI) {
101                 throw new UnsupportedOperationException();
102             }
103 
104             @Override
105             public String getNamespaceURI(final String prefix) {
106                 if ("copal".equals(prefix))
107                     return COPAL_NAMESPACE_URI;
108 
109                 return XMLConstants.NULL_NS_URI;
110             }
111         });
112     }
113 
114     private final ClassLoader classLoader;
115     private final Map<String, Element> queries = new HashMap<String, Element>();
116     private final XMLContextEventType[] eventTypes;
117 
118     /**
119      * Use specified <code>ClassLoader</code> to find the XML configuration file
120      * with specified name.
121      * 
122      * @param classLoader the class loader.
123      * @param fileName the name of the configuration file.
124      */
125     public Context(final ClassLoader classLoader, final String fileName) {
126         super();
127 
128         this.classLoader = classLoader;
129         final Document document = getDocument(fileName, CONTEXT_SCHEMA);
130         if (document == null)
131             this.eventTypes = new XMLContextEventType[0];
132         else {
133             final NodeList eventTypeNodes = evaluate(
134                     compile("/copal:Context/copal:Event"), document,
135                     XPathConstants.NODESET);
136             this.eventTypes = new XMLContextEventType[eventTypeNodes.getLength()];
137             for (int i = 0; i < eventTypeNodes.getLength(); i++)
138                 try {
139                     this.eventTypes[i] = new XMLContextEventType(classLoader,
140                             (Element) eventTypeNodes.item(i));
141                 } catch (final ParsingException ex) {
142                     LOGGER.warn(
143                             "Could not unmarshal an event definition! Ignored.",
144                             ex);
145                 }
146 
147             final NodeList queryNodes = evaluate(
148                     compile("/copal:Context/copal:Query"), document,
149                     XPathConstants.NODESET);
150             for (int i = 0; i < queryNodes.getLength(); i++) {
151                 final Element query = (Element) queryNodes.item(i);
152                 this.queries.put(query.getAttribute("name"), query);
153             }
154         }
155     }
156 
157     /**
158      * @return the defined {@link XMLContextEventType}s in the XML configuration
159      *         file or an empty array if there was problem reading the file.
160      */
161     public XMLContextEventType[] getDefinedEventTypes() {
162         return Arrays.copyOf(this.eventTypes, this.eventTypes.length);
163     }
164 
165     /**
166      * @return the defined {@link ContextQuery}s in the XML configuration file
167      *         or empty array if there was problem reading the file.
168      */
169     public String[] getDefinedQueries() {
170         final Set<String> result = this.queries.keySet();
171         return result.toArray(new String[result.size()]);
172     }
173 
174     /**
175      * Try to register all defined {@link XMLContextEventType}s with specified
176      * {@link ContextEventTypeRegistry} and return all
177      * {@link XMLContextEventType}s that were successfully registered.
178      * 
179      * @param eventTypeRegistry the event type registry with which to register
180      *        defined {@link XMLContextEventType}s.
181      * @return successfully registered {@link XMLContextEventType}s.
182      */
183     public XMLContextEventType[] registerEventTypes(
184             final ContextEventTypeRegistry eventTypeRegistry) {
185         final List<XMLContextEventType> result = new LinkedList<XMLContextEventType>();
186 
187         for (final XMLContextEventType eventType : this.eventTypes)
188             if (eventTypeRegistry.register(eventType))
189                 result.add(eventType);
190             else
191                 LOGGER.warn(MessageFormat.format(
192                         "Could not register event type ''{0}''! Ignoring.",
193                         eventType.getName()));
194 
195         return result.toArray(new XMLContextEventType[result.size()]);
196     }
197 
198     /**
199      * Try to create all defined {@link ProcessedEventQuery}s with specified
200      * {@link ContextQueryFactory} and return all {@link ProcessedEventQuery}s
201      * that were successfully created.
202      * 
203      * @param queryFactory the query factory with which to create defined
204      *        {@link ProcessedEventQuery}s.
205      * @return successfully created {@link ProcessedEventQuery}s.
206      */
207     public ProcessedEventQuery[] createQueries(
208             final ContextQueryFactory queryFactory) {
209         final List<ProcessedEventQuery> result = new LinkedList<ProcessedEventQuery>();
210 
211         for (final Entry<String, Element> entry : this.queries.entrySet())
212             try {
213                 result.add(new XMLProcessedEventQuery(queryFactory,
214                         entry.getValue()).unmarshal());
215             } catch (final ParsingException ex) {
216                 LOGGER.warn("Could not unmarshal a query definition! Ignored.",
217                         ex);
218             } catch (final RedefinitionOfQueryException ex) {
219                 LOGGER.warn(
220                         "Could not creat a query from a definition! Ignored.",
221                         ex);
222             }
223 
224         return result.toArray(new ProcessedEventQuery[result.size()]);
225     }
226 
227     private Document getDocument(final String fileName, final Schema schema) {
228         Document result = null;
229 
230         final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
231         builderFactory.setNamespaceAware(true);
232         if (schema != null)
233             builderFactory.setSchema(schema);
234         final URL url = this.classLoader.getResource(fileName);
235         if (url == null)
236             LOGGER.warn(MessageFormat.format("Could not find {0}! Ignoring.",
237                     fileName));
238         else {
239             InputStream in = null;
240             try {
241                 in = url.openStream();
242                 result = builderFactory.newDocumentBuilder().parse(in);
243             } catch (final IOException ex) {
244                 LOGGER.warn("Could not read context.cfg.xml! Ignoring.", ex);
245             } catch (final SAXException ex) {
246                 LOGGER.warn("Could not parse context.cfg.xml! Ignoring.", ex);
247             } catch (final ParserConfigurationException ex) {
248                 LOGGER.warn("Could not find context.cfg.xml! Ignoring.", ex);
249             } finally {
250                 if (in != null)
251                     try {
252                         in.close();
253                     } catch (final IOException ex) {
254                         LOGGER.warn(
255                                 "Could not close context.cfg.xml! Ignoring.",
256                                 ex);
257                     }
258             }
259         }
260 
261         return result;
262     }
263 
264     @SuppressWarnings("unchecked")
265     private static <T> T evaluate(final XPathExpression expression,
266             final Node node, final QName returnType) {
267         T result = null;
268 
269         try {
270             result = (T) expression.evaluate(node, returnType);
271             if (XPathConstants.STRING.equals(returnType)) {
272                 final String str = ((String) result).trim();
273                 if (str.isEmpty())
274                     result = null;
275                 else
276                     result = (T) str;
277             }
278         } catch (final XPathExpressionException ex) {
279             /* ignored */
280         }
281 
282         return result;
283     }
284 
285     private static XPathExpression compile(final String expression) {
286         XPathExpression result = null;
287 
288         try {
289             result = X_PATH.compile(expression);
290         } catch (final XPathExpressionException ex) {
291             LOGGER.error(MessageFormat.format(
292                     "Could not compile XPath expression ''{0}''!", expression),
293                     ex);
294         }
295 
296         return result;
297     }
298 }