Index: topia-security/src/java/org/codelutin/topia/history/TopiaHistoryHelper.java diff -u /dev/null topia-security/src/java/org/codelutin/topia/history/TopiaHistoryHelper.java:1.1 --- /dev/null Tue Oct 17 13:50:44 2006 +++ topia-security/src/java/org/codelutin/topia/history/TopiaHistoryHelper.java Tue Oct 17 13:50:39 2006 @@ -0,0 +1,60 @@ +/* *##% + * Copyright (C) 2006 + * Code Lutin, Cédric Pineau, Benjamin Poussin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +/* * + * HistoryHelper.java + * + * Created: 16 oct. 06 19:45:01 + * + * @author poussin + * @version $Revision: 1.1 $ + * + * Last update: $Date: 2006/10/17 13:50:39 $ + * by : $Author: bpoussin $ + */ + +package org.codelutin.topia.history; + +import org.codelutin.topia.TopiaContext; +import org.codelutin.topia.framework.TopiaContextImplementor; + + +/** + * @author poussin + * + */ + +public class TopiaHistoryHelper { + + public static final String SERVICE_NAME = "history"; + + public static boolean isEnabled(TopiaContext context) { + TopiaContextImplementor tci = (TopiaContextImplementor)context; + boolean result = tci.serviceEnabled(SERVICE_NAME); + return result; + } + + public static TopiaHistoryService get(TopiaContext context) { + TopiaContextImplementor tci = (TopiaContextImplementor)context; + TopiaHistoryService result = (TopiaHistoryService)tci.getService(SERVICE_NAME); + return result; + } +} + + Index: topia-security/src/java/org/codelutin/topia/history/TopiaHistoryListener.java diff -u /dev/null topia-security/src/java/org/codelutin/topia/history/TopiaHistoryListener.java:1.1 --- /dev/null Tue Oct 17 13:50:44 2006 +++ topia-security/src/java/org/codelutin/topia/history/TopiaHistoryListener.java Tue Oct 17 13:50:39 2006 @@ -0,0 +1,149 @@ +/* *##% + * Copyright (C) 2006 + * Code Lutin, Cédric Pineau, Benjamin Poussin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +/* * + * HistoryListener.java + * + * Created: 13 oct. 06 21:31:23 + * + * @author poussin + * @version $Revision: 1.1 $ + * + * Last update: $Date: 2006/10/17 13:50:39 $ + * by : $Author: bpoussin $ + */ + +package org.codelutin.topia.history; + +import java.util.Collection; +import java.util.Date; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codelutin.topia.TopiaContext; +import org.codelutin.topia.TopiaException; +import org.codelutin.topia.TopiaServiceDAOHelper; +import org.codelutin.topia.event.TopiaEntityEvent; +import org.codelutin.topia.event.TopiaEntityListener; +import org.codelutin.topia.event.TopiaEntityLoadEvent; +import org.codelutin.topia.event.TopiaEntityLoadListener; +import org.codelutin.topia.history.entities.History; +import org.codelutin.topia.history.entities.HistoryDAO; +import org.codelutin.topia.persistence.TopiaEntity; +import org.codelutin.topia.security.util.TopiaSecurityUtil; + + +/** + * + * @author poussin + */ +public class TopiaHistoryListener implements TopiaEntityListener, + TopiaEntityLoadListener { + + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(TopiaHistoryListener.class); + + protected boolean historyCreate; + protected boolean historyDelete; + protected boolean historyUpdate; + protected boolean historyLoad; + protected TopiaContext context; + /** + * + */ + public TopiaHistoryListener(TopiaContext context, boolean historyCreate, + boolean historyDelete, boolean historyUpdate, boolean historyLoad) { + this.context = context; + this.historyCreate = historyCreate; + this.historyDelete = historyDelete; + this.historyUpdate = historyUpdate; + this.historyLoad = historyLoad; + } + + /* (non-Javadoc) + * @see org.codelutin.topia.event.TopiaEntityListener#entityCreated(org.codelutin.topia.event.TopiaEntityEvent) + */ + public void entityCreated(TopiaEntityEvent e) { + if (historyCreate) { + Collection entities = e.getTopiaEntities(); + addToHistory(entities, TopiaSecurityUtil.CREATE); + } + } + + /* (non-Javadoc) + * @see org.codelutin.topia.event.TopiaEntityListener#entityDeleted(org.codelutin.topia.event.TopiaEntityEvent) + */ + public void entityDeleted(TopiaEntityEvent e) { + if (historyDelete) { + Collection entities = e.getTopiaEntities(); + addToHistory(entities, TopiaSecurityUtil.DELETE); + } + } + + /* (non-Javadoc) + * @see org.codelutin.topia.event.TopiaEntityListener#entityUpdated(org.codelutin.topia.event.TopiaEntityEvent) + */ + public void entityUpdated(TopiaEntityEvent e) { + if (historyUpdate) { + Collection entities = e.getTopiaEntities(); + addToHistory(entities, TopiaSecurityUtil.UPDATE); + } + } + + /* (non-Javadoc) + * @see org.codelutin.topia.event.TopiaEntityLoadListener#entityLoaded(org.codelutin.topia.event.TopiaEntityLoadEvent) + */ + public void entityLoaded(TopiaEntityLoadEvent e) { + if (historyLoad) { + Collection entities = e.getTopiaEntities(); + addToHistory(entities, TopiaSecurityUtil.LOAD); + } + } + + protected void addToHistory(Collection entities, int action) { + if (entities.size() > 0) { + String user = TopiaSecurityUtil.getUserPrincipal(); + Date date = new Date(); + try { + TopiaContext tx = context.beginTransaction(); + HistoryDAO dao = TopiaServiceDAOHelper.getHistoryDAO(tx); + for (Object o : entities) { + if (o instanceof TopiaEntity) { + String target = ((TopiaEntity)o).getTopiaId(); + + History history = dao.create(); + history.setActionDate(date); + history.setUser(user); + history.setAction(action); + history.setTypeAndTarget(target); + } + } + tx.commitTransaction(); + tx.closeContext(); + } catch (TopiaException eee) { + if (log.isWarnEnabled()) { + log.warn("Can't add create action in history", eee); + } + } + } + } + +} + + Index: topia-security/src/java/org/codelutin/topia/history/TopiaHistoryService.java diff -u /dev/null topia-security/src/java/org/codelutin/topia/history/TopiaHistoryService.java:1.1 --- /dev/null Tue Oct 17 13:50:44 2006 +++ topia-security/src/java/org/codelutin/topia/history/TopiaHistoryService.java Tue Oct 17 13:50:39 2006 @@ -0,0 +1,81 @@ +/* *##% + * Copyright (C) 2006 + * Code Lutin, Cédric Pineau, Benjamin Poussin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +/* * + * TopiaHistoryService.java + * + * Created: 14 oct. 06 00:58:13 + * + * @author poussin + * @version $Revision: 1.1 $ + * + * Last update: $Date: 2006/10/17 13:50:39 $ + * by : $Author: bpoussin $ + */ + +package org.codelutin.topia.history; + +import java.io.Writer; +import java.util.Date; +import java.util.List; + +import org.codelutin.topia.framework.TopiaService; + + +/** + * @author poussin + */ +public interface TopiaHistoryService extends TopiaService { + + /** + * Supprime tout l'historique jusqu'a la date passé en parametre + * @param toDate la derniere date effacé inclue + */ + public void clear(Date toDate) throws Exception; + + /** + * Ne garde dans l'historique que les number dernier elements + * @param number le nombre d'element a conserver dans l'historique + */ + public void keep(int number) throws Exception; + + /** + * Permet de stocker l'historique dans un writer (par exemple un fichier) + * + * @param toDate l'historique est sauve jusqu'a cette date incluse + * @param out le flux sur lequel il faut ecrire l'historique + */ + public void store(Date toDate, Writer out) throws Exception; + + /** + * Search TopiaId targeted by action and user. Results are ordered by + * date last is oldest + * + * @param limit maximum number of result, -1 for all + * @param user TopiaId of user, can be null for all user + * @param type type of target search (fqn class) can be null for all object + * @param Action action as int that we want to search, can be an sum of + * action wanted LOAD + UPDATE + DELETE + CREATE for all action + * @return TopiaId of target object + */ + public List findLastAction(int limit, String user, String type, int ... action) throws Exception; + +} + + Index: topia-security/src/java/org/codelutin/topia/history/TopiaHistoryServiceImpl.java diff -u /dev/null topia-security/src/java/org/codelutin/topia/history/TopiaHistoryServiceImpl.java:1.1 --- /dev/null Tue Oct 17 13:50:44 2006 +++ topia-security/src/java/org/codelutin/topia/history/TopiaHistoryServiceImpl.java Tue Oct 17 13:50:39 2006 @@ -0,0 +1,313 @@ +/* *##% + * Copyright (C) 2006 + * Code Lutin, Cédric Pineau, Benjamin Poussin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + *##%*/ + +/* * + * HistoryServiceImpl.java + * + * Created: 14 oct. 06 00:54:35 + * + * @author poussin + * @version $Revision: 1.1 $ + * + * Last update: $Date: 2006/10/17 13:50:39 $ + * by : $Author: bpoussin $ + */ + +package org.codelutin.topia.history; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codelutin.topia.TopiaContext; +import org.codelutin.topia.TopiaException; +import org.codelutin.topia.framework.TopiaContextImplementor; +import org.codelutin.topia.history.entities.HistoryImpl; +import org.codelutin.topia.security.util.TopiaSecurityUtil; +import org.hibernate.Criteria; +import org.hibernate.FlushMode; +import org.hibernate.ScrollableResults; +import org.hibernate.criterion.Order; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; + + +/** + * Pour active l'historisation des actions utilisateur il faut mettre dans le + * fichier de propriete la cle suivante: + *
  • topia.history=org.codelutin.topia.history.TopiaHistoryServiceImpl + *

    + * puis par defaut toutes les actions sont historisées, mais on peut choisir + * dans desactiver certaines: + *

  • topia.history.create=[true|false] + *
  • topia.history.delete=[true|false] + *
  • topia.history.update=[true|false] + *
  • topia.history.load=[true|false] + *

    + * On peut demander a ce que l'historique soit nettoyer automatiquement avec: + *

  • topia.history.clean.frequency=1 + *
  • topia.history.clean.date=3 + *
  • topia.history.clean.number=1000 + *
  • topia.history.store.file=/path/to/file/history + * + * Si seul frequency est specifier alors l'history est nettoyer a cette + * frequence et pour le nombre de jour indiqué. Par exemple si on indique 1d + * alors tous les jours l'history est nettoyé pour ne garder qu'un jour. + *

    + * Si on souhaite garder plus d'un jour, mais nettoyer tout de meme tous les + * jours, il faut utiliser l'option topia.history.clean.date qui donne le + * nombre de jour a conserver. + *

    + * Si on ne veut pas conserver un nombre de jour, mais un nombre + * d'enregistrement il faut utiliser a la place topia.history.clean.number + *

    + * Si l'on souhaite sauver l'history avant de le nettoyer il faut utiliser + * l'option topia.history.store.file. Le fichier indiqué est utilisé pour + * ajouter l'history. Si le fichier existe, les nouveaux history lui sont + * ajouter, sinon le fichier est créé. Sous Linux on peut utiliser logrotate + * sur ce fichier. + * + * @author poussin + */ +public class TopiaHistoryServiceImpl implements TopiaHistoryService, Runnable { + + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(TopiaHistoryServiceImpl.class); + + static final private int FIELD_DATE = 0; + static final private int FIELD_USER = 1; + static final private int FIELD_ACTION = 2; + static final private int FIELD_TYPE = 3; + static final private int FIELD_TARGET = 4; + + static private final String HISTORY_CREATE_KEY = "topia.history.create"; + static private final String HISTORY_DELETE_KEY = "topia.history.delete"; + static private final String HISTORY_UPDATE_KEY = "topia.history.update"; + static private final String HISTORY_LOAD_KEY = "topia.history.load"; + + static private final String HISTORY_CLEAN_FREQUENCY_KEY = "topia.history.clean.frequency"; + static private final String HISTORY_CLEAN_DATE_KEY = "topia.history.clean.date"; + static private final String HISTORY_CLEAN_NUMBER_KEY = "topia.history.clean.number"; + static private final String HISTORY_STORE_FILE_KEY = "topia.history.store.file"; + + + protected TopiaHistoryListener historyListener = null; + protected TopiaContextImplementor context = null; + + /** en jour */ + protected float cleanFrequency = -1; + /** nombre de jour a garder */ + protected int cleanDate = -1; + /** nombre d'history a garder */ + protected int cleanNumber = -1; + /** fichier on conserver les histories */ + protected File storeFile = null; + + /** Scheduler de nettoyage */ + protected ScheduledThreadPoolExecutor task = null; + + /* (non-Javadoc) + * @see org.codelutin.topia.framework.TopiaService#getServiceName() + */ + public String getServiceName() { + return TopiaHistoryHelper.SERVICE_NAME; + } + + /* (non-Javadoc) + * @see org.codelutin.topia.framework.TopiaService#getPersistenceClasses() + */ + public String getPersistenceClasses() { + return "org.codelutin.topia.history.entities.HistoryImpl"; + } + + /* (non-Javadoc) + * @see org.codelutin.topia.framework.TopiaService#init(org.codelutin.topia.framework.TopiaContextImplementor) + */ + public void init(TopiaContextImplementor context) { + this.context = context; + + Properties config = context.getConfig(); + + boolean historyCreate = "true".equalsIgnoreCase(config.getProperty(HISTORY_CREATE_KEY, "true")); + boolean historyDelete = "true".equalsIgnoreCase(config.getProperty(HISTORY_DELETE_KEY, "true")); + boolean historyUpdate = "true".equalsIgnoreCase(config.getProperty(HISTORY_UPDATE_KEY, "true")); + boolean historyLoad = "true".equalsIgnoreCase(config.getProperty(HISTORY_LOAD_KEY, "true")); + + historyListener = new TopiaHistoryListener(context, + historyCreate, historyDelete, historyUpdate, historyLoad); + + cleanFrequency = Float.parseFloat(config.getProperty(HISTORY_CLEAN_FREQUENCY_KEY, ""+cleanFrequency)); + cleanDate = Integer.parseInt(config.getProperty(HISTORY_CLEAN_DATE_KEY, ""+cleanDate)); + cleanNumber = Integer.parseInt(config.getProperty(HISTORY_CLEAN_NUMBER_KEY, ""+cleanNumber)); + + String storeFilename = config.getProperty(HISTORY_STORE_FILE_KEY); + if (storeFilename != null) { + storeFile = new File(storeFilename); + } + + if (cleanFrequency > 0) { + task = new ScheduledThreadPoolExecutor(1); + // on fait le nettoyage a minuit + Calendar cal = Calendar.getInstance(); + cal.setLenient(true); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.DAY_OF_YEAR, cal.get(Calendar.DAY_OF_YEAR) + 1); + long initialDelay = cal.getTimeInMillis() - System.currentTimeMillis(); + // ensuite on fait ca a interval regulier + long period = (long)cleanFrequency * 24 * 3600 * 1000; + task.scheduleAtFixedRate(this, initialDelay, period, TimeUnit.MILLISECONDS); + } + } + + /* (non-Javadoc) + * @see org.codelutin.topia.security.entities.authorization.History#clear(java.util.Date) + */ + public void clear(Date toDate) throws Exception { + TopiaContext tx = context.beginTransaction(); + tx.find("delete from " + HistoryImpl.class.getName() + " where actionDate <= ?", toDate); + tx.commitTransaction(); + tx.closeContext(); + } + + /* (non-Javadoc) + * @see org.codelutin.topia.security.entities.authorization.History#keep(int) + */ + public void keep(int number) throws Exception { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see org.codelutin.topia.security.entities.authorization.History#store(java.util.Date, java.io.Writer) + */ + public void store(Date toDate, Writer out) throws Exception { + if (out != null) { + out = new BufferedWriter(out); + TopiaContextImplementor tx = (TopiaContextImplementor)context.beginTransaction(); + ScrollableResults histories = tx.getHibernate().createCriteria(HistoryImpl.class) + .add( Restrictions.ge("actionDate", toDate)) + .addOrder( Order.asc("actionDate") ) + .setFlushMode(FlushMode.AUTO) + .scroll(); + while (histories.next()) { + Date date = histories.getDate(FIELD_DATE); + String user = histories.getString(FIELD_USER); + String action = TopiaSecurityUtil.actionsInt2String(histories.getInteger(FIELD_ACTION)); + String type = histories.getString(FIELD_TYPE); + String target = histories.getString(FIELD_TARGET); + + String log = date + "," + user + "," + action + "," + type + "," + target + "\n"; + out.write(log); + } + out.flush(); + tx.closeContext(); + } + } + + /* (non-Javadoc) + * @see org.codelutin.topia.TopiaHistoryService#findLastAction(java.lang.String, int, int) + */ + public List findLastAction(int limit, String user, String type, int ... actions) throws Exception { + TopiaContextImplementor tx = (TopiaContextImplementor)context.beginTransaction(); + + Criteria criteria = tx.getHibernate() + .createCriteria(HistoryImpl.class) + .setFlushMode(FlushMode.AUTO) + .addOrder( Order.desc("actionDate") ); + + criteria.add( Restrictions.in("action", Arrays.asList(actions))); + + if (user != null) { + criteria.add( Restrictions.eq("user", user)); + } + if (type != null) { + criteria.add( Restrictions.in("type", new String[]{type, type + "Impl"})); + } + + + List result = new ArrayList(); + + ScrollableResults histories = criteria.scroll(); + for (int i=limit; i == 0 && histories.next(); i--) { + result.add(histories.getString(FIELD_TARGET)); + } + + tx.closeContext(); + return result; + } + + /** + * Fait le menage de l'history + * @see java.lang.Runnable#run() + */ + public void run() { + log.info("Start history schuduling"); + try { + Writer out = null; + if (storeFile != null) { + storeFile.getParentFile().mkdirs(); + out = new FileWriter(storeFile, true); + } + Date date = null; + if (cleanNumber >= 0) { + // on recupere la date du cleanNumber + 1 + TopiaContextImplementor tx = (TopiaContextImplementor)context.beginTransaction(); + ScrollableResults histories = tx.getHibernate().createCriteria(HistoryImpl.class) + .setProjection(Property.forName("actionDate")) + .addOrder( Order.desc("actionDate") ) + .setMaxResults(cleanNumber + 1) + .setFlushMode(FlushMode.AUTO) + .scroll(); + histories.last(); + date = histories.getDate(0); + tx.closeContext(); + } else { + if (cleanDate < 0) { + cleanDate = (int)cleanFrequency; + } + Calendar cal = Calendar.getInstance(); + cal.setLenient(true); + int day = cal.get(Calendar.DAY_OF_YEAR); + cal.set(Calendar.DAY_OF_YEAR, day - cleanDate); + date = cal.getTime(); + } + store(date, out); + clear(date); + } catch (Exception eee) { + if (log.isWarnEnabled()) { + log.warn("Can't clean history", eee); + } + } + log.info("End history schuduling"); + } + +} + +