//
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4chee.conf.notif;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.dcm4che3.conf.core.DelegatingConfiguration;
import org.dcm4che3.conf.core.api.ConfigChangeEvent;
import org.dcm4che3.conf.core.api.ConfigChangeEvent.CONTEXT;
import org.dcm4che3.conf.core.api.Configuration;
import org.dcm4che3.conf.core.api.ConfigurationException;
import org.dcm4che3.conf.core.api.Path;
/**
* Decorator to add support for configuration change notifications to the configuration backend.
*
* @author Alexander Hoermandinger <alexander.hoermandinger@agfa.com>
*/
public class ConfigNotificationDecorator extends DelegatingConfiguration {
public static final String NOTIFICATIONS_ENABLED_PROPERTY = "org.dcm4che.conf.notifications";
private final Map<Integer,JtaTransactionConfigChangeContainer> transactionMap = new ConcurrentHashMap<>();
@Inject
private ConfigNotificationService configNotifService;
@Resource(lookup="java:jboss/TransactionManager")
private TransactionManager tmManager;
/**
* to avoid using CDI decorators
*/
public void setDelegate(Configuration delegate) {
this.delegate = delegate;
}
@Override
public void persistNode(Path path, Map<String, Object> configNode, Class configurableClass) throws ConfigurationException {
delegate.persistNode(path, configNode, configurableClass);
recordConfigChange(path);
}
@Override
public void removeNode(Path path) throws ConfigurationException {
delegate.removeNode(path);
recordConfigChange(path);
}
private void recordConfigChange(Path path) {
JtaTransactionConfigChangeContainer container = getEventForActiveTransaction();
if(container != null) {
container.addChangedPath(path.toSimpleEscapedPath());
} else {
if (Boolean.valueOf(System.getProperty(NOTIFICATIONS_ENABLED_PROPERTY, "true")))
configNotifService.sendClusterScopedConfigChangeNotification(new ConfigChangeEventImpl(path.toSimpleEscapedPath(), CONTEXT.CONFIG_CHANGE));
}
}
private JtaTransactionConfigChangeContainer getEventForActiveTransaction() {
Transaction tx = null;
try {
tx = tmManager.getTransaction();
if (tx == null || Status.STATUS_ACTIVE != tx.getStatus()) {
return null;
}
} catch (SystemException e) {
return null;
}
int transactionId = tx.hashCode();
JtaTransactionConfigChangeContainer container = transactionMap.get(transactionId);
if(container == null) {
container = new JtaTransactionConfigChangeContainer(transactionId);
try {
tx.registerSynchronization(container);
} catch (Exception e) {
throw new RuntimeException("Unexpected error - unable to register tx hook", e);
}
transactionMap.put(transactionId, container);
}
return container;
}
private class JtaTransactionConfigChangeContainer implements Synchronization {
private final int transactionId;
private List<String> changedPaths = new ArrayList<>();
private CONTEXT context = CONTEXT.CONFIG_CHANGE;
private JtaTransactionConfigChangeContainer(int transactionId) {
this.transactionId = transactionId;
}
private JtaTransactionConfigChangeContainer(String path) {
transactionId = -1;
changedPaths.add(path);
}
private void addChangedPath(String path) {
changedPaths.add(path);
}
private void setContext(CONTEXT context) {
this.context = context;
}
@Override
public void afterCompletion(int status) {
transactionMap.remove(transactionId);
// only notify if the changes were successfully committed
if (status == Status.STATUS_COMMITTED)
if (Boolean.valueOf(System.getProperty(NOTIFICATIONS_ENABLED_PROPERTY, "true")))
configNotifService.sendClusterScopedConfigChangeNotification(
new ConfigChangeEventImpl(changedPaths, context));
}
@Override
public void beforeCompletion() {
// NOOP
}
}
private static class ConfigChangeEventImpl implements ConfigChangeEvent {
private static final long serialVersionUID = 4454659631189062807L;
private final List<String> changedPaths;
private final CONTEXT context;
private ConfigChangeEventImpl(String path, CONTEXT context) {
this(Arrays.asList(path), context);
}
private ConfigChangeEventImpl(List<String> changedPaths, CONTEXT context) {
this.changedPaths = changedPaths;
this.context = context;
}
@Override
public CONTEXT getContext() {
return context;
}
@Override
public List<String> getChangedPaths() {
return changedPaths;
}
}
}