View Javadoc

1   /*
2    *  Copyright (C) 2003-2005 SINTEF
3    *  Author:  Fredrik Vraalsen (fredrik dot vraalsen at sintef dot no)
4    *  Webpage: http://coras.sourceforge.net/
5    *
6    *  This program is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public License
8    *  as published by the Free Software Foundation; either version 2.1
9    *  of the License, or (at your option) any later version.
10   *
11   *  This program is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this program; if not, write to the Free
18   *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19   *  02111-1307 USA
20   */
21  package no.sintef.xmldb;
22  
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.StringTokenizer;
27  
28  import javax.naming.Context;
29  import javax.naming.InitialContext;
30  import javax.naming.NamingException;
31  
32  import org.apache.log4j.Logger;
33  import org.exist.jboss.XmlDbService;
34  import org.xmldb.api.base.Collection;
35  import org.xmldb.api.base.Resource;
36  import org.xmldb.api.base.ResourceSet;
37  import org.xmldb.api.base.Service;
38  import org.xmldb.api.base.XMLDBException;
39  import org.xmldb.api.modules.CollectionManagementService;
40  import org.xmldb.api.modules.XMLResource;
41  import org.xmldb.api.modules.XPathQueryService;
42  
43  /***
44   * XML:DB database handler.
45   * 
46   * @author Fredrik Vraalsen
47   */
48  public final class XmlDbHandler {
49  
50  	private static final Logger LOGGER = Logger.getLogger(XmlDbHandler.class);
51  	private static XmlDbHandler handler = null;
52  	
53  	private XmlDbService xmlDbService = null;
54  	
55  	/***
56  	 * Singleton class, use getHandler() method.
57  	 */
58  	private XmlDbHandler() {
59  		try {
60  			Context ctx = new InitialContext();
61  			xmlDbService = (XmlDbService) ctx.lookup(XmlDbService.class.getName());
62  		} catch (NamingException e) {
63  			LOGGER.fatal("Unable to lookup XmlDbService", e);
64  		}
65  	}
66  	
67  	/***
68  	 * Gets the XmlDbHandler instance.
69  	 * 
70  	 * @return the XmlDbHandler
71  	 */
72  	public static synchronized XmlDbHandler getHandler() {
73  		if (handler == null) {
74  			try {
75  				handler = new XmlDbHandler();
76  			} catch (Throwable t) {
77  				LOGGER.fatal("Unable to create XmlDbHandler", t);
78  			}
79  		}
80  		return handler;
81  	}
82  
83  	/***
84  	 * Get first XMLResource in collection matching specified query.
85  	 * 
86  	 * TODO includes subcollections?
87  	 * 
88  	 * @param parent
89  	 *            full path of parent collection, using '/' as path separator
90  	 * @param query
91  	 *            XPath query string
92  	 * @param namespaceMappings
93  	 *            prefix -> namespace URI mappings
94  	 * @return first XMLResource matching query, or null if none is found or
95  	 *         error occurs
96  	 */
97  	public XMLResource getSingleXmlResource(String parent, String query, Map namespaceMappings) {
98  		Collection c = null;
99  		try {
100 			c = getCollection(parent);
101 			ResourceSet set = getResourceSet(c, query, namespaceMappings);
102 			if (set == null || set.getSize() == 0) {
103 				return null;
104 			}
105 			// TODO What to do if multiple resources?
106 			Resource r = set.getResource(0);
107 			if (r instanceof XMLResource) {
108 				return (XMLResource) r;
109 			}
110 		} catch (XMLDBException e) {
111 			LOGGER.error("Error while getting XML resource with query " + query
112 					+ " on collection " + parent, e);
113 		} finally {
114 			if (c != null) {
115 				try {
116 					c.close();
117 				} catch (XMLDBException e) {
118 					LOGGER.warn("Could not close collection " + c, e);
119 				}
120 			}
121 		}
122 		return null;
123 	}
124 
125 	/***
126 	 * Get XMLResources in collection matching specified query. Resources which
127 	 * could not be loaded from database because of errors are not included in
128 	 * result.
129 	 * 
130 	 * TODO includes subcollections?
131 	 * 
132 	 * @param parent
133 	 *            full path of parent collection, using '/' as path separator
134 	 * @param query
135 	 *            XPath query
136 	 * @param namespaceMappings
137 	 *            prefix -> namespace URI mappings
138 	 * @return array of XMLResources matching query, possibly empty (never null)
139 	 */
140 	public XMLResource[] getXmlResources(String parent, String query, Map namespaceMappings) {
141 		Collection c = null;
142 		try {
143 			c = getCollection(parent); 
144 			ResourceSet set = getResourceSet(c, query, namespaceMappings);
145 			if (set == null) {
146 				return new XMLResource[0];
147 			}
148 			ArrayList resources = new ArrayList();
149 			for (int i = 0; i < set.getSize(); i++) {
150 				try {
151 					Resource r = set.getResource(i);
152 					if (r instanceof XMLResource) {
153 						resources.add(r);
154 					}
155 				} catch (XMLDBException e) {
156 					LOGGER.error("Error while getting XML resource with query " + query
157 							+ " on collection " + parent, e);
158 				}
159 			}
160 			return (XMLResource[]) resources.toArray(new XMLResource[0]);
161 		} catch (XMLDBException e) {
162 			LOGGER.error("Error while getting XML resources with query " + query
163 					+ " on collection " + parent, e);
164 			return new XMLResource[0];
165 		} finally {
166 			if (c != null) {
167 				try {
168 					c.close();
169 				} catch (XMLDBException e) {
170 					LOGGER.warn("Could not close collection " + c, e);
171 				}
172 			}
173 		}
174 	}
175 
176 	/***
177 	 * Create XMLResource in specified collection.
178 	 * 
179 	 * @param parent
180 	 *            full path of parent collection to create resource in, using
181 	 *            '/' as path separator
182 	 * @return the created XMLResource, or null if error occured
183 	 */
184 	public XMLResource createXmlResource(String parent) {
185 		if (parent == null) {
186 			return null;
187 		}
188 		Collection c = getCollection(parent);
189 		if (c == null) {
190 			c = createCollection(parent);
191 		}
192 		if (c == null) {
193 			return null;
194 		}
195 		try {
196 			return (XMLResource) c.createResource(null, XMLResource.RESOURCE_TYPE);
197 		} catch (XMLDBException e) {
198 			LOGGER.error("Could not create XMLResource in collection " + parent, e);
199 			return null;
200 		} finally {
201 			try {
202 				c.close();
203 			} catch (XMLDBException e) {
204 				LOGGER.warn("Could not close collection " + c, e);
205 			}
206 		}
207 	}
208 
209 	/***
210 	 * Delete XMLResource.
211 	 * 
212 	 * @param resource
213 	 *            resource to delete
214 	 * @throws XMLDBException
215 	 *             if resource cannot be deleted from database
216 	 */
217 	public void deleteResource(XMLResource resource) throws XMLDBException {
218 		if (resource == null) {
219 			return;
220 		}
221 		Collection c = null;
222 		try {
223 			c = resource.getParentCollection();
224 			c.removeResource(resource);
225 		} catch (XMLDBException e) {
226 			LOGGER.error("Could not remove XMLResource " + resource + " from collection " + c, e);
227 			throw e;
228 		} finally {
229 			if (c != null) {
230 				try {
231 					c.close();
232 				} catch (XMLDBException e) {
233 					LOGGER.warn("Could not close collection " + c, e);
234 				}
235 			}
236 		}
237 	}
238 
239 	/***
240 	 * Store an XMLResource in the database.
241 	 * 
242 	 * @param resource
243 	 *            the XMLResource to store
244 	 * @throws XMLDBException
245 	 *             if error occurs when storing resource in database
246 	 */
247 	public void storeResource(XMLResource resource) throws XMLDBException {
248 		if (resource == null) {
249 			return;
250 		}
251 		Collection c = null;
252 		try {
253 			c = resource.getParentCollection();
254 			c.storeResource(resource);
255 		} catch (XMLDBException e) {
256 			LOGGER.error("Could not store XMLResource " + resource.getDocumentId()
257 					+ " in collection " + c.getName(), e);
258 			throw e;
259 		} finally {
260 			if (c != null) {
261 				try {
262 					c.close();
263 				} catch (XMLDBException e) {
264 					LOGGER.warn("Could not close collection " + c, e);
265 				}
266 			}
267 		}
268 	}
269 
270 	/***
271 	 * Create a collection in the database.
272 	 * 
273 	 * @param path
274 	 *            full path specifying location and name of collection, using
275 	 *            '/' as path separator
276 	 * @return the XML:DB Collection, or null if error occured
277 	 */
278 	private Collection createCollection(String path) {
279 		LOGGER.debug("Create collection " + path);
280 		if (path == null) {
281 			return null;
282 		}
283 		int pos = path.lastIndexOf('/');
284 		String parentName = pos == -1 ? "" : path.substring(0, pos);
285 		String name = pos == -1 ? path : path.substring(pos + 1);
286 		Collection parentCollection = null; 
287 		try {
288 			parentCollection = getCollection(parentName);
289 			if (parentCollection == null && !("".equals(parentName))) {
290 				parentCollection = createCollection(parentName);
291 			}
292 			if (parentCollection == null) {
293 				LOGGER.error("Could not create parent collection " + parentName);
294 				return null;
295 			}
296 			CollectionManagementService service = 
297 				(CollectionManagementService) parentCollection.getService(
298 						"CollectionManagementService", "1.0");
299 			return service.createCollection(name);
300 		} catch (XMLDBException e) {
301 			LOGGER.error("Could not create Collection " + path, e);
302 			return null;
303 		} finally {
304 			try {
305 				parentCollection.close();
306 			} catch (XMLDBException e) {
307 				LOGGER.warn("Could not close collection " + parentCollection, e);
308 			}
309 		}
310 	}
311 
312 	/***
313 	 * Get the set of XMLResources matching the query.
314 	 * 
315 	 * @param parent
316 	 *            the parent collection
317 	 * @param query
318 	 *            XPath query string
319 	 * @param namespaceMappings
320 	 *            prefix -> namespace URI mappings
321 	 * @return ResourceSet containing XMLResources matching query
322 	 */
323 	private ResourceSet getResourceSet(Collection parent, String query, Map namespaceMappings) {
324 		LOGGER.debug("getResourceSet(" + parent + ", " + query + ")");
325 		if (query == null || parent == null) {
326 			return null;
327 		}
328 		
329 		XPathQueryService queryService = getXPathQueryService(parent);
330 		try {
331 			// TODO: need namespace declaration?
332 		    if (namespaceMappings != null) {
333 		        for (Iterator i = namespaceMappings.keySet().iterator(); i.hasNext();) {
334                     Object name = i.next();
335                     Object uri = namespaceMappings.get(name);
336                     if ((name instanceof String) && (uri instanceof String)) {
337                         queryService.setNamespace((String) name, (String) uri);
338                     } // TODO else what?
339                 }
340 		    }
341 			return queryService.query(query);
342 		} catch (XMLDBException e) {
343 			LOGGER.error("Unable to perform XPath query " + query + " on collection " + parent, e);
344 			return null;
345 		} finally {
346 			try {
347 				parent.close();
348 			} catch (XMLDBException e) {
349 				LOGGER.warn("Unable to close collection " + parent, e);
350 			}
351 		}
352 	}
353 
354 	/***
355 	 * Get the specified Collection from the database.
356 	 * 
357 	 * @param path
358 	 *            full path specifying location and name of collection, using
359 	 *            '/' as path separator
360 	 * @return the Collection, or null if error occurs
361 	 */
362 	private Collection getCollection(String path) {
363 		try {
364 			if (path == null) {
365 				return null;
366 			}
367 			Collection result = xmlDbService.getBaseCollection();
368 			StringTokenizer tk = new StringTokenizer(path, "/");
369 			while (result != null && tk.hasMoreTokens()) {
370 				String name = tk.nextToken();
371 				if (name == null || name.equals("")) {
372 					continue;
373 				}
374 				result = result.getChildCollection(name);
375 			}
376 			return result;
377 		} catch (XMLDBException e) {
378 			LOGGER.error("Error while looking up collection " + path, e);
379 			return null;
380 		}
381 	}
382 
383 	/***
384 	 * Get XPathQueryService for collection.
385 	 * 
386 	 * @param collection
387 	 *            the collection
388 	 * @return the XPathQueryService, or null if error occurs
389 	 */
390 	private static XPathQueryService getXPathQueryService(Collection collection) {
391 		if (collection == null) {
392 			return null;
393 		}
394 		Service s = null;
395 		try {
396 			s = collection.getService("XPathQueryService", "1.0");
397 		} catch (XMLDBException e) {
398 			LOGGER.error("Error while getting XPathQueryService for collection " + collection, e);
399 		}
400 		if (s instanceof XPathQueryService) {
401 			return (XPathQueryService) s;
402 		} else {
403 			LOGGER.error("Unable to get XPathQueryService for collection " + collection + ", got " + s);
404 			return null;
405 		}
406 	}
407 
408 	
409 }