/* * Copyright (c) 2015 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: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.common.headless.orient; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.headless.transform.AbstractTransformationSink; import eu.esdihumboldt.hale.common.headless.transform.LimboInstanceSink; import eu.esdihumboldt.hale.common.instance.model.DataSet; import eu.esdihumboldt.hale.common.instance.model.Filter; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.instance.model.InstanceCollection; import eu.esdihumboldt.hale.common.instance.model.InstanceReference; import eu.esdihumboldt.hale.common.instance.model.ResourceIterator; import eu.esdihumboldt.hale.common.instance.orient.storage.BrowseOrientInstanceCollection; import eu.esdihumboldt.hale.common.instance.orient.storage.LocalOrientDB; import eu.esdihumboldt.hale.common.instance.orient.storage.OrientInstanceReference; import eu.esdihumboldt.hale.common.instance.orient.storage.OrientInstanceSink; import eu.esdihumboldt.hale.common.schema.model.TypeIndex; /** * Reiterable transformation sink based on OrientDB. * * @author Simon Templer */ public class OrientTransformationSink extends AbstractTransformationSink { private class OrientLimboCollection implements InstanceCollection { private boolean firstIterator = true; private boolean limboOpen = false; @Override public InstanceReference getReference(Instance instance) { if (instance instanceof InstanceWithReference) { // instance served by limbo sink collection return ((InstanceWithReference) instance).getReference(); } // served by Orient instance collection return OrientInstanceReference.createReference(instance); } @Override public Instance getInstance(InstanceReference reference) { OrientInstanceReference ref = (OrientInstanceReference) reference; return ref.load(database, this); } @Override public ResourceIterator<Instance> iterator() { if (!complete.get() && !skipLimbo.get() && firstIterator) { firstIterator = false; limboOpen = true; return limboSink.getInstanceCollection().iterator(); } else { waitToComplete(); return new BrowseOrientInstanceCollection(database, types, DataSet.TRANSFORMED) .iterator(); } } @Override public boolean hasSize() { return false; } @Override public int size() { return UNKNOWN_SIZE; } @Override public boolean isEmpty() { // XXX have to return false, even if it actually may be empty return false; } @Override public InstanceCollection select(Filter filter) { waitToComplete(); // delegate to orient instance collection return new BrowseOrientInstanceCollection(database, types, DataSet.TRANSFORMED) .select(filter); } /** * Blocks until the database is completely populated. */ private void waitToComplete() { if (!limboOpen) { skipLimbo.set(true); // skip limbo sink (prevent blocking when // adding to limbo sink) // consume limbo sink, make sure it does not block any more limboSink.done(true); } // block until complete while (!complete.get()) { try { Thread.sleep(300); } catch (InterruptedException e) { log.error("Waiting for transformation completion interrupted", e); } } dbThread.shutdown(); try { dbThread.awaitTermination(100, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("Waiting for termination of database thread interupted", e); } } } private static final ALogger log = ALoggerFactory.getLogger(OrientTransformationSink.class); private final Path tmpLocation; private final LocalOrientDB database; private final OrientInstanceSink orientSink; private final LimboInstanceSink limboSink; private final OrientLimboCollection collection; private TypeIndex types; private final AtomicBoolean complete = new AtomicBoolean(); private final AtomicBoolean skipLimbo = new AtomicBoolean(); private final ExecutorService dbThread = Executors.newSingleThreadExecutor(); /** * Default constructor. */ public OrientTransformationSink() { super(); // create temporary database try { this.tmpLocation = Files.createTempDirectory("transformationSink"); } catch (IOException e) { throw new IllegalStateException("Cannot create temporary database location", e); } this.database = new LocalOrientDB(tmpLocation.toFile()); // create orient sink this.orientSink = new OrientInstanceSink(database, false); /* * create limbo sink (for first iteration while data it still stored in * the Orient database) */ this.limboSink = new LimboInstanceSink(); this.collection = new OrientLimboCollection(); } @Override public void setTypes(TypeIndex types) { this.types = types; } @Override protected void internalDone(boolean cancel) { dbThread.execute(new Runnable() { @Override public void run() { try { orientSink.close(); } catch (IOException e) { log.error("Failed to close OrientDB instance sink", e); } } }); limboSink.done(cancel); complete.set(true); dbThread.shutdown(); } @Override public void dispose() { dbThread.shutdown(); try { dbThread.awaitTermination(60, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("Waiting for termination of database thread interupted", e); } database.delete(); try { Files.deleteIfExists(tmpLocation); } catch (IOException e) { log.warn("Could not delete database directory", e); } super.dispose(); } @Override protected void internalAddInstance(final Instance instance) { dbThread.execute(new Runnable() { @Override public void run() { // add to database InstanceReference ref = orientSink.putInstance(instance); // add to limbo sink if (!skipLimbo.get()) { // possible problem: limbo sink may block limboSink.addInstance(new InstanceWithReference(instance, ref)); } } }); } @Override public InstanceCollection getInstanceCollection() { return collection; } }