From 35bf6c76b271f0cd972837501154cc76670014c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Gonz=C3=A1lez=20Fern=C3=A1ndez?= Date: Thu, 9 Sep 2010 01:14:29 +0200 Subject: [PATCH] Create mechanism for autoupdating snapshots of data FEA: ItEr60S04ValidacionEProbasFuncionaisItEr59S04 --- ...ibernateDatabaseModificationsListener.java | 127 +++++++++++ .../notification/IAutoUpdatedSnapshot.java | 29 +++ .../ISnapshotRefresherService.java | 32 +++ .../NotBlockingAutoUpdatedSnapshot.java | 201 ++++++++++++++++++ .../hibernate/notification/ReloadOn.java | 50 +++++ .../navalplanner-business-spring-config.xml | 15 ++ 6 files changed, 454 insertions(+) create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/HibernateDatabaseModificationsListener.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/IAutoUpdatedSnapshot.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ISnapshotRefresherService.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/NotBlockingAutoUpdatedSnapshot.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/hibernate/notification/ReloadOn.java 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> 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 @@ + + + + + + + + + + + + + + +