diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/HibernateDatabaseModificationsListener.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/HibernateDatabaseModificationsListener.java
new file mode 100644
index 000000000..fd60b7631
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/HibernateDatabaseModificationsListener.java
@@ -0,0 +1,127 @@
+/*
+ * This file is part of NavalPlan
+ *
+ * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
+ * Desenvolvemento Tecnolóxico de Galicia
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.navalplanner.business.hibernate.notification;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.event.PostDeleteEvent;
+import org.hibernate.event.PostDeleteEventListener;
+import org.hibernate.event.PostInsertEvent;
+import org.hibernate.event.PostInsertEventListener;
+import org.hibernate.event.PostUpdateEvent;
+import org.hibernate.event.PostUpdateEventListener;
+import org.hibernate.proxy.HibernateProxy;
+
+/**
+ * @author Óscar González Fernández
+ *
+ */
+public class HibernateDatabaseModificationsListener implements
+ PostInsertEventListener, PostUpdateEventListener,
+ PostDeleteEventListener, ISnapshotRefresherService {
+
+ private static final Log LOG = LogFactory
+ .getLog(HibernateDatabaseModificationsListener.class);
+
+ private final ExecutorService executor = Executors.newFixedThreadPool(3);
+
+ private final ConcurrentMap, BlockingQueue>> interested;
+
+ public HibernateDatabaseModificationsListener() {
+ interested = new ConcurrentHashMap, BlockingQueue>>();
+ }
+
+ @Override
+ public void onPostDelete(PostDeleteEvent event) {
+ modificationOn(inferEntityClass(getEntityObject(event)));
+ }
+
+ @Override
+ public void onPostUpdate(PostUpdateEvent event) {
+ modificationOn(inferEntityClass(getEntityObject(event)));
+ }
+
+ @Override
+ public void onPostInsert(PostInsertEvent event) {
+ modificationOn(inferEntityClass(getEntityObject(event)));
+ }
+
+ private Object getEntityObject(PostInsertEvent event) {
+ return event.getEntity();
+ }
+
+ private static Object getEntityObject(PostDeleteEvent event) {
+ return event.getEntity();
+ }
+
+ private static Object getEntityObject(PostUpdateEvent event) {
+ return event.getEntity();
+ }
+
+ private static Class> inferEntityClass(Object entity) {
+ if (entity instanceof HibernateProxy) {
+ HibernateProxy proxy = (HibernateProxy) entity;
+ return proxy.getHibernateLazyInitializer().getPersistentClass();
+ }
+ return entity.getClass();
+ }
+
+ private void modificationOn(Class> entityClass) {
+ LOG.debug("modification on " + entityClass);
+ BlockingQueue> queue = interested
+ .get(entityClass);
+ if (queue == null) {
+ LOG.debug("nobody interested on modification on: " + entityClass);
+ return;
+ }
+ LOG.debug("notifying modification on: " + entityClass + " to " + queue);
+ for (NotBlockingAutoUpdatedSnapshot> each : queue) {
+ each.reloadNeeded(executor);
+ }
+ }
+
+ @Override
+ public IAutoUpdatedSnapshot takeSnapshot(Callable callable, ReloadOn reloadOn) {
+ final NotBlockingAutoUpdatedSnapshot result;
+ result = new NotBlockingAutoUpdatedSnapshot(callable);
+ for (Class> each : reloadOn.getClassesOnWhichToReload()) {
+ interested.putIfAbsent(each, emptyQueue());
+ BlockingQueue> queue = interested
+ .get(each);
+ boolean success = queue.add(result);
+ assert success : "the type of queue used must not have restricted capacity";
+ }
+ result.ensureFirstLoad(executor);
+ return result;
+ }
+
+ private BlockingQueue> emptyQueue() {
+ return new LinkedBlockingQueue>();
+ }
+
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/IAutoUpdatedSnapshot.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/IAutoUpdatedSnapshot.java
new file mode 100644
index 000000000..beb001028
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/IAutoUpdatedSnapshot.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of NavalPlan
+ *
+ * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
+ * Desenvolvemento Tecnolóxico de Galicia
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.navalplanner.business.hibernate.notification;
+
+/**
+ * @author Óscar González Fernández
+ *
+ */
+public interface IAutoUpdatedSnapshot {
+
+ T getValue();
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ISnapshotRefresherService.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ISnapshotRefresherService.java
new file mode 100644
index 000000000..d54b1fd92
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ISnapshotRefresherService.java
@@ -0,0 +1,32 @@
+/*
+ * This file is part of NavalPlan
+ *
+ * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
+ * Desenvolvemento Tecnolóxico de Galicia
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.navalplanner.business.hibernate.notification;
+
+import java.util.concurrent.Callable;
+
+/**
+ * @author Óscar González Fernández
+ *
+ */
+public interface ISnapshotRefresherService {
+
+ public IAutoUpdatedSnapshot takeSnapshot(Callable callable, ReloadOn reloadOn);
+
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/NotBlockingAutoUpdatedSnapshot.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/NotBlockingAutoUpdatedSnapshot.java
new file mode 100644
index 000000000..2b82fb94b
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/NotBlockingAutoUpdatedSnapshot.java
@@ -0,0 +1,201 @@
+/*
+ * This file is part of NavalPlan
+ *
+ * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
+ * Desenvolvemento Tecnolóxico de Galicia
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.navalplanner.business.hibernate.notification;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.lang.Validate;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author Óscar González Fernández
+ *
+ */
+class NotBlockingAutoUpdatedSnapshot implements IAutoUpdatedSnapshot {
+
+ private static final Log LOG = LogFactory
+ .getLog(NotBlockingAutoUpdatedSnapshot.class);
+
+ private final Callable callable;
+
+ private final AtomicReference currentState;
+
+ private abstract class State {
+ abstract T getValue();
+
+ void cancel() {
+ }
+
+ State nextState(Future future) {
+ return new PreviousValueAndOngoingCalculation(this, future);
+ }
+
+ boolean hasBeenInitialized() {
+ return true;
+ }
+ }
+
+ private class NotLaunchState extends State {
+
+ @Override
+ T getValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ State nextState(Future future) {
+ return new FirstCalculation(future);
+ }
+
+ @Override
+ boolean hasBeenInitialized() {
+ return false;
+ }
+
+ }
+
+ private class NoOngoingCalculation extends State {
+ private final T value;
+
+ NoOngoingCalculation(T value) {
+ this.value = value;
+ }
+
+ @Override
+ T getValue() {
+ return value;
+ }
+ }
+
+ private class PreviousValueAndOngoingCalculation extends State {
+ private final State previousValue;
+
+ private final Future ongoingCalculation;
+
+ private PreviousValueAndOngoingCalculation(State value,
+ Future ongoingCalculation) {
+ Validate.notNull(value);
+ Validate.notNull(ongoingCalculation);
+ this.previousValue = value;
+ this.ongoingCalculation = ongoingCalculation;
+ }
+
+ @Override
+ T getValue() {
+ if (!ongoingCalculation.isCancelled()
+ && ongoingCalculation.isDone()) {
+ T newValue = getValueFromFuture();
+ currentState.compareAndSet(this, new NoOngoingCalculation(
+ newValue));
+ return newValue;
+ }
+ return previousValue.getValue();
+ }
+
+ private T getValueFromFuture() {
+ try {
+ return ongoingCalculation.get();
+ } catch (Exception e) {
+ LOG.error("error creating new snapshot, keeping old value",
+ e);
+ return previousValue.getValue();
+ }
+ }
+
+ @Override
+ void cancel() {
+ try {
+ ongoingCalculation.cancel(true);
+ } catch (Exception e) {
+ LOG.error("error cancelling future", e);
+ }
+ }
+ }
+
+ private class FirstCalculation extends State {
+ private final Future ongoingCalculation;
+
+ private FirstCalculation(Future ongoingCalculation) {
+ this.ongoingCalculation = ongoingCalculation;
+ }
+
+ @Override
+ T getValue() {
+ try {
+ return ongoingCalculation.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ void cancel() {
+ ongoingCalculation.cancel(true);
+ }
+
+ }
+
+ public NotBlockingAutoUpdatedSnapshot(Callable callable) {
+ Validate.notNull(callable);
+ this.callable = callable;
+ currentState = new AtomicReference(new NotLaunchState());
+ }
+
+ @Override
+ public T getValue() {
+ return currentState.get().getValue();
+ }
+
+ public void reloadNeeded(ExecutorService executorService) {
+ Future future = executorService.submit(callable);
+ State previousState;
+ State newState = null;
+ do {
+ if (newState != null) {
+ newState.cancel();
+ }
+ previousState = currentState.get();
+ newState = previousState.nextState(future);
+ } while (!currentState.compareAndSet(previousState, newState));
+ previousState.cancel();
+ }
+
+ public void ensureFirstLoad(ExecutorService executorService) {
+ if (hasBeenInitialized()) {
+ return;
+ }
+ Future future = executorService.submit(callable);
+ State previous = currentState.get();
+ State newState = previous.nextState(future);
+ boolean compareAndSet = currentState.compareAndSet(previous, newState);
+ if (!compareAndSet) {
+ newState.cancel();
+ }
+ }
+
+ private boolean hasBeenInitialized() {
+ return currentState.get().hasBeenInitialized();
+ }
+
+}
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ReloadOn.java b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ReloadOn.java
new file mode 100644
index 000000000..d72b9a94b
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ReloadOn.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of NavalPlan
+ *
+ * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
+ * Desenvolvemento Tecnolóxico de Galicia
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package org.navalplanner.business.hibernate.notification;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Óscar González Fernández
+ *
+ */
+public class ReloadOn {
+
+ public static ReloadOn onChangeOf(Class>... klasses) {
+ return onChangeOf(Arrays.asList(klasses));
+ }
+
+ public static ReloadOn onChangeOf(Collection> klasses) {
+ return new ReloadOn(klasses);
+ }
+
+ private final List> classes;
+
+ private ReloadOn(Collection extends Class>> classes) {
+ this.classes = new ArrayList>(classes);
+ }
+
+ public List> getClassesOnWhichToReload() {
+ return classes;
+ }
+}
diff --git a/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml
index 8970e7655..fa0adce1e 100644
--- a/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml
+++ b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml
@@ -19,6 +19,8 @@
+
+
+
+
+