/*
* 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.cst.internal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import eu.esdihumboldt.cst.extension.hooks.HooksUtil;
import eu.esdihumboldt.cst.extension.hooks.TransformationTreeHook.TreeState;
import eu.esdihumboldt.cst.extension.hooks.TransformationTreeHooks;
import eu.esdihumboldt.hale.common.align.model.Alignment;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.Priority;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationTree;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.context.ContextMatcher;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.context.impl.matcher.AsDeepAsPossible;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.DuplicationVisitor;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.visitor.InstanceVisitor;
import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog;
import eu.esdihumboldt.hale.common.align.transformation.report.TransformationReporter;
import eu.esdihumboldt.hale.common.align.transformation.report.impl.TransformationMessageImpl;
import eu.esdihumboldt.hale.common.align.transformation.service.InstanceSink;
import eu.esdihumboldt.hale.common.align.transformation.service.PropertyTransformer;
import eu.esdihumboldt.hale.common.core.HalePlatform;
import eu.esdihumboldt.hale.common.instance.extension.metadata.MetadataWorker;
import eu.esdihumboldt.hale.common.instance.model.FamilyInstance;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.InstanceMetadata;
import eu.esdihumboldt.hale.common.instance.model.InstanceUtil;
import eu.esdihumboldt.hale.common.instance.model.MutableInstance;
import gnu.trove.TObjectIntHashMap;
import gnu.trove.TObjectIntProcedure;
/**
* Property transformer based on a {@link TransformationTree}.
*
* @author Simon Templer
*/
public class TreePropertyTransformer implements PropertyTransformer {
// private final TransformationReporter reporter;
private final InstanceSink sink;
private final TransformationTreePool treePool;
private final List<FunctionExecutor> executors;
private final InstanceBuilder builder;
private final ExecutorService executorService;
/**
* Controls if multiple threads are used for transformation.
*/
private final boolean forkedTransformation = false;
// make metadataworker threadsave
private final ThreadLocal<MetadataWorker> metaworkerthread = new ThreadLocal<MetadataWorker>() {
@Override
protected MetadataWorker initialValue() {
return new MetadataWorker();
}
};
private final TransformationTreeHooks treeHooks;
private final TObjectIntHashMap<Cell> instanceCounter = new TObjectIntHashMap<>();
private final TransformationReporter reporter;
/**
* Create a simple property transformer
*
* @param alignment the alignment
* @param reporter the transformation log to report any transformation
* messages to
* @param sink the target instance sink
* @param engines the transformation engine manager
* @param context the transformation execution context
*/
public TreePropertyTransformer(Alignment alignment, TransformationReporter reporter,
InstanceSink sink, EngineManager engines, TransformationContext context) {
this.reporter = reporter;
this.sink = sink;
// XXX how to determine matcher?
ContextMatcher matcher = new AsDeepAsPossible(context.getServiceProvider());
treePool = new TransformationTreePool(alignment, matcher);
/*
* create executors in order of priority, highest first.
*/
executors = new ArrayList<FunctionExecutor>();
Priority[] priorityValuesDescending = Priority.values();
for (Priority priority : priorityValuesDescending) {
FunctionExecutor executor = new FunctionExecutor(reporter, engines, context, priority);
executors.add(executor);
}
builder = new InstanceBuilder();
treeHooks = HalePlatform.getService(TransformationTreeHooks.class);
if (forkedTransformation) {
executorService = new ThreadPoolExecutor(4, 4, // 4 threads
0L, TimeUnit.MILLISECONDS,
// maximum queue size 1000 (keep 1000 instances/workers in
// memory
// simultaneously at max)
new LinkedBlockingQueue<Runnable>(1000) {
private static final long serialVersionUID = 1L;
@Override
public boolean offer(Runnable e) {
// wait for space to be free in the queue
try {
super.put(e);
} catch (InterruptedException e1) {
// XXX correct to return false?
return false;
}
// then accept
return true;
/*
* Alternative could be calling offer in a loop with
* a wait until it returns true.
*/
}
});
}
else {
executorService = null;
}
}
/**
* @see PropertyTransformer#publish(FamilyInstance, MutableInstance,
* TransformationLog, Cell)
*/
@Override
public void publish(final FamilyInstance source, final MutableInstance target,
final TransformationLog typeLog, final Cell typeCell) {
instanceCounter.adjustOrPutValue(typeCell, 1, 1);
Runnable job = new Runnable() {
@Override
public void run() {
try {
// Add the meta data ID of the source as SourceID to the
// target
Collection<Instance> sources = InstanceUtil.getInstanceOutOfFamily(source);
Set<Object> ids = new HashSet<Object>();
for (Instance inst : sources) {
// Merge instances may have multiple IDs
List<Object> sourceIDs = inst.getMetaData(InstanceMetadata.METADATA_ID);
if (sourceIDs != null) {
ids.addAll(sourceIDs);
}
}
InstanceMetadata.setSourceID(target, ids.toArray());
// identify transformations to be executed on given
// instances
// create/get a transformation tree
TransformationTree tree = treePool.getTree(typeCell);
// State: base tree
HooksUtil.executeTreeHooks(treeHooks, TreeState.MINIMAL, tree, target);
// apply instance value to transformation tree
InstanceVisitor instanceVisitor = new InstanceVisitor(source, tree, typeLog);
tree.accept(instanceVisitor);
// State: basic source populated tree
// duplicate subtree as necessary
DuplicationVisitor duplicationVisitor = new DuplicationVisitor(tree, typeLog);
tree.accept(duplicationVisitor);
duplicationVisitor.doAugmentationTrackback();
// State: source populated tree (duplication complete)
HooksUtil.executeTreeHooks(treeHooks, TreeState.SOURCE_POPULATED, tree, target);
// apply functions
for (FunctionExecutor functionExecutor : executors) {
functionExecutor.setTypeCell(typeCell);
tree.accept(functionExecutor);
}
// State: full tree (target populated)
HooksUtil.executeTreeHooks(treeHooks, TreeState.FULL, tree, target);
// fill instance
builder.populate(target, tree, typeLog);
// generate the rest of the metadatas
metaworkerthread.get().generate(target);
// XXX ok to add to sink in any thread?!
// XXX addInstance and close were made synchronized in
// OrientInstanceSink
// XXX instead collect instances and write them in only one
// thread?
// after property transformations, publish target instance
sink.addInstance(target);
// and release the tree for further use
treePool.releaseTree(tree);
} catch (Throwable e) {
/*
* Catch any error, as exceptions in the executor service
* will only result in a message on the console.
*/
typeLog.error(
typeLog.createMessage("Error performing property transformations", e));
}
}
};
if (executorService != null) {
executorService.execute(job);
}
else {
job.run();
}
}
@Override
public void join(boolean cancel) {
if (executorService != null) {
if (cancel) {
executorService.shutdownNow();
}
else {
executorService.shutdown();
}
if (executorService.isTerminated()) {
return;
}
try {
// TODO make configurable?
if (!executorService.awaitTermination(15, TimeUnit.MINUTES)) {
// TODO error message
}
} catch (InterruptedException e) {
// ignore
}
}
// report instance counts
instanceCounter.forEachEntry(new TObjectIntProcedure<Cell>() {
@Override
public boolean execute(Cell cell, int count) {
reporter.info(new TransformationMessageImpl(cell,
MessageFormat.format("Created {0} instances during transformation", count),
null));
return true;
}
});
}
}