001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.business.hibernate;
020:
021: import java.io.StringBufferInputStream;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.LinkedList;
025: import java.util.List;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.hibernate.HibernateException;
029: import org.hibernate.Session;
030: import org.hibernate.SessionFactory;
031: import org.hibernate.Transaction;
032: import org.hibernate.cfg.Configuration;
033: import org.apache.roller.RollerException;
034: import org.apache.roller.pojos.Assoc;
035: import org.apache.roller.pojos.HierarchicalPersistentObject;
036: import org.apache.roller.pojos.PersistentObject;
037: import org.jdom.Attribute;
038: import org.jdom.Document;
039: import org.jdom.Element;
040: import org.jdom.input.SAXBuilder;
041: import org.jdom.output.DOMOutputter;
042: import org.xml.sax.EntityResolver;
043: import org.xml.sax.InputSource;
044:
045: /**
046: * Base class for Hibernate persistence implementation.
047: *
048: * This class serves as a helper/util class for all of the Hibernate
049: * manager implementations by providing a set of basic persistence methods
050: * that can be easily reused.
051: *
052: */
053: public class HibernatePersistenceStrategy {
054:
055: static final long serialVersionUID = 2561090040518169098L;
056:
057: protected static SessionFactory sessionFactory = null;
058:
059: private static Log log = LogFactory
060: .getLog(HibernatePersistenceStrategy.class);
061:
062: /** No-op so XML parser doesn't hit the network looking for Hibernate DTDs */
063: private EntityResolver noOpEntityResolver = new EntityResolver() {
064: public InputSource resolveEntity(String publicId,
065: String systemId) {
066: return new InputSource(new StringBufferInputStream(""));
067: }
068: };
069:
070: public HibernatePersistenceStrategy() {
071: }
072:
073: /**
074: * Construct self using Hibernate config resource and optional dialect.
075: * @param configResouce Classpath-based path to Hibernate config file (e.g. "/hibernate.cgf.xml")
076: * @parma dialect Classname of Hibernate dialect to be used (overriding any specified in the configResource)
077: */
078: public HibernatePersistenceStrategy(String configResource,
079: String dialect) throws Exception {
080:
081: log.info("configResource: " + configResource);
082: log.info("dialect: " + dialect);
083:
084: // read configResource into DOM form
085: SAXBuilder builder = new SAXBuilder();
086: builder.setEntityResolver(noOpEntityResolver);
087: Document configDoc = builder.build(getClass()
088: .getResourceAsStream(configResource));
089: Element root = configDoc.getRootElement();
090: Element sessionFactoryElem = root.getChild("session-factory");
091:
092: // remove any existing connection.datasource and dialect properties
093: List propertyElems = sessionFactoryElem.getChildren("property");
094: List removeList = new ArrayList();
095: for (Iterator it = propertyElems.iterator(); it.hasNext();) {
096: Element elem = (Element) it.next();
097: if (elem.getAttribute("name") != null
098: && elem.getAttribute("name").getValue().equals(
099: "dialect")) {
100: removeList.add(elem);
101: }
102: }
103: for (Iterator it = removeList.iterator(); it.hasNext();) {
104: Element elem = (Element) it.next();
105: sessionFactoryElem.removeContent(elem);
106: }
107:
108: // add Roller dialect property
109: Element prop = new Element("property")
110: .setAttribute(new Attribute("name", "dialect"));
111: prop.addContent(dialect);
112: sessionFactoryElem.addContent(prop);
113:
114: Configuration config = new Configuration();
115: DOMOutputter outputter = new DOMOutputter();
116: config.configure(outputter.output(configDoc));
117: this .sessionFactory = config.buildSessionFactory();
118: }
119:
120: /**
121: * Construct self using Hibernate config resource and optional dialect.
122: * @param configResouce Classpath-based path to Hibernate config file (e.g. "/hibernate.cgf.xml")
123: * @parma dialect Classname of Hibernate dialect to be used (or null to use one specified in configResource)
124: */
125: public HibernatePersistenceStrategy(String configResource,
126: String dialect, String driverClass, String connectionURL,
127: String username, String password) throws Exception {
128:
129: log.info("configResource: " + configResource);
130: log.info("dialect: " + dialect);
131: log.info("driverClass: " + driverClass);
132: log.info("connectionURL: " + connectionURL);
133: log.info("username: " + username);
134:
135: // read configResource into DOM form
136: SAXBuilder builder = new SAXBuilder();
137: builder.setEntityResolver(noOpEntityResolver);
138: Document configDoc = builder.build(getClass()
139: .getResourceAsStream(configResource));
140: Element root = configDoc.getRootElement();
141: Element sessionFactoryElem = root.getChild("session-factory");
142:
143: // remove any existing connection.datasource and dialect properties
144: List propertyElems = sessionFactoryElem.getChildren("property");
145: List removeList = new ArrayList();
146: for (Iterator it = propertyElems.iterator(); it.hasNext();) {
147: Element elem = (Element) it.next();
148: if (elem.getAttribute("name") != null
149: && elem.getAttribute("name").getValue().equals(
150: "connection.datasource")) {
151: removeList.add(elem);
152: }
153: if (elem.getAttribute("name") != null
154: && elem.getAttribute("name").getValue().equals(
155: "dialect")) {
156: removeList.add(elem);
157: }
158: }
159: for (Iterator it = removeList.iterator(); it.hasNext();) {
160: Element elem = (Element) it.next();
161: sessionFactoryElem.removeContent(elem);
162: }
163:
164: // add JDBC connection params instead
165: Element prop = new Element("property")
166: .setAttribute(new Attribute("name",
167: "hibernate.connection.driver_class"));
168: prop.addContent(driverClass);
169: sessionFactoryElem.addContent(prop);
170:
171: prop = new Element("property").setAttribute(new Attribute(
172: "name", "hibernate.connection.url"));
173: prop.addContent(connectionURL);
174: sessionFactoryElem.addContent(prop);
175:
176: prop = new Element("property").setAttribute(new Attribute(
177: "name", "hibernate.connection.username"));
178: prop.addContent(username);
179: sessionFactoryElem.addContent(prop);
180:
181: prop = new Element("property").setAttribute(new Attribute(
182: "name", "hibernate.connection.password"));
183: prop.addContent(password);
184: sessionFactoryElem.addContent(prop);
185:
186: prop = new Element("property").setAttribute(new Attribute(
187: "name", "dialect"));
188: prop.addContent(dialect);
189: sessionFactoryElem.addContent(prop);
190:
191: Configuration config = new Configuration();
192: DOMOutputter outputter = new DOMOutputter();
193: config.configure(outputter.output(configDoc));
194: this .sessionFactory = config.buildSessionFactory();
195: }
196:
197: /**
198: * Get persistence session on current thread.
199: *
200: * This will open a new Session if one is not already open, otherwise
201: * it will return the already open Session.
202: */
203: public Session getSession() {
204:
205: log.debug("Opening Hibernate Session");
206:
207: // get Hibernate Session and make sure we are in a transaction
208: // this will join existing Session/Transaction if they exist
209: Session session = sessionFactory.getCurrentSession();
210: session.beginTransaction();
211:
212: return session;
213: }
214:
215: public void flush() throws RollerException {
216:
217: Session session = getSession();
218: try {
219: session.getTransaction().commit();
220: } catch (Throwable t) {
221: // uh oh ... failed persisting, gotta release
222: release();
223:
224: // wrap and rethrow so caller knows something bad happened
225: throw new RollerException(t);
226: }
227: }
228:
229: /**
230: * Release database session, rollback any uncommitted changes.
231: *
232: * IMPORTANT: we don't want to open a transaction and force the use of a
233: * jdbc connection just to close the session and do a rollback, so this
234: * method must be sensitive about how the release is triggered.
235: *
236: * In particular we don't want to use our custom getSession() method which
237: * automatically begins a transaction. Instead we get a Session and check
238: * if there is already an active transaction that needs to be rolled back.
239: * If not then we can close the Session without ever getting a jdbc
240: * connection, which is important for scalability.
241: */
242: public void release() {
243:
244: try {
245: Session session = sessionFactory.getCurrentSession();
246:
247: if (session != null && session.isOpen()) {
248:
249: log.debug("Closing Hibernate Session");
250:
251: try {
252: Transaction tx = session.getTransaction();
253:
254: if (tx != null && tx.isActive()) {
255: log
256: .debug("Forcing rollback on active transaction");
257: tx.rollback();
258: }
259: } catch (Throwable t) {
260: log.error("ERROR doing Hibernate rollback", t);
261: } finally {
262: if (session.isOpen()) {
263: session.close();
264: }
265: }
266: }
267: } catch (Throwable t) {
268: log.error("ERROR closing Hibernate Session", t);
269: }
270: }
271:
272: /**
273: * Retrieve object. We return null if the object is not found.
274: */
275: public PersistentObject load(String id, Class clazz)
276: throws RollerException {
277:
278: if (id == null || clazz == null) {
279: throw new RollerException(
280: "Cannot load objects when value is null");
281: }
282:
283: return (PersistentObject) getSession().get(clazz, id);
284: }
285:
286: /**
287: * Store object.
288: */
289: public void store(PersistentObject obj) throws HibernateException {
290:
291: if (obj == null) {
292: throw new HibernateException("Cannot save null object");
293: }
294:
295: Session session = getSession();
296:
297: // TODO BACKEND: this is wacky, we should double check logic here
298:
299: // TODO BACKEND: better to use session.saveOrUpdate() here, if possible
300: if (obj.getId() == null || obj.getId().trim().equals("")) {
301: // Object has never been written to database, so save it.
302: // This makes obj into a persistent instance.
303: session.save(obj);
304: }
305:
306: /*
307: * technically we shouldn't have any reason to support the saving
308: * of detached objects, so at some point we should re-evaluate this.
309: *
310: * objects should be re-attached before being saved again. it would
311: * be more appropriate to reject these kinds of saves because they are
312: * not really safe.
313: *
314: * NOTE: this may be coming from the way we use formbeans on the UI.
315: * we very commonly repopulate all data in a pojo (including id) from
316: * form data rather than properly loading the object from a Session
317: * then modifying its properties.
318: */
319: if (!session.contains(obj)) {
320:
321: log.debug("storing detached object: " + obj.toString());
322:
323: // Object has been written to database, but instance passed in
324: // is not a persistent instance, so must be loaded into session.
325: PersistentObject vo = (PersistentObject) session.load(obj
326: .getClass(), obj.getId());
327: vo.setData(obj);
328: obj = vo;
329: }
330:
331: }
332:
333: /**
334: * Remove object.
335: *
336: * TODO BACKEND: force the use of remove(Object) moving forward.
337: */
338: public void remove(String id, Class clazz)
339: throws HibernateException {
340:
341: if (id == null || clazz == null) {
342: throw new HibernateException(
343: "Cannot remove object when values are null");
344: }
345:
346: Session session = getSession();
347:
348: PersistentObject obj = (PersistentObject) session.load(clazz,
349: id);
350: session.delete(obj);
351: }
352:
353: /**
354: * Remove object.
355: */
356: public void remove(PersistentObject obj) throws HibernateException {
357:
358: if (obj == null) {
359: throw new HibernateException("Cannot remove null object");
360: }
361:
362: // TODO BACKEND: can hibernate take care of this check for us?
363: // what happens if object does not use id?
364: // can't remove transient objects
365: if (obj.getId() != null) {
366:
367: getSession().delete(obj);
368: }
369: }
370:
371: /**
372: * Store hierarchical object.
373: *
374: * NOTE: if the object has proper cascade setting then is all this necessary?
375: */
376: public void store(HierarchicalPersistentObject obj)
377: throws HibernateException, RollerException {
378:
379: if (obj == null) {
380: throw new HibernateException("Cannot save null object");
381: }
382:
383: log.debug("Storing hierarchical object " + obj);
384:
385: Session session = getSession();
386:
387: HierarchicalPersistentObject mNewParent = obj.getNewParent();
388: boolean fresh = (obj.getId() == null || "".equals(obj.getId()));
389:
390: if (fresh) {
391: // Object has never been written to database, so save it.
392: // This makes obj into a persistent instance.
393: session.save(obj);
394: }
395:
396: if (!session.contains(obj)) {
397:
398: // Object has been written to database, but instance passed in
399: // is not a persistent instance, so must be loaded into session.
400: HierarchicalPersistentObject vo = (HierarchicalPersistentObject) session
401: .load(obj.getClass(), obj.getId());
402: vo.setData(obj);
403: obj = vo;
404: }
405:
406: if (fresh) {
407: // Every fresh cat needs a parent assoc
408: Assoc parentAssoc = obj.createAssoc(obj, mNewParent,
409: Assoc.PARENT);
410: this .store(parentAssoc);
411: } else if (null != mNewParent) {
412: // New parent must be added to parentAssoc
413: Assoc parentAssoc = obj.getParentAssoc();
414: if (parentAssoc == null)
415: log.error("parent assoc is null");
416: parentAssoc.setAncestor(mNewParent);
417: this .store(parentAssoc);
418: }
419:
420: // Clear out existing grandparent associations
421: Iterator ancestors = obj.getAncestorAssocs().iterator();
422: while (ancestors.hasNext()) {
423: Assoc assoc = (Assoc) ancestors.next();
424: if (assoc.getRelation().equals(Assoc.GRANDPARENT)) {
425: this .remove(assoc);
426: }
427: }
428:
429: // Walk parent assocations, creating new grandparent associations
430: int count = 0;
431: Assoc currentAssoc = obj.getParentAssoc();
432: while (null != currentAssoc.getAncestor()) {
433: if (count > 0) {
434: Assoc assoc = obj.createAssoc(obj, currentAssoc
435: .getAncestor(), Assoc.GRANDPARENT);
436: this .store(assoc);
437: }
438: currentAssoc = currentAssoc.getAncestor().getParentAssoc();
439: count++;
440: }
441:
442: Iterator children = obj.getChildAssocs().iterator();
443: while (children.hasNext()) {
444: Assoc assoc = (Assoc) children.next();
445:
446: // resetting parent will cause reset of ancestors links
447: assoc.getObject().setParent(obj);
448:
449: // recursively...
450: this .store(assoc.getObject());
451: }
452:
453: // Clear new parent now that new parent has been saved
454: mNewParent = null;
455: }
456:
457: /**
458: * Store assoc.
459: */
460: public void store(Assoc assoc) throws HibernateException {
461:
462: if (assoc == null) {
463: throw new HibernateException("Cannot save null object");
464: }
465:
466: getSession().saveOrUpdate(assoc);
467: }
468:
469: /**
470: * Remove hierarchical object.
471: *
472: * NOTE: if the object has proper cascade setting then is all this necessary?
473: */
474: public void remove(HierarchicalPersistentObject obj)
475: throws RollerException {
476:
477: if (obj == null) {
478: throw new RollerException("Cannot remove null object");
479: }
480:
481: log.debug("Removing hierarchical object " + obj.getId());
482:
483: // loop to remove all descendents and associations
484: List toRemove = new LinkedList();
485: List assocs = obj.getAllDescendentAssocs();
486: for (int i = assocs.size() - 1; i >= 0; i--) {
487: Assoc assoc = (Assoc) assocs.get(i);
488: HierarchicalPersistentObject hpo = assoc.getObject();
489:
490: // remove my descendent's parent and grandparent associations
491: Iterator ancestors = hpo.getAncestorAssocs().iterator();
492: while (ancestors.hasNext()) {
493: Assoc dassoc = (Assoc) ancestors.next();
494: this .remove(dassoc);
495: }
496:
497: // remove decendent association and descendents
498: //assoc.remove();
499: toRemove.add(hpo);
500: }
501: Iterator removeIterator = toRemove.iterator();
502: while (removeIterator.hasNext()) {
503: PersistentObject po = (PersistentObject) removeIterator
504: .next();
505: getSession().delete(po);
506: }
507:
508: // loop to remove my own parent and grandparent associations
509: Iterator ancestors = obj.getAncestorAssocs().iterator();
510: while (ancestors.hasNext()) {
511: Assoc assoc = (Assoc) ancestors.next();
512: this .remove(assoc);
513: }
514:
515: getSession().delete(obj);
516: }
517:
518: /**
519: * Remove assoc.
520: */
521: public void remove(Assoc assoc) throws HibernateException {
522:
523: if (assoc == null) {
524: throw new HibernateException("Cannot save null object");
525: }
526:
527: getSession().delete(assoc);
528: }
529:
530: }
|