001: //
002: // Informa -- RSS Library for Java
003: // Copyright (c) 2002 by Niko Schmuck
004: //
005: // Niko Schmuck
006: // http://sourceforge.net/projects/informa
007: // mailto:niko_schmuck@users.sourceforge.net
008: //
009: // This library is free software.
010: //
011: // You may redistribute it and/or modify it under the terms of the GNU
012: // Lesser General Public License as published by the Free Software Foundation.
013: //
014: // Version 2.1 of the license should be included with this distribution in
015: // the file LICENSE. If the license is not included with this distribution,
016: // you may find a copy at the FSF web site at 'www.gnu.org' or 'www.fsf.org',
017: // or you may write to the Free Software Foundation, 675 Mass Ave, Cambridge,
018: // MA 02139 USA.
019: //
020: // This library is distributed in the hope that it will be useful,
021: // but WITHOUT ANY WARRANTY; without even the implied waranty of
022: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
023: // Lesser General Public License for more details.
024: //
025:
026: // $Id: ChannelBuilder.java,v 1.29 2006/12/04 23:43:27 italobb Exp $
027:
028: package de.nava.informa.impl.hibernate;
029:
030: import java.net.URL;
031: import java.util.Date;
032: import java.util.Properties;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.hibernate.HibernateException;
037: import org.hibernate.Query;
038: import org.hibernate.Session;
039: import org.hibernate.Transaction;
040: import org.jdom.Element;
041:
042: import de.nava.informa.core.CategoryIF;
043: import de.nava.informa.core.ChannelBuilderException;
044: import de.nava.informa.core.ChannelBuilderIF;
045: import de.nava.informa.core.ChannelGroupIF;
046: import de.nava.informa.core.ChannelIF;
047: import de.nava.informa.core.CloudIF;
048: import de.nava.informa.core.ImageIF;
049: import de.nava.informa.core.ItemEnclosureIF;
050: import de.nava.informa.core.ItemGuidIF;
051: import de.nava.informa.core.ItemIF;
052: import de.nava.informa.core.ItemSourceIF;
053: import de.nava.informa.core.TextInputIF;
054:
055: /**
056: * Factory for the creation of the channel object model with the hibernate
057: * persistent store.
058: * <p/>
059: * NOT THREAD SAFE
060: * <p/>
061: * Hibernate Multi-threading notes:
062: * ChannelBuilder has some subtleties as it relates to threading. The specifics
063: * of the way it is supported still need to be proven. Certainly the error
064: * handling here and in UpdateChannelTask and in ChannelRegistry is incomplete.
065: * It seems to work, but I would consider it incomplete still.
066: * <p/>
067: * The key facts are
068: * (1) Sessions are not thread safe and
069: * (2) Sessions should have relatively short lifespans.
070: * <p/>
071: * To support this, there is a mode of using
072: * ChannelBuilder where it holds on to a SessionHandler and manages the creation
073: * and destruction of Sessions on behalf of the caller. When you supply a
074: * SessionHandler to ChannelBuilder, you may use the beginTransaction() and
075: * endTransaction() calls to take all the steps needed before and after a
076: * transaction. At the end of endTransaction() the transaction will be closed
077: * and the session will be flushed and closed. To use this mode, you should
078: * (1) Create a SessionHandler ,
079: * (2) Create a JDBC Connection to the database,
080: * (3) sessionHandler.setConnection(connection), and
081: * (4) use new ChannelBuilder(sessionHandler).
082: *
083: * @author Niko Schmuck (niko@nava.de)
084: */
085: public class ChannelBuilder implements ChannelBuilderIF {
086:
087: private static Log logger = LogFactory.getLog(ChannelBuilder.class);
088:
089: private Session session;
090:
091: private SessionHandler handler;
092:
093: private Transaction transaction;
094:
095: /**
096: * ChannelBuilder constructor. Caller is responsible for managing sessions and
097: * transactions.
098: */
099: public ChannelBuilder(Session session) {
100: logger.info("New Channel Builder for: " + session);
101: this .session = session;
102: this .handler = null;
103: }
104:
105: /**
106: * ChannelBuilder constructor. ChannelBuilder will manage sessions and
107: * transactions. Supplied SessionHandler needs to have a live JDBC connection
108: * available.
109: */
110: public ChannelBuilder(SessionHandler handler) {
111: logger.debug("New Channel Builder for: " + handler);
112: this .handler = handler;
113: this .session = null;
114: }
115:
116: // --------------------------------------------------------------
117: // Hibernate Specific Methods
118: // --------------------------------------------------------------
119:
120: /**
121: * Processing needed at the start of a transaction. - creating a session -
122: * beginning the transaction
123: */
124: public void beginTransaction() throws ChannelBuilderException {
125: logger.info("beginTransaction");
126: if (session != null || handler == null)
127: throw new IllegalStateException(
128: "Session != null || handler == null");
129: try {
130: session = handler.getSession();
131: transaction = session.beginTransaction();
132: } catch (HibernateException e) {
133: e.printStackTrace();
134: transaction = null;
135: throw new ChannelBuilderException(e);
136: }
137: }
138:
139: /**
140: * Processing needed at the end of a transaction. - commit the transaction -
141: * flush the session - close the session TODO: catch the exception so this
142: * method doesn't have any throws.
143: */
144: public void endTransaction() throws ChannelBuilderException {
145: logger.info("endTransaction");
146: if (handler == null || transaction == null || session == null)
147: throw new IllegalStateException(
148: "handler == null || transaction == null || session == null");
149: try {
150: transaction.commit();
151: session.flush();
152: session.close();
153: session = null;
154: transaction = null;
155:
156: } catch (HibernateException he) {
157: if (transaction != null)
158: try {
159: he.printStackTrace();
160: transaction.rollback();
161: transaction = null;
162: if (session.isOpen()) {
163: session.close();
164: session = null;
165: }
166: } catch (HibernateException e) {
167: if (session.isOpen()) {
168: session = null;
169: }
170: e.printStackTrace();
171: throw new ChannelBuilderException(e);
172: }
173: throw new ChannelBuilderException(he);
174: }
175: }
176:
177: /**
178: * Check if we are already in the middle of a transaction. This is needed
179: * because as of now begin/endTransactions cannot be nested and in fact give
180: * assert errors if you try.
181: *
182: * @return - boolean indicating whether we are currently in a transaction.
183: */
184: public boolean inTransaction() {
185: return session != null && transaction != null;
186: }
187:
188: /**
189: * resetTransaction - Used during error handling. If in a catch block there is
190: * a potentially still open transaction (i.e. beginTransaction() was called)
191: * then call this method to reset the state of the ChannelBuilder and clean up
192: * the transaction.
193: *
194: */
195: public void resetTransaction() {
196: logger.debug("Transaction being reset.");
197: if (transaction != null) {
198: try {
199: transaction.commit();
200: transaction = null;
201: } catch (HibernateException e) {
202: transaction = null;
203: e.printStackTrace();
204: }
205: }
206: if (session != null) {
207: try {
208: session.flush();
209: session.close();
210: session = null;
211: } catch (HibernateException e) {
212: e.printStackTrace();
213: session = null;
214: }
215: }
216: }
217:
218: /**
219: * Certain Hibernate calls require the session. Note that this call should
220: * only be made between a beginTransaction and endTransaction call which is
221: * why we throw an IllegalStateException otherwise.
222: */
223: public Session getSession() {
224: if (handler == null || session == null)
225: throw new IllegalStateException(
226: "getSession must be bracketed by begin/endTransaction");
227: if (!handler.isSessionOpen())
228: throw new IllegalStateException(
229: "Hibernate Handler must be open");
230: return session;
231: }
232:
233: /**
234: * update - Hibernate Update some object
235: *
236: * @param o
237: * @throws ChannelBuilderException -
238: */
239: public void update(Object o) throws ChannelBuilderException {
240: try {
241: session.update(o);
242: } catch (HibernateException e) {
243: e.printStackTrace();
244: throw new ChannelBuilderException("update() Failed");
245: }
246: }
247:
248: /**
249: * Hibernate Delete some object
250: *
251: * @param o -
252: * Object to Delete
253: * @throws ChannelBuilderException -
254: * Translation of Hibernate exception
255: */
256: public void delete(Object o) throws ChannelBuilderException {
257: try {
258: session.delete(o);
259: } catch (HibernateException e) {
260: e.printStackTrace();
261: throw new ChannelBuilderException("delete() Failed");
262: }
263: }
264:
265: // --------------------------------------------------------------
266: // implementation of ChannelBuilderIF interface
267: // --------------------------------------------------------------
268:
269: public void init(Properties props) throws ChannelBuilderException {
270: logger
271: .debug("initialising channel builder for hibernate backend");
272: }
273:
274: public ChannelGroupIF createChannelGroup(String title) {
275: ChannelGroupIF obj = new ChannelGroup(title);
276: save(obj);
277: return obj;
278: }
279:
280: public ChannelIF createChannel(String title) {
281: return createChannel((Element) null, title);
282: }
283:
284: public ChannelIF createChannel(Element channelElement, String title) {
285: return createChannel(channelElement, title, null);
286: }
287:
288: public ChannelIF createChannel(String title, String location) {
289: return createChannel(null, title, location);
290: }
291:
292: /**
293: * May throw runtime HibernateException
294: */
295: public ChannelIF createChannel(Element channelElement,
296: String title, String location) {
297: ChannelIF obj = null;
298: if (location != null) {
299: Query query = session
300: .createQuery("from Channel as channel where channel.locationString = ? ");
301: query.setString(0, location);
302: obj = (ChannelIF) query.uniqueResult();
303: }
304: if (obj == null) {
305: obj = new Channel(channelElement, title, location);
306: session.save(obj);
307: } else {
308: logger
309: .info("Found already existing channel instance with location "
310: + location);
311: }
312: return obj;
313: }
314:
315: public ItemIF createItem(ChannelIF channel, String title,
316: String description, URL link) {
317: return createItem(null, channel, title, description, link);
318: }
319:
320: public ItemIF createItem(Element itemElement, ChannelIF channel,
321: String title, String description, URL link) {
322: // according to RSS 2.0 spec link may be omitted, but need link
323: // for unique identifier. Add channel location for uniqueness?
324: //
325: if (link == null) {
326: throw new RuntimeException("link required for item "
327: + title + " for persistence uniqueness");
328: }
329:
330: Query query = session
331: .createQuery("from Item as item where item.linkString = ? ");
332: query.setString(0, link.toString());
333: ItemIF obj = (ItemIF) query.uniqueResult();
334: if (obj == null) {
335: obj = new Item(channel, title, description, link);
336: if (channel != null) {
337: channel.addItem(obj);
338: }
339: session.save(obj);
340: } else {
341: logger
342: .info("Found already existing item instance with location "
343: + link);
344: }
345: return obj;
346: }
347:
348: public ItemIF createItem(ChannelIF channel, ItemIF item) {
349: throw new RuntimeException("Not implemented yet.");
350: }
351:
352: public ImageIF createImage(String title, URL location, URL link) {
353: Query query = session
354: .createQuery("from Image as img where img.locationString = ? ");
355: query.setString(0, location.toString());
356: ImageIF obj = (Image) query.uniqueResult();
357: if (obj == null) {
358: obj = new Image(title, location, link);
359: session.save(obj);
360: }
361: return obj;
362: }
363:
364: public CloudIF createCloud(String domain, int port, String path,
365: String registerProcedure, String protocol) {
366: logger.info("ChannelBuilder is creating a Persistent Cloud");
367: // equality by domain, port, and path
368:
369: Query query = session
370: .createQuery("from Cloud as cld where cld.domain = ? and cld.port = ? and cld.path = ?");
371: query.setString(0, domain);
372: query.setInteger(1, port);
373: query.setString(2, path);
374: CloudIF obj = (CloudIF) query.uniqueResult();
375: if (obj == null) {
376: obj = new Cloud(domain, port, path, registerProcedure,
377: protocol);
378: session.save(obj);
379: }
380:
381: return obj;
382: }
383:
384: public TextInputIF createTextInput(String title,
385: String description, String name, URL link) {
386: Query query = session
387: .createQuery("from TextInput as txt where txt.title = ? and txt.name = ? and txt.linkString = ? ");
388: query.setString(0, title);
389: query.setString(1, name);
390: query.setString(2, link.toString());
391: TextInputIF obj = (TextInput) query.uniqueResult();
392: if (obj == null) {
393: obj = new TextInput(title, description, name, link);
394: session.save(obj);
395: }
396: return obj;
397: }
398:
399: public ItemSourceIF createItemSource(ItemIF item, String name,
400: String location, Date timestamp) {
401: // TODO Auto-generated method stub
402: return null;
403: }
404:
405: public ItemSourceIF createItemSource(String name, String location,
406: Date timestamp) {
407:
408: Query query = session
409: .createQuery("from ItemSource as src where src.name = ? and src.location = ? and src.timestamp = ? ");
410: query.setString(0, name);
411: query.setString(1, location);
412: query.setTimestamp(2, timestamp);
413: ItemSourceIF obj = (ItemSourceIF) query.uniqueResult();
414: if (obj == null) {
415: obj = new ItemSource(null, name, location, timestamp);
416: session.save(obj);
417: }
418: return obj;
419: }
420:
421: public ItemEnclosureIF createItemEnclosure(ItemIF item,
422: URL location, String type, int length) {
423: Query query = session
424: .createQuery("from ItemEnclosure as enc where enc.item.id = ? ");
425: query.setLong(0, item.getId());
426: ItemEnclosureIF obj = (ItemEnclosureIF) query.uniqueResult();
427: if (obj == null) {
428: obj = new ItemEnclosure(item, location, type, length);
429: session.save(obj);
430: }
431: return obj;
432: }
433:
434: public ItemGuidIF createItemGuid(ItemIF item, String location,
435: boolean permaLink) {
436: Query query = session
437: .createQuery("from ItemGuid as guid where guid.location = ? ");
438: query.setString(0, location);
439: ItemGuidIF guid = (ItemGuidIF) query.uniqueResult();
440: if (guid == null) {
441: guid = new ItemGuid(item, location, permaLink);
442: guid.setPermaLink(permaLink);
443: session.save(guid);
444: }
445: return guid;
446: }
447:
448: public CategoryIF createCategory(CategoryIF parent, String title) {
449: return createCategory(parent, title, null);
450: }
451:
452: public CategoryIF createCategory(CategoryIF parent, String title,
453: String domain) {
454: Query query = session
455: .createQuery("from Category as cat where cat.title = ? and cat.domain = ? ");
456: query.setString(0, title);
457: query.setString(1, domain);
458: CategoryIF cat = (CategoryIF) query.uniqueResult();
459: if (cat == null) {
460: cat = new Category(title);
461: cat.setDomain(domain);
462: if (parent != null) {
463: parent.addChild(cat);
464: }
465: session.save(cat);
466: }
467: return cat;
468: }
469:
470: public void close() throws ChannelBuilderException {
471: logger.debug("closing channel builder for hibernate backend");
472: }
473:
474: /**
475: * Reloads group for use in new session.
476: *
477: * @param group
478: * to reload.
479: *
480: * @return reloaded group for chaning.
481: *
482: * @throws ChannelBuilderException
483: * when unable to reload data.
484: */
485: public ChannelGroup reload(ChannelGroup group)
486: throws ChannelBuilderException {
487: try {
488: getSession().load(group, new Long(group.getId()));
489: } catch (HibernateException e) {
490: throw new ChannelBuilderException(
491: "Unable to reload group: " + e.getMessage());
492: }
493:
494: return group;
495: }
496:
497: /**
498: * Reloads channel for use in new session.
499: *
500: * @param channel
501: * channel to reload.
502: *
503: * @return reloaded channel for chaining.
504: *
505: * @throws ChannelBuilderException
506: * when unable to reload data.
507: */
508: public Channel reload(Channel channel)
509: throws ChannelBuilderException {
510: try {
511: getSession().load(channel, new Long(channel.getId()));
512: } catch (HibernateException e) {
513: throw new ChannelBuilderException(
514: "Unable to reload channel: " + e.getMessage());
515: }
516:
517: return channel;
518: }
519:
520: /**
521: * Reloads item for use in new session.
522: *
523: * @param item
524: * item to reload.
525: *
526: * @return reloaded item for chaning.
527: *
528: * @throws ChannelBuilderException
529: * when unable to reload data.
530: */
531: public Item reload(Item item) throws ChannelBuilderException {
532: try {
533: getSession().load(item, new Long(item.getId()));
534: } catch (HibernateException e) {
535: throw new ChannelBuilderException("Unable to reload item: "
536: + e.getMessage());
537: }
538:
539: return item;
540: }
541:
542: // -------------------------------------------------------------
543: // internal helper methods
544: // -------------------------------------------------------------
545:
546: protected void save(Object dataObject) {
547: if (session == null)
548: throw new IllegalStateException("Session == null");
549: try {
550: session.save(dataObject);
551: } catch (HibernateException he) {
552: throw new RuntimeException(he.getMessage());
553: }
554: }
555:
556: }
|