From f6457e00fca5995852761d1ec6333403535b008e Mon Sep 17 00:00:00 2001 From: Manuel Rego Casasnovas Date: Wed, 4 Apr 2012 12:19:34 +0200 Subject: [PATCH] Add a thread to perform SumChargedEffort recalculations FEA: ItEr76S14ConcurrencyProblemWorkReports --- .../ISumChargedEffortRecalculator.java | 44 ++++++ .../SumChargedEffortRecalculator.java | 140 ++++++++++++++++++ .../web/planner/order/SaveCommandBuilder.java | 6 +- 3 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java create mode 100644 libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java new file mode 100644 index 000000000..7db66e04c --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/ISumChargedEffortRecalculator.java @@ -0,0 +1,44 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * 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.libreplan.business.orders.entities; + +import java.util.concurrent.BlockingQueue; + +/** + * Interface to recalculate {@link SumChargedEffort} for an {@link Order}.
+ * + * This is needed to be called when some elements are moved in the {@link Order} + * . + * + * @author Manuel Rego Casasnovas + */ +public interface ISumChargedEffortRecalculator { + + /** + * Mark {@link Order} to recalculate {@link SumChargedEffort}.
+ * + * It adds the orderId to a {@link BlockingQueue} that will be + * read by a thread in charge of perform the recalculations. + * + * @param orderId + */ + void recalculate(Long orderId); + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java new file mode 100644 index 000000000..663ccfff1 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/SumChargedEffortRecalculator.java @@ -0,0 +1,140 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2012 Igalia, S.L. + * + * 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.libreplan.business.orders.entities; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.orders.daos.ISumChargedEffortDAO; +import org.libreplan.business.workreports.entities.WorkReport; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.stereotype.Component; + +/** + * Class to recalculate {@link SumChargedEffort} for an {@link Order}.
+ * + * This is needed to be called when some elements are moved in the {@link Order} + * .
+ * + * This class uses a thread, in order to call one by one all the requests + * received. Moreover, if there's any concurrency issue (because of some reports + * were saving in the meanwhile) the recalculation is repeated again (with + * MAX_ATTEMPS_BECAUSE_CONCURRENCY as maximum) till it's performed + * without concurrency problems. + * + * @author Manuel Rego Casasnovas + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class SumChargedEffortRecalculator implements + ISumChargedEffortRecalculator { + + private static final Log LOG = LogFactory + .getLog(SumChargedEffortRecalculator.class); + + /** + * Number of times that an order is tried to be recalculated if there is any + * concurrency issue.
+ * + * Concurrency problems could happen because while the recalculation is + * being done a {@link WorkReport} is saved with elements in the same + * {@link Order}. + */ + protected static final int MAX_ATTEMPS_BECAUSE_CONCURRENCY = 100; + + @Autowired + private ISumChargedEffortDAO sumChargedEffortDAO; + + /** + * Queue to store the id of the {@link Order} to be recalculated. + */ + private BlockingQueue queue = new ArrayBlockingQueue(1); + + /** + * The constructor launch the thread, that will be waiting for elements in + * the queue in order to perform the recalculations.
+ * + * The class is instantiated by Spring and it is a singleton, so you don't + * need to worry about calling this constructor. This will be done by Spring + * while launching the application. + */ + public SumChargedEffortRecalculator() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(new Runnable() { + + @Override + public void run() { + while (true) { + try { + LOG.info("Waiting for orders to recalculate from queue"); + Long orderId = queue.take(); + recalculateSumChargedEfforts(orderId); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private void recalculateSumChargedEfforts(Long orderId) + throws InterruptedException { + recalculateSumChargedEfforts(orderId, 0); + } + + private void recalculateSumChargedEfforts(Long orderId, int counter) + throws InterruptedException { + if (counter > MAX_ATTEMPS_BECAUSE_CONCURRENCY) { + LOG.error("Impossible to recalculate order (id=" + orderId + + ") due to concurrency problems"); + return; + } + + try { + LOG.info("Recalculate order (id=" + orderId + ")"); + sumChargedEffortDAO.recalculateSumChargedEfforts(orderId); + } catch (OptimisticLockingFailureException e) { + // Wait 1 second and try again + LOG.info("Concurrency problem recalculating order (id=" + + orderId + ") trying again in 1 second (attempt " + + counter + ")"); + Thread.sleep(1000); + recalculateSumChargedEfforts(orderId, counter++); + } + } + }); + } + + @Override + public void recalculate(Long orderId) { + try { + queue.put(orderId); + LOG.info("Add order (id=" + orderId + ") to recalculate in queue"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java index 1873c7635..fb720f09f 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/order/SaveCommandBuilder.java @@ -51,8 +51,8 @@ import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.orders.daos.IOrderDAO; import org.libreplan.business.orders.daos.IOrderElementDAO; -import org.libreplan.business.orders.daos.ISumChargedEffortDAO; import org.libreplan.business.orders.entities.HoursGroup; +import org.libreplan.business.orders.entities.ISumChargedEffortRecalculator; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderLineGroup; @@ -205,7 +205,7 @@ public class SaveCommandBuilder { private IDependencyDAO dependencyDAO; @Autowired - private ISumChargedEffortDAO sumChargedEffortDAO; + private ISumChargedEffortRecalculator sumChargedEffortRecalculator; private class SaveCommand implements ISaveCommand { @@ -291,7 +291,7 @@ public class SaveCommandBuilder { if (state.getOrder() .isNeededToRecalculateSumChargedEfforts()) { - sumChargedEffortDAO.recalculateSumChargedEfforts(state + sumChargedEffortRecalculator.recalculate(state .getOrder().getId()); }