/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.instance.orient.storage;
import java.lang.ref.Reference;
import java.util.Arrays;
import java.util.Set;
import javax.xml.namespace.QName;
import com.google.common.base.FinalizablePhantomReference;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.collect.Sets;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import eu.esdihumboldt.hale.common.instance.model.Group;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.impl.GroupDecorator;
import eu.esdihumboldt.hale.common.instance.model.impl.InstanceDecorator;
/**
* Database handle that manages objects referencing the database object. It will
* release the connection when all those objects have been garbage collected.
*
* @author Simon Templer
*/
public class DatabaseHandle {
@SuppressWarnings("javadoc")
public final class InstanceHandle extends InstanceDecorator {
public InstanceHandle(Instance instance) {
super(instance);
}
@Override
public Object[] getProperty(QName propertyName) {
return augmentValues(getOriginalInstance().getProperty(propertyName));
}
}
@SuppressWarnings("javadoc")
public final class GroupHandle extends GroupDecorator {
public GroupHandle(Group group) {
super(group);
}
@Override
public Object[] getProperty(QName propertyName) {
return augmentValues(getOriginalGroup().getProperty(propertyName));
}
}
private static final ALogger log = ALoggerFactory.getLogger(DatabaseHandle.class);
/**
* The database connection.
*/
protected final ODatabaseDocumentTx database;
private long count = 0;
private static final FinalizableReferenceQueue referenceQueue = new FinalizableReferenceQueue();
private final Set<Reference<?>> references = Sets.newConcurrentHashSet();
// This ensures that the FinalizablePhantomReference itself is not
// garbage-collected.
private static final Set<Reference<?>> handleReferences = Sets.newConcurrentHashSet();
/**
* Create a database handle
*
* @param database the database connection
*/
public DatabaseHandle(final ODatabaseDocumentTx database) {
super();
this.database = database;
handleReferences.add(new FinalizablePhantomReference<DatabaseHandle>(this, referenceQueue) {
@Override
public void finalizeReferent() {
handleReferences.remove(this);
try {
if (!database.isClosed()) {
database.close();
}
} catch (Exception e) {
// ignore
}
log.info("Closed garbage collected database handle");
}
});
}
/**
* Add an object that references the database connection.
*
* It is preferred to use {@link #addInstance(Instance)} or
* {@link #addGroup(Group)} instead.
*
* @param object the object referencing the database
*/
public synchronized void addReference(Object object) {
FinalizablePhantomReference<?> ref = new FinalizablePhantomReference<Object>(object,
referenceQueue) {
@Override
public void finalizeReferent() {
references.remove(this);
removeReference();
}
};
references.add(ref);
count++;
}
/**
* Augment an instance and add a reference for the database connection.
* Makes sure child instances or groups also reference the database
* connection.
*
* @param instance the instance to augment
* @return the augmented instance
*/
public Instance addInstance(Instance instance) {
if (instance == null) {
return null;
}
Instance result = new InstanceHandle(instance);
addReference(result);
return result;
}
/**
* Augment a group and add a reference for the database connection. Makes
* sure child instances or groups also reference the database connection.
*
* @param group the group to augment
* @return the augmented group
*/
public Group addGroup(Group group) {
if (group == null) {
return null;
}
Group result = new GroupHandle(group);
addReference(result);
return result;
}
/**
* Augment an array of values.
*
* @param values the values to augment
* @return the augmented objects
*/
protected Object[] augmentValues(Object[] values) {
if (values == null) {
return null;
}
return Arrays.stream(values).map(value -> {
if (value instanceof Instance) {
return addInstance((Instance) value);
}
if (value instanceof Group) {
return addGroup((Group) value);
}
return value;
}).toArray();
}
private synchronized void removeReference() {
count--;
tryClose();
}
/**
* Try closing the database connection
*/
public synchronized void tryClose() {
if (count <= 0) {
database.close();
onClose();
}
}
/**
* Called when the database connection was closed.
*/
protected void onClose() {
// override me
}
/**
* @return the database
*/
public ODatabaseDocumentTx getDatabase() {
return database;
}
}