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
67
68 /** Get a commons logger */
69 private static final Log log = LogUtil.getLog(HibernateXmlImportHandler.class);
70
71
72
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
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
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
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
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
139 session = (SessionImplementor)factory.openSession();
140 try{
141
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
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
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
159 session.close();
160 }
161 return objects;
162 }
163
164
165
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
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
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
312
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
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
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
381 Class indexClazz = Class.forName(collectionIndexType);
382 ClassPersister indexClassPersister = getPersister(indexClazz);
383 Type indexIdType = indexClassPersister.getIdentifierType();
384
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
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
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