View Javadoc

1   /**
2    * Copyright 2005-2006 the original author or authors.
3    *
4    * Licensed under the Gnu General Pubic License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    *
8    *      http://www.opensource.org/licenses/gpl-license.php
9    *
10   * This program 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.
13   * See the Gnu General Public License for more details.
14   */
15  package org.figure8.join.core.persistence;
16  
17  import org.figure8.join.util.LogUtil;
18  
19  import org.apache.commons.logging.Log;
20  import org.xml.sax.Attributes;
21  import org.xml.sax.SAXException;
22  import org.xml.sax.SAXParseException;
23  import org.xml.sax.helpers.DefaultHandler;
24  import net.sf.hibernate.SessionFactory;
25  import net.sf.hibernate.MappingException;
26  import net.sf.hibernate.HibernateException;
27  import net.sf.hibernate.id.Assigned;
28  import net.sf.hibernate.id.IdentifierGenerator;
29  import net.sf.hibernate.engine.SessionFactoryImplementor;
30  import net.sf.hibernate.engine.SessionImplementor;
31  import net.sf.hibernate.metadata.ClassMetadata;
32  import net.sf.hibernate.persister.ClassPersister;
33  import net.sf.hibernate.collection.CollectionPersister;
34  import net.sf.hibernate.type.Type;
35  import net.sf.hibernate.type.DateType;
36  import net.sf.hibernate.type.TextType;
37  import net.sf.hibernate.type.StringType;
38  import net.sf.hibernate.type.LiteralType;
39  import net.sf.hibernate.type.TimestampType;
40  import net.sf.hibernate.type.ComponentType;
41  import net.sf.hibernate.type.PersistentCollectionType;
42  
43  import javax.xml.parsers.SAXParser;
44  import javax.xml.parsers.SAXParserFactory;
45  
46  import java.util.Map;
47  import java.util.Set;
48  import java.util.List;
49  import java.util.HashMap;
50  import java.util.HashSet;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.io.InputStream;
54  import java.io.Serializable;
55  import java.io.CharArrayWriter;
56  import java.sql.SQLException;
57  /**
58   * This utility class is an Xml import handler for Hibernate entity objects.
59   * It is adapted from the one written by Ara Abrahamian and available on
60   * http://opensource.atlassian.com/projects/hibernate/browse/HB-493.
61   * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
62   * @version $Revision: 1.1 $
63   */
64  public class HibernateXmlImportHandler extends DefaultHandler{
65  
66     // Static -------------------------------------------------------------------
67  
68     /** Get a commons logger */
69     private static final Log log = LogUtil.getLog(HibernateXmlImportHandler.class);
70  
71  
72     // Attributes ---------------------------------------------------------------
73  
74     /** The created objects from load() method */
75     private List objects = new ArrayList();
76     /** The map of created objects */
77     private HashMap objectsMap = new HashMap();
78     private HashMap fixedIds = new HashMap();
79     private HashMap unfixedIds = new HashMap();
80     private ArrayList compositeItPropertiesArray = new ArrayList();
81  
82     /** The object we are building for each object tag */
83     private Object object = null;
84     /** Tag contents */
85     private CharArrayWriter contents = new CharArrayWriter();
86     /** An objects collection content (collection may be a Map so use Object) */
87     private Object collection = null;
88  
89     /* Variables for storing info within tags */
90     private Class objectClass = null;
91     private String elementClass = null;
92     private String elementPackage = null;
93     private String elementIndexId = null;
94     private String propertyName = null;
95     private String propertyClass = null;
96     private String propertyPackage = null;
97     private String collectionName = null;
98     private String collectionIndexType = null;
99  
100    /* Position markers */
101    private boolean inElement = false;
102    private boolean inProperty = false;
103    private boolean inCompositeId = false;
104    /** Whether identifiers should be presevered during import */
105    private boolean preserveIds = false;
106 
107    /** The Hibernate session implementor to use */
108    private SessionImplementor session = null;
109    /** The Hibernate session factory implementor to use for getting entities metadata */
110    private SessionFactoryImplementor factory = null;
111 
112 
113    // Constructors -------------------------------------------------------------
114 
115    /**
116     * Creates a new instance of HibernateXmlImportHandler
117     * @param factory The SessionFactory to use for getting entities metadata
118     */
119    public HibernateXmlImportHandler(SessionFactory factory){
120       super();
121       this.factory = (SessionFactoryImplementor)factory;
122    }
123 
124 
125    // Public -------------------------------------------------------------------
126 
127    /** @return The loaded entity objects */
128    public List getEntityObjects(){
129       return objects;
130    }
131 
132    /**
133     * Load the entity objects from their Xml representation read on stream
134     * @param inputstream The stream for reading Xml representations
135     * @throws HibernateException if metadata on entity objects cannot be retrieved
136     */
137    public Collection loadEntityObjects(InputStream inputstream) throws HibernateException{
138       // Open session (necessary for instantiating ids)
139       session = (SessionImplementor)factory.openSession();
140       try{
141          // Create a new SAX parser and use current object has handler.
142          SAXParserFactory parserFactory = SAXParserFactory.newInstance();
143          parserFactory.setValidating(false);
144          SAXParser saxparser = parserFactory.newSAXParser();
145          saxparser.parse(inputstream , this);
146       }
147       catch (SAXParseException sax){
148          // Log and wrap as Hibernate exception.
149          log.error(new StringBuffer().append("Error parsing entities.xml : line [").append(sax.getLineNumber()).append("], col [").append(sax.getColumnNumber()).append("]. ").toString() , sax);
150          throw new HibernateException("Error processing backup. Refer to logs for more details" , sax);
151       }
152       catch (Exception exception){
153          // Log and wrap as Hibernate exception.
154          log.error("Error processing backup: " , exception);
155          throw new HibernateException("Error processing backup. Refer to logs for more details." , exception);
156       }
157       finally{
158          // Close the session.
159          session.close();
160       }
161       return objects;
162    }
163 
164 
165    // Override of DefaultHandler -----------------------------------------------
166 
167    /**
168     * Start an Xml element handling method
169     * @param uri The namespace URI of parsed stream
170     * @param localName Local name of started element
171     * @param qName Qualified name of started element
172     * @param attributes Attributes of started element
173     * @throws SAXException if something wrong occurs
174     */
175    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException{
176       try{
177          if (HibernateXmlDatabinder.CONST_OBJECT.equals(qName)){
178             objectClass = getClassForElement(attributes, HibernateXmlDatabinder.CONST_CLASS, "package");
179          }
180          else if (HibernateXmlDatabinder.CONST_COMPOSITE_ID.equals(qName)){
181             compositeItPropertiesArray.clear();
182             inCompositeId = true;
183          }
184          else if (HibernateXmlDatabinder.CONST_PROPERTY.equals(qName)){
185             propertyName = attributes.getValue(HibernateXmlDatabinder.CONST_NAME);
186             propertyPackage = attributes.getValue("package");
187             propertyClass = attributes.getValue(HibernateXmlDatabinder.CONST_CLASS);
188             inProperty = true;
189          }
190          else if (HibernateXmlDatabinder.CONST_ELEMENT.equals(qName)){
191             elementClass = attributes.getValue(HibernateXmlDatabinder.CONST_CLASS);
192             elementPackage = attributes.getValue("package");
193             elementIndexId = attributes.getValue(HibernateXmlDatabinder.CONST_INDEX_ID);
194             inElement = true;
195          }
196          else if (HibernateXmlDatabinder.CONST_COLLECTION.equals(qName)){
197             collectionName = attributes.getValue(HibernateXmlDatabinder.CONST_NAME);
198             collectionIndexType = attributes.getValue(HibernateXmlDatabinder.CONST_INDEX_TYPE);
199             handleStartCollection();
200          }
201       }
202       catch (Exception exception) {
203          throw new SAXException(exception);
204       }
205    }
206 
207    /**
208     * Read content as characteers
209     * @param iArr The characters array read
210     * @param start Start index
211     * @param length Charactes length
212     * @throws SAXException if somethong wrong occurs
213     */
214    public void characters(char[] iArr, int start, int length) throws SAXException{
215       contents.write(iArr , start , length);
216    }
217 
218    /**
219     * End an Xml element handling method
220     * @param uri The namespace URI of parsed stream
221     * @param localName Local name of ended element
222     * @param qName Qualified name of ended element
223     * @throws SAXException if somethong wrong occurs
224     */
225    public void endElement(String uri, String localName, String qName) throws SAXException{
226       try{
227          if (HibernateXmlDatabinder.CONST_OBJECT.equals(qName))
228             object = null;
229          else if (HibernateXmlDatabinder.CONST_ID.equals(qName))
230             handleId();
231          else if (HibernateXmlDatabinder.CONST_COMPOSITE_ID.equals(qName))
232             handleCompositeId();
233          else if (HibernateXmlDatabinder.CONST_PROPERTY.equals(qName))
234             handleProperty();
235          else if (HibernateXmlDatabinder.CONST_ELEMENT.equals(qName))
236             inElement = false;
237          else if (HibernateXmlDatabinder.CONST_COLLECTION.equals(qName))
238             handleStartCollection();
239          contents.reset();
240       }
241       catch (Exception exception) {
242          exception.printStackTrace();
243          throw new SAXException(exception);
244       }
245    }
246 
247 
248    // Protected ---------------------------------------------------------------
249 
250    /** @return The tag contents as text */
251    protected String getContents(){
252       return (contents.toString());
253    }
254 
255    /** @return The ClassPersister correponsding to this class of entity */
256    protected ClassPersister getPersister(Class clazz) throws MappingException{
257       return factory.getPersister(clazz);
258    }
259 
260 
261    // Private -----------------------------------------------------------------
262 
263    /** Retrieve the class from an element attributes */
264    private Class getClassForElement(Attributes attributes, String classAttributeName, String packageAttributeName)
265            throws ClassNotFoundException{
266       String className = attributes.getValue(classAttributeName);
267       String packageName = packageAttributeName == null ? "" : attributes.getValue(packageAttributeName);
268       return getClassForElement(className, packageName);
269 	}
270    /** Load the class with FQN corresponding to package + class */
271    private Class getClassForElement(String className, String packageName)throws ClassNotFoundException{
272        String fullyQualifiedClassName = packageName + "." + className;
273        Class clazz = Class.forName(fullyQualifiedClassName);
274        return clazz;
275 	}
276 
277    /** Handle the creation of an object identifier */
278    private void handleId() throws Exception{
279 	   ClassPersister persister = getPersister(objectClass);
280 	   Type idType = persister.getIdentifierType();
281 	   String idStrValue = getContents();
282 	   if (!inProperty && !inElement)
283          object = addObjectOrGetExisting(persister, idStrValue, idType, objectClass);
284 	   else
285 	      handleIdForAssociationElement(persister, idStrValue);
286    }
287 
288    /** Handle the creation of a composite object identifier */
289    private void handleCompositeId() throws Exception{
290       ClassPersister persister = getPersister(objectClass);
291       Type idType = persister.getIdentifierType();
292 	   Object obj = addObjectOrGetExisting(persister, compositeItPropertiesArray, idType, objectClass);
293 	   Object propertyValuesOfCompositeId[] = new Object[compositeItPropertiesArray.size()];
294 	   Type subtypes[] = ((ComponentType)idType).getSubtypes();
295 	   for (int i=0; i<subtypes.length; i++){
296          Type subtype = subtypes[i];
297 	      propertyValuesOfCompositeId[i] = extractValue(subtype, (String)compositeItPropertiesArray.get(i));
298       }
299 
300 	   ((ComponentType)idType).setPropertyValues(obj, propertyValuesOfCompositeId);
301 	   if (!inProperty && !inElement)
302          object = obj;
303 	   else
304          handleIdForAssociationElement(persister, compositeItPropertiesArray);
305 	   inCompositeId = false;
306 	}
307 
308    /** Handle the creation of an object property */
309    private void handleProperty() throws Exception{
310       ClassPersister persister = getPersister(objectClass);
311       // If no object, this means no id...
312       // Create a new object, generate an id and add it
313       if (object == null){
314          object = objectClass.newInstance();
315          IdentifierGenerator generator = persister.getIdentifierGenerator();
316          Object idValue = generator.generate(session, object);
317          persister.setIdentifier(object, (Serializable)idValue);
318          objects.add(object);
319       }
320       if (inCompositeId){
321          compositeItPropertiesArray.add(getContents());
322       }
323       else{
324          String propertyValueStr = getContents();
325          Type type = persister.getPropertyType(propertyName);
326          if ((type instanceof LiteralType) || (type instanceof TextType)){
327             if ((type instanceof StringType) || (type instanceof TextType))
328                propertyValueStr = unescapeCDATA(propertyValueStr);
329             Object propertyValue = extractValue(type, propertyValueStr);
330             ((ClassMetadata)persister).setPropertyValue(object, propertyName, propertyValue);
331          }
332       }
333       inProperty = false;
334    }
335 
336    /** Handle the creation of a collection of associated objects */
337    private void handleStartCollection() throws Exception{
338       ClassPersister persister = getPersister(objectClass);
339       //collection = (Collection)persister.getPropertyValue(object, collectionName);
340       collection = persister.getPropertyValue(object, collectionName);
341       if (collection == null){
342          Type type = persister.getPropertyType(collectionName);
343          CollectionPersister collectionPersister = session.getFactory().getCollectionPersister(((PersistentCollectionType)type).getRole());
344          //collection = (Collection)((PersistentCollectionType)type).instantiate(session, collectionPersister);
345          collection = ((PersistentCollectionType)type).instantiate(session, collectionPersister);
346          if (collection instanceof Set)
347             collection = new HashSet();
348          else if (collection instanceof List)
349             collection = new ArrayList();
350          else if (collection instanceof Map)
351             collection = new HashMap();
352          ((ClassMetadata)persister).setPropertyValue(object, collectionName, collection);
353       }
354    }
355 
356    /** Handle the creation of identifier for object being an association element */
357    private void handleIdForAssociationElement(ClassPersister persister, Object idValue)
358            throws ClassNotFoundException, HibernateException, SQLException, IllegalAccessException, InstantiationException{
359       String className;
360 	   String packageName;
361 	   if (inProperty){
362          className = propertyClass;
363 	      packageName = propertyPackage;
364       }
365 	   else{
366 	      className = elementClass;
367          packageName = elementPackage;
368       }
369       Class entityClazz = getClassForElement(className, packageName);
370       ClassPersister entityPersister = getPersister(entityClazz);
371       Type entityIdType = entityPersister.getIdentifierType();
372       ClassPersister returnedClassPersister = getPersister(entityClazz);
373       Object propertyValue = addObjectOrGetExisting(returnedClassPersister, idValue, entityIdType, entityClazz);
374       if (inProperty)
375          ((ClassMetadata)persister).setPropertyValue(object, propertyName, propertyValue);
376       else if (inElement){
377          if (collectionIndexType == null)
378             ((Collection)collection).add(propertyValue);
379          else{
380             // Collection is indexed : it's a Map.
381             Class indexClazz = Class.forName(collectionIndexType);
382             ClassPersister indexClassPersister = getPersister(indexClazz);
383             Type indexIdType = indexClassPersister.getIdentifierType();
384             // Retrieve key entity.
385             Object keyValue = addObjectOrGetExisting(indexClassPersister, elementIndexId, indexIdType, indexClazz);
386             ((Map)collection).put(keyValue, propertyValue);
387          }
388       }
389    }
390 
391    /** Create and add a new object or get an already existing one */
392    private Object addObjectOrGetExisting(ClassPersister persister, Object rawIdValue, Type idType, Class clazz)
393            throws HibernateException, SQLException, IllegalAccessException, InstantiationException{
394 	   if (rawIdValue == null)
395          return clazz.newInstance();
396 	   Object object = null;
397       if (persister.hasIdentifierPropertyOrEmbeddedCompositeIdentifier()){
398          Object idValue = null;
399          if (persister.hasIdentifierProperty())
400             idValue = extractValue(idType, rawIdValue.toString());
401          else if (persister.getIdentifierType() instanceof ComponentType)
402             idValue = rawIdValue;
403          idValue = getFixedIdFor(persister, idValue, object);
404          ClassAndIdPair classAndIdPair = new ClassAndIdPair(clazz, idValue);
405          object = objectsMap.get(classAndIdPair);
406          if (object == null){
407             if (persister.hasIdentifierProperty())
408                object = session.instantiate(clazz, (Serializable)idValue);
409             else
410                object = clazz.newInstance();
411             if (persister.hasIdentifierProperty())
412                persister.setIdentifier(object, (Serializable)idValue);
413             objects.add(object);
414             objectsMap.put(classAndIdPair, object);
415          }
416       }
417       else object = clazz.newInstance();
418       return object;
419    }
420 
421    /** @return Object value corresponding to string for type */
422    private Object extractValue(Type type, String value) throws HibernateException{
423       if (value != null) value = value.trim();
424 
425       if (type instanceof TimestampType || type instanceof DateType){
426          try{
427             if (value == null || value.length() == 0) return null;
428             if (type instanceof TimestampType)
429                return HibernateXmlDatabinder.parseTimestamp(value);
430             return HibernateXmlDatabinder.parseDate(value);
431          }
432          catch (Exception e) {return null;}
433       }
434       return type.fromString(value);
435    }
436 
437    /** Retrieve or generate a broken identifier */
438    private Object getFixedIdFor(ClassPersister persister, Object idValue, Object obj) throws HibernateException, SQLException{
439       if (preserveIds)
440          return idValue;
441       Class mappedClass = persister.getMappedClass();
442       ClassAndIdPair key = new ClassAndIdPair(mappedClass, idValue);
443       Object fixedId = fixedIds.get(key);
444       if (fixedId == null){
445          IdentifierGenerator identifierGenerator = persister.getIdentifierGenerator();
446          if (identifierGenerator instanceof Assigned)
447             fixedId = idValue;
448          else
449             fixedId = identifierGenerator.generate(session, obj);
450          // Record fixed ids.
451          fixedIds.put(key, fixedId);
452          ClassAndIdPair unfixedKey = new ClassAndIdPair(mappedClass, fixedId);
453          unfixedIds.put(unfixedKey, idValue);
454       }
455       return fixedId;
456 	}
457 
458    /** @return The given string with unescape CDATA markers */
459    private static String unescapeCDATA(String string){
460       if (string.indexOf("]] ") < 0)
461          return string;
462       return string.replaceAll("\\]\\] " , "]]");
463    }
464 
465 
466    // Inner classes ------------------------------------------------------------
467 
468    /** Represent an identifier based on class and id of entity */
469    private final class ClassAndIdPair{
470       public Class clazz = null;
471 	   public Object id = null;
472 
473       public ClassAndIdPair(Class clazz, Object id){
474          this.clazz = clazz;
475          this.id = id;
476       }
477 
478       public int hashCode(){
479          int result = clazz.hashCode();
480          result = 29 * result + (id == null ? 0 : id.hashCode());
481          return result;
482       }
483 
484       public boolean equals(Object obj){
485          ClassAndIdPair theOtherObj = (ClassAndIdPair)obj;
486          return clazz.equals(theOtherObj.clazz) && (id != null ? id.equals(theOtherObj.id) : theOtherObj.id == null);
487       }
488    }
489 }
490