View Javadoc

1   /*
2    *  Copyright (C) 2003-2005 SINTEF
3    *  Author:  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.assetrepository.dao;
22  
23  import java.io.BufferedOutputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.UnsupportedEncodingException;
26  import java.lang.reflect.Constructor;
27  import java.lang.reflect.Method;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  
35  import javax.ejb.CreateException;
36  import javax.ejb.DuplicateKeyException;
37  import javax.ejb.EJBException;
38  import javax.ejb.EntityBean;
39  import javax.ejb.FinderException;
40  import javax.ejb.NoSuchEntityException;
41  import javax.ejb.ObjectNotFoundException;
42  import javax.ejb.RemoveException;
43  import javax.xml.bind.JAXBContext;
44  import javax.xml.bind.JAXBException;
45  import javax.xml.bind.Marshaller;
46  import javax.xml.bind.Unmarshaller;
47  import javax.xml.bind.UnmarshallerHandler;
48  import javax.xml.bind.ValidationEvent;
49  import javax.xml.bind.ValidationEventHandler;
50  import javax.xml.bind.ValidationEventLocator;
51  import javax.xml.bind.Validator;
52  
53  import no.sintef.assetrepository.jaxb.IOid;
54  import no.sintef.assetrepository.jaxb.impl.IOidImpl;
55  import no.sintef.xml.XmlHelper;
56  import no.sintef.xmldb.XmlDbHandler;
57  
58  import org.apache.log4j.Logger;
59  import org.xml.sax.Attributes;
60  import org.xml.sax.ContentHandler;
61  import org.xml.sax.Locator;
62  import org.xml.sax.SAXException;
63  import org.xml.sax.helpers.AttributesImpl;
64  import org.xmldb.api.base.XMLDBException;
65  import org.xmldb.api.modules.XMLResource;
66  
67  /***
68   * @author fvr
69   *
70   * To change the template for this generated type comment go to
71   * Window - Preferences - Java - Code Generation - Code and Comments
72   */
73  public abstract class AbstractDAO {
74  
75  	private static final Logger LOGGER = Logger.getLogger(AbstractDAO.class);
76      private static final String AR_NAMESPACE_NAME = "ar";
77      private static final String AR_NAMESPACE_URI = "http://www.sintef.no/2004/schemas/asset-repository-1_0.xsd";
78      private static final Map NAMESPACE_MAPPING = new HashMap();
79      
80      private static final String EXIST_NAMESPACE_URI = "http://exist.sourceforge.net/NS/exist";
81  
82  	private XmlDbHandler dbHandler = null;
83  	private Marshaller marshaller = null;
84  	private Unmarshaller unmarshaller = null;
85  	private Validator validator = null;
86  	private String collection;
87  
88  	
89      static {
90          NAMESPACE_MAPPING.put(AR_NAMESPACE_NAME, AR_NAMESPACE_URI);
91      }
92      
93  	/***
94  	 * 
95  	 */
96  	public AbstractDAO(String collection) {
97  		super();
98  		this.collection = collection;
99  	}
100 
101 	/* (non-Javadoc)
102 	 * @see coras.ar.dao.RiskAssessmentProjectVersionDAO#init()
103 	 */
104 	public void init() {
105 		try {
106 			JAXBContext jc = JAXBContext.newInstance("no.sintef.assetrepository.jaxb");
107 			marshaller = jc.createMarshaller();
108 			marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
109 			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE);
110 
111 			// TODO: Jaxme2 handles namespaces by default?
112 			/*
113 			// Namespace mapping for JAXB Reference Implementation
114 			marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
115 				public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
116 					if (XmlDbHandler.NAMESPACE_URI.equals(namespaceUri))
117 						return XmlDbHandler.PREFIX;
118 					if (CorasTable.NAMESPACE_URI.equals(namespaceUri))
119 						return CorasTable.PREFIX;
120 					return suggestion;
121 				}
122 			});
123 			*/
124 
125 			unmarshaller = jc.createUnmarshaller();
126 			validator = jc.createValidator();
127 			
128 			// FIXME JAXB debugging
129 			unmarshaller.setEventHandler(new ValidationEventHandler() {
130                 public boolean handleEvent(ValidationEvent ev) {
131                     LOGGER.error("JAXB validation event: " + ev.getMessage());
132                     ValidationEventLocator locator = ev.getLocator();
133                     LOGGER.error("Line: " + locator.getLineNumber() + ", Column: " + locator.getColumnNumber() + ", Offset: " + locator.getOffset());
134                     /*
135                     Node n = locator.getNode();
136                     LOGGER.error("Node: " + n);
137                     LOGGER.error("Object: " + locator.getObject());
138                     LOGGER.error("URL: " + locator.getURL());
139                     byte[] xmlBytes = XmlHelper.xmlToUtf8(n, true);
140 					try {
141 						LOGGER.error("Offending XML: " + new String(xmlBytes, "UTF-8"));
142 					} catch (UnsupportedEncodingException e) {
143 						LOGGER.error("Offending XML: " + new String(xmlBytes));
144 					}
145 					*/
146                     return true;
147                 }
148 			});
149 			unmarshaller.setValidating(true);
150 			
151 			dbHandler = XmlDbHandler.getHandler();
152 		} catch (JAXBException e) {
153 			// TODO Auto-generated catch block
154 			e.printStackTrace();
155 		}
156 	}
157 
158 	/***
159 	 * 
160 	 * @param pk
161 	 * @param ejb
162 	 * @throws EJBException
163 	 */
164     protected void load(Object pk, EntityBean ejb) throws EJBException {
165     	if (LOGGER.isDebugEnabled()) {
166 			LOGGER.debug("Loading entity (" + (ejb != null ? ejb.getClass() : null) + ") with primary key " + pk);
167 		}
168     	XMLResource resource = null;
169     	try {
170     		if (ejb == null)
171     			throw new EJBException("Entity bean instance is null");
172     		resource = getXMLResource(pk);
173     		Object jaxbObject = unmarshalResource(resource);
174     		setJaxbObject(ejb, jaxbObject);
175 		} catch (EJBException e) {
176 			throw e;
177 		} catch (Throwable t) {
178 			LOGGER.error(t);
179 			throw new EJBException("Error while loading Entity bean: " + t.getMessage());
180 		}
181     }
182     
183     /***
184      * 
185      * @param ejb
186      * @throws EJBException
187      */
188 	protected void store(EntityBean ejb) throws EJBException {
189 		String id = getId(ejb);
190 		if (LOGGER.isDebugEnabled()) {
191 			LOGGER.debug("Storing entity (" + (ejb != null ? ejb.getClass() : null) + ") with id " + id);
192 		}
193 		try {
194 			XMLResource resource = getXMLResource(ejb);
195 			Object jaxbObject = createJaxbObject(ejb);
196 			
197 		    if (LOGGER.isDebugEnabled()) {
198 		        ByteArrayOutputStream baos = new ByteArrayOutputStream();
199 		        marshaller.marshal(jaxbObject, new BufferedOutputStream(baos));
200 		        LOGGER.debug("marshalling: " + new String(baos.toByteArray(), "UTF-8"));
201 		    }
202 
203 		    validator.setEventHandler(new ValidationEventHandler() {
204                 public boolean handleEvent(ValidationEvent ev) {
205                     LOGGER.error("JAXB validation error: " + ev.getMessage());
206                     return true;
207                 }
208 		    });
209 		    validator.validate(jaxbObject);
210 			
211 		    storeResource(jaxbObject, resource);
212 		} catch (XMLDBException e) {
213 			throw new EJBException("Unable to store entity bean because of database error: " + e.getMessage());
214 		} catch (EJBException e) {
215 			throw e;
216 		} catch (Throwable t) {
217 			LOGGER.error(t);
218 			throw new EJBException("Error while storing Entity bean " + id + ": " + t.getMessage());
219 		}
220     }
221 
222 	/***
223 	 * @param jaxbObject
224 	 * @param resource
225 	 * @throws XMLDBException
226 	 * @throws SAXException
227 	 * @throws JAXBException
228 	 */
229 	private void storeResource(Object jaxbObject, XMLResource resource) throws XMLDBException, SAXException, JAXBException {
230 		ContentHandler contentHandler = resource.setContentAsSAX();
231 //		contentHandler.startDocument(); // FIXME JaxMe doesn't output startDocument event
232 		marshaller.marshal(jaxbObject, contentHandler);
233 //		contentHandler.endDocument(); // FIXME JaxMe doesn't output endDocument event
234 		dbHandler.storeResource(resource);
235 	}
236 
237 	/***
238 	 * 
239 	 * @param pk
240 	 * @throws RemoveException
241 	 * @throws EJBException
242 	 */
243 	protected void remove(Object pk) throws RemoveException, EJBException {
244 		if (LOGGER.isDebugEnabled()) {
245 			LOGGER.debug("Removing entity with primary key " + pk);
246 		}
247 		try {
248 			XMLResource resource = getXMLResource(pk);
249 			dbHandler.deleteResource(resource);
250 		} catch (XMLDBException e) {
251 			throw new RemoveException("Unable to remove entity with primary key " + pk + ": " + e.getMessage());
252 		} catch (EJBException e) {
253 			throw e;
254 		} catch (Throwable t) {
255 			LOGGER.error(t);
256 			throw new EJBException("Error while removing Entity bean " + pk + ": " + t.getMessage());
257 		}
258 	}
259 
260 	/***
261 	 * 
262 	 * @param ejb
263 	 * @return
264 	 * @throws CreateException
265 	 * @throws EJBException
266 	 */
267 	protected Object create(EntityBean ejb) throws CreateException, EJBException {
268 		if (LOGGER.isDebugEnabled()) {
269 			LOGGER.debug("Creating entity (" + (ejb != null ? ejb.getClass() : null) + ") with id " + getId(ejb));
270 		}
271 		try {
272 			if (ejb == null)
273 				throw new EJBException("Entity bean is null");
274 			String id = getId(ejb);
275 			if (id == null)
276 				throw new CreateException("Entity id is null");
277 			Object pk = getPrimaryKey(id);
278 			try {
279 				if (findByPrimaryKey(pk) != null) {
280 					throw new DuplicateKeyException("Duplicate primary key: " + pk);
281 				}
282 			} catch (FinderException e) {
283 				// This is desired - means key is not in use
284 			}
285 			
286 			XMLResource resource = dbHandler.createXmlResource(collection);
287 //			Object jaxbObject = createJaxbObject(id);
288 			Object jaxbObject = createJaxbObject(ejb);
289 			storeResource(jaxbObject, resource);
290 			return pk;
291 		} catch (DuplicateKeyException e) {
292 			throw e;
293 		} catch (EJBException e) {
294 			throw e;
295 		} catch (XMLDBException e) {
296 			throw new CreateException("Unable to create entity resource due to database error: " + e.getMessage());
297 		} catch (Throwable t) {
298 			LOGGER.error(t);
299 			throw new CreateException("Error while creating Entity bean: " + t.getMessage());
300 		}
301 	}
302 
303 	/***
304 	 * @param pk
305 	 * @return
306 	 */
307 	protected Object findByPrimaryKey(Object pk) throws FinderException {
308 		if (LOGGER.isDebugEnabled()) {
309 			LOGGER.debug("findByPrimaryKey(" + pk + ")");
310 		}
311 		try {
312 			XMLResource resource = getXMLResource(pk);
313 			return pk;
314 		} catch (NoSuchEntityException e) {
315 			throw new ObjectNotFoundException("Could not find entity resource with primary key " + pk);
316 		} catch (Throwable t) {
317 			LOGGER.error(t);
318 			throw new ObjectNotFoundException("Could not find entity resource with primary key " + pk);
319 		}
320 	}
321 
322 	/***
323 	 * 
324 	 * @param query
325 	 * @return
326 	 */
327 	protected Collection getPrimaryKeys(String query) {
328 		if (LOGGER.isDebugEnabled()) {
329 			LOGGER.debug("getPrimaryKeys(" + query + ")");
330 		}
331 		XMLResource resource = null;
332 		try {
333 			XMLResource[] resources = dbHandler.getXmlResources(collection, query, NAMESPACE_MAPPING);
334 			ArrayList result = new ArrayList(resources.length);
335 			for (int i = 0; i < resources.length; i++) {
336 				resource = resources[i];
337 				Object jaxbObject = unmarshalResource(resource);
338 				String id = getId(jaxbObject);
339 				if (id != null) {
340 					result.add(getPrimaryKey(id));
341 				}
342 			}
343 			return result;
344 		} catch (Throwable t) {
345 			LOGGER.error(t);
346 			return new ArrayList();
347 		}
348 	}
349 
350 	protected void pksToOids(Collection pks, List oids, Class pkClass) {
351 	    if (pks == null) {
352 	        LOGGER.warn("pksToOids: pks is null");
353 	        return; // FIXME should not happen!?
354 	    }
355 	    if (oids == null) {
356 	        LOGGER.error("pksToOids: oids is null");
357 	        return; // FIXME should not happen!?
358 	    }
359 	    try {
360 	        Method getIdMethod = pkClass.getMethod("getId", null);
361 	        for (Iterator i = pks.iterator(); i.hasNext();) {
362 	            Object pk = i.next();
363 	            if (! (pkClass.isInstance(pk))) {
364 	                LOGGER.warn("Expected " + pkClass.getName() + "primary key, got " + (pk != null ? pk.getClass() : null));
365 	                continue; // FIXME: Error!
366 	            }
367 	            IOid oid = new IOidImpl();
368 	            String id = (String) getIdMethod.invoke(pk, null);
369 	            oid.setOid(id);
370 	            oids.add(oid);
371 	        }
372 	    } catch (Throwable t) {
373 	    	LOGGER.error(t);
374 	    }
375 	}
376 	
377 	protected Collection oidsToPks(List oids, Class pkClass) {
378 		Collection pks = new ArrayList();
379 		if (oids == null) {
380 		    LOGGER.warn("oidsToPks: oids is null");
381 		    return pks; // FIXME should not happen!?
382 		}
383 		try {
384 		    Class[] clazz = { String.class };
385             Constructor constructor = pkClass.getConstructor(clazz);
386             for (Iterator i = oids.iterator(); i.hasNext();) {
387                 Object o = i.next();
388                 if (! (o instanceof IOid)) {
389                     LOGGER.warn("Expected oid, got " + (o != null ? o.getClass() : null));
390                     continue; // FIXME: Error!
391                 }
392                 IOid oid = (IOid) o;
393                 Object[] args = { oid.getOid() };
394                 Object pk = constructor.newInstance(args);
395                 pks.add(pk);
396             }
397         } catch (Throwable t) {
398         	LOGGER.error(t);
399         }
400         return pks;
401 	}
402 	
403 	/***
404 	 * @param pk
405 	 * @return
406 	 */
407 	private XMLResource getXMLResource(Object pk) throws NoSuchEntityException {
408 		String id = getId(pk);
409 		if (id == null) {
410 			throw new NoSuchEntityException("Primary key is null");
411 		}
412 		String query = getSingleResourceQuery(id);
413 		XMLResource[] resources = dbHandler.getXmlResources(collection, query, NAMESPACE_MAPPING);
414 		if (resources == null || resources.length == 0 || resources[0] == null) {
415 			throw new NoSuchEntityException("No entity with primary key " + id + " found for query " + query);
416 		}
417 		if (resources.length > 1) {
418 			LOGGER.error("Primary key " + id + " is not unique");
419 		}
420 		return resources[0];
421 	}
422 
423     /***
424      * @param resource
425      * @return
426      */
427     private Object unmarshalResource(XMLResource resource) {
428     	try {
429     	    
430     	    // FIXME JAXB debugging
431     	    if (LOGGER.isDebugEnabled()) {
432     	    	byte[] bytes = XmlHelper.xmlToUtf8(resource.getContentAsDOM(), true);
433     	        try {
434     	            LOGGER.debug("unmarshalling: " + new String(bytes, "UTF-8"));
435     	        } catch (UnsupportedEncodingException e) {
436     	            // TODO Auto-generated catch block
437     	            e.printStackTrace();
438     	        }
439     	    }
440     	    
441 //    	    return unmarshaller.unmarshal(new ByteArrayInputStream(bytes));
442 
443     	    final UnmarshallerHandler contentHandler = unmarshaller.getUnmarshallerHandler();
444     		ContentHandler filter = new ContentHandler() {
445                 public void setDocumentLocator(Locator locator) {
446                     contentHandler.setDocumentLocator(locator);
447                 }
448                 public void startDocument() throws SAXException {
449                     contentHandler.startDocument();
450                 }
451                 public void endDocument() throws SAXException {
452                     contentHandler.endDocument();
453                 }
454                 public void startPrefixMapping(String prefix, String uri) throws SAXException {
455                     contentHandler.startPrefixMapping(prefix, uri);
456                 }
457                 public void endPrefixMapping(String prefix) throws SAXException {
458                     contentHandler.endPrefixMapping(prefix);
459                 }
460                 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
461                     AttributesImpl newAtts = new AttributesImpl();
462                     for (int i = 0; i < atts.getLength(); i++) {
463                         String uri = atts.getURI(i);
464                         if (!EXIST_NAMESPACE_URI.equals(uri)) {
465                         	newAtts.addAttribute(uri, 
466                         			atts.getLocalName(i),
467                         			atts.getQName(i), 
468                         			atts.getType(i), 
469                         			atts.getValue(i));
470                         }
471                     }
472                     contentHandler.startElement(namespaceURI, localName, qName, newAtts);
473                 }
474                 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
475                     contentHandler.endElement(namespaceURI, localName, qName);
476                 }
477                 public void characters(char[] ch, int start, int length) throws SAXException {
478                     contentHandler.characters(ch, start, length);
479                 }
480                 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
481                     contentHandler.ignorableWhitespace(ch, start, length);
482                 }
483                 public void processingInstruction(String target, String data) throws SAXException {
484                     contentHandler.processingInstruction(target, data);
485                 }
486                 public void skippedEntity(String name) throws SAXException {
487                     contentHandler.skippedEntity(name);
488                 }
489     		};
490     	    // JAXB has trouble with non-declared attributes from eXist...
491     		// resource.getContentAsSAX(contentHandler);
492     		resource.getContentAsSAX(filter);
493     		return contentHandler.getResult();
494     	} catch (XMLDBException e) {
495     		throw new EJBException("Database error while loading XML resource: " + e.getMessage(), e);
496     	} catch (Throwable e) {
497 			if (LOGGER.isDebugEnabled()) {
498 				/*
499 				try {
500 					LOGGER.debug("Offending XML: " + new String(bytes, "UTF-8"));
501 				} catch (UnsupportedEncodingException ue) {
502 					LOGGER.debug("Offending XML: " + new String(bytes));
503 				}
504 				*/
505 				try {
506 					LOGGER.debug("Error unmarshalling XML:");
507 					LOGGER.debug(resource.getContent());
508 				} catch (XMLDBException xe) {
509 					LOGGER.debug(xe);
510 				}
511 			}
512 			if (e instanceof Exception) {
513 				throw new EJBException("Error while parsing XML content: " + e.getMessage(), (Exception)e);
514 			} else {
515 				LOGGER.error(e);
516 				throw new EJBException("Error while parsing XML content: " + e.getMessage());
517 			}
518     	}
519     }
520 
521 	/***
522 	 * @param o The object containing the primary key. 
523 	 *          May be an Entity bean, primary key or JAXB object.
524 	 * @return
525 	 */
526 	protected abstract String getId(Object o);
527 
528 	/***
529 	 * @param id The primary key ID, <strong>never</strong> <code>null</code>.
530 	 * @return
531 	 */
532 	protected abstract Object getPrimaryKey(String id);
533 	
534 	/***
535 	 * @param ejb
536 	 * @return
537 	 */
538 	protected abstract Object createJaxbObject(EntityBean ejb);
539 
540     /***
541 	 * @param ejb
542 	 * @param jaxbObject
543 	 */
544 	protected abstract void setJaxbObject(EntityBean ejb, Object jaxbObject);
545 
546 	/***
547 	 * @param id
548 	 * @return
549 	 */
550 	protected abstract String getSingleResourceQuery(String id);
551 
552     /***
553      * @param id
554      * @return
555      */
556     protected static final IOid createIOid(String id) {
557         IOid oid = new IOidImpl();
558         oid.setOid(id);
559         return oid;
560     }
561 
562 }
563 
564