/*
* **** 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) 2015
* 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.storage;
import org.codehaus.jackson.map.ObjectMapper;
import org.dcm4che3.conf.core.Nodes;
import org.dcm4che3.conf.core.api.BatchRunner.Batch;
import org.dcm4che3.conf.core.api.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Roman K
*/
@Stateless
public class DBStorageBean {
private static final Logger log = LoggerFactory.getLogger(DBStorageBean.class);
private final ObjectMapper OM = new ObjectMapper();
@PersistenceContext(unitName = "dcm4chee-conf")
private EntityManager em;
public static final String LOCK_PATH = "/misc/locking/dblock";
@EJB
DBStorageBean self;
public Map<String, Object> getFullTree() {
Query query = em.createQuery("SELECT n FROM ConfigNodeEntity n");
List<ConfigNodeEntity> resultList = query.getResultList();
Map<String, Object> map = new HashMap<String, Object>();
for (ConfigNodeEntity configNodeEntity : resultList) {
Map loadedNode = configNodeEntity.getContent() == null ?
new HashMap() :
fromBytes(configNodeEntity.getContent());
String path = configNodeEntity.getPath();
Nodes.replaceNode(map, loadedNode, Path.fromSimpleEscapedPath(path).getPathItems());
}
return map;
}
public boolean isEmpty() {
Query query = em.createQuery("SELECT count (n) FROM ConfigNodeEntity n");
Long count = (Long) query.getSingleResult();
return count == 0;
}
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void lock() {
getLockOnExistingLockEntity();
}
private void getLockOnExistingLockEntity() {
Query query = em.createQuery("SELECT n FROM ConfigNodeEntity n WHERE n.path=?1");
query.setParameter(1, LOCK_PATH);
ConfigNodeEntity singleResult = (ConfigNodeEntity) query.getSingleResult();
log.debug("Trying to aquire a pessimistic lock on " + LOCK_PATH);
em.lock(singleResult, LockModeType.PESSIMISTIC_WRITE);
log.debug("Aquired a pessimistic lock on " + LOCK_PATH);
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void createLockingRowIfnotExists() throws UnableToPersistLockingRowException {
log.debug("Checking if locking row exists");
Query query = em.createQuery("SELECT count (n) FROM ConfigNodeEntity n WHERE n.path=?1");
query.setParameter(1, LOCK_PATH);
Long count = (Long) query.getSingleResult();
if (count == 0) {
log.debug("Locking row does not exist, inserting...");
ConfigNodeEntity entity = new ConfigNodeEntity();
entity.setPath(LOCK_PATH);
entity.setContent(new byte[]{'{', '}'});
try {
em.persist(entity);
log.debug("Locking row inserted");
} catch (Exception e) {
throw new UnableToPersistLockingRowException(e);
}
} else {
log.debug("Locking row already exists");
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean isLockingRowExists() {
Query query = em.createQuery("SELECT count (n) FROM ConfigNodeEntity n WHERE n.path=?1");
query.setParameter(1, LOCK_PATH);
Long count = (Long) query.getSingleResult();
return count == 1;
}
public static class UnableToPersistLockingRowException extends Exception {
public UnableToPersistLockingRowException(Exception e) {
super(e);
}
}
public Object getNode(List<Object> pathItemsForDB) {
ConfigNodeEntity node = getConfigNodeEntityForDBPath(pathItemsForDB);
Object loadedNode = node.getContent() == null ?
new HashMap() :
fromBytes(node.getContent());
return loadedNode;
}
public void removeNode(List<Object> pathItemsForDB, List<Object> restPathItems) {
if (restPathItems.size() == 0) {
Query query;
// if less than level, allow bulk delete of x/y/*
if (pathItemsForDB.size() < SemiSerializedDBConfigStorage.level) {
query = em.createQuery("DELETE FROM ConfigNodeEntity n WHERE n.path like ?1");
query.setParameter(1, Nodes.toSimpleEscapedPath(pathItemsForDB) + "%");
}
// otherwise must be equals
else {
query = em.createQuery("DELETE FROM ConfigNodeEntity n WHERE n.path=?1");
query.setParameter(1, Nodes.toSimpleEscapedPath(pathItemsForDB));
}
query.executeUpdate();
} else
try {
ConfigNodeEntity node = getConfigNodeEntityForDBPath(pathItemsForDB);
Map<String, Object> map = fromBytes(node.getContent());
Nodes.removeNode(map, restPathItems);
node.setContent(toBytes(map));
em.merge(node);
} catch (NoResultException e) {
return;
}
}
public void modifyNode(List<Object> pathItemsForDB, List<Object> restPathItems, Map<String, Object> configNode) {
String dbPath = Nodes.toSimpleEscapedPath(pathItemsForDB);
ConfigNodeEntity node;
try {
node = getConfigNodeEntityForDBPath(pathItemsForDB);
} catch (NoResultException e) {
// its ok, create new
node = new ConfigNodeEntity();
node.setPath(dbPath);
Map<String, Object> map = Nodes.replaceNode(new HashMap<>(), configNode, restPathItems);
node.setContent(toBytes(map));
em.persist(node);
return;
} catch (Exception e) {
throw new RuntimeException("Cannot persist configuration node @ " + dbPath, e);
}
// merge
Map<String, Object> map = Nodes.replaceNode(fromBytes(node.getContent()), configNode, restPathItems);
node.setContent(toBytes(map));
em.merge(node);
}
public boolean nodeExists(List<Object> pathItemsForDB, List<Object> restPathItems) {
try {
ConfigNodeEntity node = getConfigNodeEntityForDBPath(pathItemsForDB);
return Nodes.nodeExists(fromBytes(node.getContent()), restPathItems);
} catch (NoResultException e) {
return false;
}
}
private Map<String, Object> fromBytes(byte[] content) {
try {
return OM.readValue(content, Map.class);
} catch (IOException e) {
throw new RuntimeException("Cannot deserialize node", e);
}
}
private byte[] toBytes(Map<String, Object> map) {
try {
return OM.writeValueAsBytes(map);
} catch (IOException e) {
throw new RuntimeException("Cannot serialize node", e);
}
}
private ConfigNodeEntity getConfigNodeEntityForDBPath(List<Object> pathItemsForDB) {
String dbPath = Nodes.toSimpleEscapedPath(pathItemsForDB);
Query query = em.createQuery("SELECT n FROM ConfigNodeEntity n WHERE n.path=?1");
query.setParameter(1, dbPath);
return (ConfigNodeEntity) query.getSingleResult();
}
public void runBatch(Batch r) {
r.run();
}
}