/* * codjo.net * * Common Apache License 2.0 */ package net.codjo.persistent.sql; // Persistance import net.codjo.persistent.PersistenceException; import net.codjo.persistent.Persistent; import net.codjo.persistent.Reference; import net.codjo.utils.QueryHelper; import net.codjo.utils.SQLFieldList; import net.codjo.utils.sql.event.DbChangeEvent; import net.codjo.utils.sql.event.DbChangeListener; import org.apache.log4j.Logger; import java.beans.IntrospectionException; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Iterator; import java.util.Map; import java.util.ResourceBundle; /** * Classe Home generique par Introspection. Cette classe administre un ensemble d'objet. * * <p> * Une instance de SimpleHome est configure grace a un fichier. Ce fichier est decompose * en 5 partie :<br> * L'objet gere doit : * * <ul> * <li> * Home sp�cific : * </li> * <li> * Object - D�finition : * </li> * <li> * Primary Key - D�finition : * </li> * <li> * Correspondance colonne et property : * </li> * <li> * Traducteurs : * </li> * </ul> * * L'objet administr� doit avoir un seul constructeur avec une <code>Reference </code> * comme premier argument. * </p> * * @author $Author: blazart $ * @version $Revision: 1.4 $ * * @see net.codjo.persistent.sql.SimpleHomeTranslator */ public abstract class SimpleHome extends AbstractHome { // Log private static final Logger APP = Logger.getLogger(SimpleHome.class); private DbChangeListener dbChangeListener; private ResourceBundle homeDef; // DB private String dbTableName; // Mapping property private SimpleHomeMapping property; private SimpleHomeMapping externalProperty; // Traducteur private SimpleHomeTranslator translator; // Objet private SimpleHomeFactory objectFactory; private SimpleHomePKFactory pkFactory; /** * Constructeur. * * @param con Connection du home * @param resb Le ressourceBundle contenant la definition du home * * @exception SQLException Erreur d'acces a la base * @throws RuntimeException si erreur de configuration */ protected SimpleHome(Connection con, ResourceBundle resb) throws SQLException { super(con); homeDef = resb; try { init(); } catch (SQLException ex) { throw ex; } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException("Erreur de configuration (" + dbTableName + ") :" + ex.getLocalizedMessage()); } } /** * Ajoute un ecouteur sur les modifications BD directe dont ce home est responsable.. * * @param l Le listener */ public void addDbChangeListener(DbChangeListener l) { dbChangeListener = l; } /** * Construction d'une reference a partir d'une table de hash. * * @param pk La map (Colonne / valeur) * * @return Une instance non null. */ protected Reference getReference(Map pk) { return getReference(pkFactory.fillConstructorVals(pk)); } /** * Charge un objet (sans les liens externe) a partir du <code>ResultSet * </code>donnee. L'objet est construit en appelant le constructeur par Reflection. * * @param rs Le ResultSet utilise pour la construction. * @param ref Reference sur l'instance a construire. * * @return Une instance non null. * * @exception SQLException Erreur d'acces a la base * @exception PersistenceException Erreur dans la couche de persistence. */ protected Persistent loadObjectInternalProperty(ResultSet rs, Reference ref) throws SQLException, PersistenceException { if (ref.isLoaded()) { return ref.getLoadedObject(); } try { Persistent obj = (Persistent)objectFactory.newInstance(rs, ref); obj.setStored(); obj.setSynchronized(true); return obj; } catch (Exception ex) { throw newPersistenceException("Chargement ligne", ex); } } /** * Charge un objet a partir du <code>ResultSet</code> donnee. L'objet est construit * en appelant le constructeur par Reflection. * * @param rs Le ResultSet utilise pour la construction. * @param ref Reference sur l'instance a construire. * * @return Une instance non null. * * @exception SQLException Erreur d'acces a la base * @exception PersistenceException Erreur dans la couche de persistence. */ protected Persistent loadObject(ResultSet rs, Reference ref) throws SQLException, PersistenceException { if (ref.isLoaded()) { return ref.getLoadedObject(); } try { Persistent obj = loadObjectInternalProperty(rs, ref); // Positionne les liens externe for (int i = 0; i < externalProperty.size(); i++) { Object v = translator.translateValue(externalProperty.getName(i), rs.getObject(externalProperty.getColumn(i))); externalProperty.setPropertyValue(i, obj, v); } return obj; } catch (Exception ex) { throw newPersistenceException("Chargement ligne", ex); } } /** * Methode utilitaire qui remplit la requete pour une insertion. L'objet reference * doit etre en memoire. De plus, cette methode cree un identifiant pour * l'enregistrement (si necessaire). * * @param ref La reference de l'objet a inserer. * * @exception SQLException Si il est impossible de recuperer un ID. */ protected void fillQueryHelperForInsert(Reference ref) throws SQLException { debug("Enregistre Reference : " + ref); if (ref.getId() == null) { buildId(ref); } Object obj = ref.getLoadedObject(); try { for (int i = 0; i < property.size(); i++) { Object value = property.getPropertyValue(i, obj); if (value != null && value instanceof Persistent) { value = ((Persistent)value).getId(); } debug(" " + property.getColumn(i) + "=" + value); queryHelper.setInsertValue(property.getColumn(i), value); } } catch (Exception ex) { throw newReflectionException("Enregistrement ligne", ex); } } /** * Construction d'une reference a partir d'un ResultSet. * * @param rs Le ResultSet a utilise pour la construction. * * @return Une instance non null. * * @exception SQLException En cas d'erreur d'acces a la base */ protected Reference loadReference(ResultSet rs) throws SQLException { return getReference(pkFactory.fillConstructorVals(rs)); } /** * Methode utilitaire qui remplit la clause where du QueryHelper. * * @param ref La reference utilisee pour remplir la clause. */ protected void fillQueryHelperSelector(Reference ref) { debug("Requete pour chargement : " + ref.toString()); try { pkFactory.fillSelectorValue(queryHelper, ref); } catch (Exception ex) { throw newReflectionException("Creation clause 'where'", ex); } } /** * Retourne une Reference construite avec un tableau d'arguments. * * @param v Le tableau des arguments utilise pour construire la pk. * * @return Une reference */ private Reference getReference(Object[] v) { try { return getReference(pkFactory.newInstance(v)); } catch (Exception ex) { throw newReflectionException("Load Reference", ex); } } /** * Construit un nouvel id (ou clef primaire) pour une Reference. * * @param ref La reference a remplir avec un nouvel id. */ private void buildId(Reference ref) { try { pkFactory.buildId(ref, queryHelper); } catch (Exception ex) { throw newReflectionException("Build ID", ex); } } /** * Initialisation du Home. * * @exception SQLException Erreur acces Base * @exception ClassNotFoundException Classe de l'objet ou PK introuvable * @exception NoSuchFieldException Erreur de configuration * @exception NoSuchMethodException Erreur de configuration * @exception IntrospectionException Erreur de configuration */ private void init() throws SQLException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IntrospectionException { dbTableName = homeDef.getString("home.dbTableName"); debug("Init Home : " + dbTableName); objectFactory = new SimpleHomeFactory(homeDef, "object."); pkFactory = new SimpleHomePKFactory(homeDef); property = new SimpleHomeMapping(homeDef, "property.", objectFactory.getObjectClass(), SimpleHomeMapping.INIT_GETTER); externalProperty = new SimpleHomeMapping(homeDef, "externalProperty.", objectFactory.getObjectClass(), SimpleHomeMapping.INIT_SETTER); translator = new SimpleHomeTranslator(homeDef, this); objectFactory.setPropertyMapping(property); objectFactory.setTranslator(translator); pkFactory.setPropertyMapping(property); pkFactory.setTranslator(translator); // Init du QueryHelper SQLFieldList tableFields = new SQLFieldList(dbTableName, getConnection()); SQLFieldList selectById = new SQLFieldList(); for (Iterator iter = pkFactory.columns(); iter.hasNext();) { String fieldName = (String)iter.next(); int sqlType = tableFields.getFieldType(fieldName); selectById.addField(fieldName, sqlType); } queryHelper = new QueryHelper(dbTableName, getConnection(), tableFields, selectById); } /** * Construit une <code>PersistenceException</code> lors d'une erreur dans la * mecanique de reflection. * * @param jobLabel Le nom de la tache lancant l'exception * @param ex L'exception lance * * @return la <code>PersistenceException</code> construite. */ private PersistenceException newPersistenceException(String jobLabel, Exception ex) { ex.printStackTrace(); if (ex instanceof PersistenceException) { return (PersistenceException)ex; } if (ex instanceof IllegalAccessException) { return new PersistenceException(ex, jobLabel + " : Access interdit"); } if (ex instanceof IllegalArgumentException) { return new PersistenceException(ex, jobLabel + " : Nb argument incorrecte"); } else if (ex instanceof InvocationTargetException) { InvocationTargetException invEx = (InvocationTargetException)ex; if (invEx.getTargetException() instanceof PersistenceException) { return (PersistenceException)invEx.getTargetException(); } else if (invEx.getTargetException() instanceof SQLException) { return new PersistenceException((SQLException)invEx.getTargetException()); } } return new PersistenceException(ex, jobLabel + " : Erreur inconnue "); } /** * Construit une <code>RuntimeException</code> lors d'une erreur dans la mecanique de * reflection. * * @param jobLabel Le nom de la tache lancant l'exception * @param ex L'exception lance * * @return la <code>RuntimeException</code> construite. */ private RuntimeException newReflectionException(String jobLabel, Exception ex) { ex.printStackTrace(); String exceptionString = ex.getLocalizedMessage(); if (exceptionString == null) { exceptionString = ex.getClass().toString(); } return new RuntimeException("Erreur d'introspection lors de \"" + jobLabel + "\" : " + exceptionString); } /** * Fait une trace * * @param msg message de la trace */ private void debug(String msg) { APP.debug("Home(" + dbTableName + ") " + msg); } /** * Classe offrant un comportement par defaut pour la mise a jours de ce Home, lors de * modification en directe de la BD. * * <p> * Lors d'un delete La reference est supprime du buffer. Lors d'un "Modify" la * reference est decharge. * </p> * * @author $Author: blazart $ * @version $Revision: 1.4 $ */ /** * DOCUMENT ME! * */ public class DefaultDbChangeListener implements DbChangeListener { /** * DOCUMENT ME! * * @param evt Description of Parameter */ public void succeededChange(DbChangeEvent evt) { if (dbChangeListener != null) { dbChangeListener.succeededChange(evt); } if (isBufferOn() == false) { return; } debug(evt.toString()); switch (evt.getEventType()) { case DbChangeEvent.DELETE_EVENT: Reference ref = getReference(evt.getPrimaryKey()); ref.unload(); removeReference(ref); break; case DbChangeEvent.MODIFY_EVENT: getReference(evt.getPrimaryKey()).unload(); break; } } } }