/******************************************************************************* * Copyright (c) 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.db.layer0.migration; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.container.DataContainer; import org.simantics.databoard.container.DataContainers; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.WriteGraph; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.utils.Logger; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.AssumptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.adapter.impl.DefaultPasteImportAdvisor; import org.simantics.db.layer0.internal.SimanticsInternal; import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.layer0.util.ModelTransferableGraphSourceRequest; import org.simantics.db.layer0.util.TGProgressMonitor; import org.simantics.db.layer0.util.TGTransferableGraphSource; import org.simantics.db.layer0.util.TransferableGraphConfiguration2; import org.simantics.db.request.Read; import org.simantics.db.service.ManagementSupport; import org.simantics.db.service.SerialisationSupport; import org.simantics.graph.db.IImportAdvisor; import org.simantics.graph.db.IImportAdvisor2; import org.simantics.graph.db.ImportResult; import org.simantics.graph.db.StreamingTransferableGraphFileReader; import org.simantics.graph.db.TGStatusMonitor; import org.simantics.graph.db.TransferableGraphImporter; import org.simantics.graph.db.TransferableGraphSource; import org.simantics.graph.db.TransferableGraphs; import org.simantics.graph.db.WrapperAdvisor; import org.simantics.graph.representation.TransferableGraph1; import org.simantics.graph.representation.TransferableGraphFileReader; import org.simantics.layer0.Layer0; import org.simantics.utils.logging.TimeLogger; public class MigrationStateImpl implements MigrationState { final private HashMap properties = new HashMap(); @SuppressWarnings("unchecked") @Override public T probeProperty(String key) throws DatabaseException { return (T)properties.get(key); } @SuppressWarnings("unchecked") @Override public T getProperty(String key) throws DatabaseException { T property = (T)properties.get(key); if(property != null) return property; if(MigrationStateKeys.BASE_URI.equals(key)) { throw new IllegalStateException("Base URI needs to be supplied for migration."); } else if (MigrationStateKeys.SESSION.equals(key)) { throw new IllegalStateException("Session needs to be supplied for migration."); } else if (MigrationStateKeys.MODEL_FILE.equals(key)) { throw new IllegalStateException("Model file needs to be supplied for migration."); } else if (MigrationStateKeys.CURRENT_TG.equals(key)) { final Resource resource = probeProperty(MigrationStateKeys.CURRENT_RESOURCE); final Collection roots = probeProperty(MigrationStateKeys.CURRENT_ROOT_RESOURCES); if(roots != null) { Session session = getProperty(MigrationStateKeys.SESSION); TransferableGraph1 tg = session.syncRequest(new Read() { @Override public TransferableGraph1 perform(ReadGraph graph) throws DatabaseException { TransferableGraphConfiguration2 conf = new TransferableGraphConfiguration2(graph, roots, true, false); TransferableGraphSource source = graph.syncRequest(new ModelTransferableGraphSourceRequest(conf)); return TransferableGraphs.create(graph, source); } }); if (resource != null) MigrationUtils.clearTempResource(session, resource); setProperty(MigrationStateKeys.CURRENT_RESOURCE, null); setProperty(MigrationStateKeys.CURRENT_ROOT_RESOURCES, null); setProperty(MigrationStateKeys.DATABASE_REVISION_AFTER_TG_IMPORT, null); setProperty(MigrationStateKeys.CURRENT_TG, tg); return (T)tg; } try { File modelFile = getProperty(MigrationStateKeys.MODEL_FILE); TimeLogger.log(MigrationStateImpl.class, "reading TG into memory from " + modelFile); TransferableGraph1 tg = TransferableGraphFileReader.read(modelFile, false); TimeLogger.log(MigrationStateImpl.class, "read TG into memory from " + modelFile); setProperty(MigrationStateKeys.CURRENT_TG, tg); return (T)tg; } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } } else if (MigrationStateKeys.CURRENT_TGS.equals(key)) { File modelFile = getProperty(MigrationStateKeys.MODEL_FILE); return (T) initializeTransferableGraphSource(modelFile); } else if (MigrationStateKeys.CURRENT_DATA_CONTAINER.equals(key)) { try { TransferableGraphSource tgs = getProperty(MigrationStateKeys.CURRENT_TGS); DataContainer dc = tgs.getHeader(); setProperty(MigrationStateKeys.CURRENT_DATA_CONTAINER, dc); return (T)dc; } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } } else if (MigrationStateKeys.TG_EXTENSIONS.equals(key)) { try { TransferableGraphSource tgs = getProperty(MigrationStateKeys.CURRENT_TGS); Map extensions = tgs.getExtensions(); setProperty(MigrationStateKeys.TG_EXTENSIONS, extensions); return (T)extensions; } catch (DatabaseException e) { throw e; } catch (Throwable t) { throw new DatabaseException(t); } } else if (MigrationStateKeys.CURRENT_RESOURCE.equals(key) || MigrationStateKeys.CURRENT_ROOT_RESOURCES.equals(key)) { final Session session = getProperty(MigrationStateKeys.SESSION); final IProgressMonitor monitor = probeProperty(MigrationStateKeys.PROGRESS_MONITOR); final boolean updateDependencies = MigrationUtils.getProperty(this, MigrationStateKeys.UPDATE_DEPENDENCIES, Boolean.TRUE); File temporaryTg = exportCurrentTgAsTemporaryFile(session, monitor); if (temporaryTg != null) setProperty(MigrationStateKeys.CURRENT_TGS, initializeTransferableGraphSource(temporaryTg)); TransferableGraphSource tgs = getProperty(MigrationStateKeys.CURRENT_TGS); if (tgs != null) { importTransferableGraphSource(monitor, session, updateDependencies, tgs); // Delete temporary file if necessary if (temporaryTg != null) temporaryTg.delete(); return getProperty(key); } } else if (MigrationStateKeys.UPDATE_DEPENDENCIES.equals(key)) { return null; } return null; } @Override public void setProperty(String key, T value) { properties.put(key, value); } @Override public void dispose() { // Close all possible open file handles try { StreamingTransferableGraphFileReader tgs = probeProperty(MigrationStateKeys.CURRENT_TGS_READER); uncheckedClose(tgs); } catch (DatabaseException e) { Logger.defaultLogError(e); } } private static void uncheckedClose(Closeable closeable) { try { if (closeable != null) closeable.close(); } catch (IOException e) { //ignore } } private Resource createTemporaryRoot(WriteGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); Resource project = SimanticsInternal.getProject(); Resource root = graph.getPossibleObject(project, L0.PartOf); Resource temp = Layer0Utils.getPossibleChild(graph, root, "Temp"); if (temp == null) throw new AssumptionException("Temporary folder 'Temp' not found under " + graph.getPossibleURI(root)); Resource indexRoot = graph.newResource(); String indexRootName = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date()); indexRootName = NameUtils.findFreshName(graph, indexRootName, temp, L0.ConsistsOf, "%s-%d"); graph.claim(indexRoot, L0.InstanceOf, L0.IndexRoot); graph.addLiteral(indexRoot, L0.HasName, L0.String, indexRootName, Bindings.STRING); graph.claim(temp, L0.ConsistsOf, indexRoot); return indexRoot; } private File exportCurrentTgAsTemporaryFile(Session session, IProgressMonitor monitor) throws DatabaseException { TransferableGraph1 tg = probeProperty(MigrationStateKeys.CURRENT_TG); if (tg == null) return null; try { // Write TG back to disk and initialize CURRENT_TGS for the migrated TG. File modelFile = getProperty(MigrationStateKeys.MODEL_FILE); File tempFile = File.createTempFile("temporary-tgs", ".tg", SimanticsInternal.getTemporaryDirectory()); TimeLogger.log(MigrationStateImpl.class, "export temporary TG " + tempFile); DataContainer dc = DataContainers.readHeader(modelFile); TransferableGraphs.writeTransferableGraph(session, dc.format, dc.version, dc.metadata, new TGTransferableGraphSource(tg), tempFile, new TGExportMonitor(monitor, "Exporting temporary transferable graph")); // Allow potentially large TG structure to be GC'ed. setProperty(MigrationStateKeys.CURRENT_TG, null); TimeLogger.log(MigrationStateImpl.class, "export temporary TG done " + tempFile); return tempFile; } catch (Exception e) { throw new DatabaseException(e); } } private TransferableGraphSource initializeTransferableGraphSource(File dataContainer) throws DatabaseException { try { StreamingTransferableGraphFileReader reader = new StreamingTransferableGraphFileReader(dataContainer); TransferableGraphSource tgs = reader.readTG(); setProperty(MigrationStateKeys.CURRENT_TGS_READER, reader); setProperty(MigrationStateKeys.CURRENT_TGS, tgs); return tgs; } catch (DatabaseException e) { throw e; } catch (IOException e) { throw new DatabaseException("An I/O exception occurred during reading '" + dataContainer.getAbsolutePath() + "'", e); } catch (Throwable t) { throw new DatabaseException(t); } } private void importTransferableGraphSource(IProgressMonitor monitor, Session session, boolean updateDependencies, TransferableGraphSource tgs) throws DatabaseException { TimeLogger.log(MigrationStateImpl.class, "import TGS " + tgs); final Resource indexRoot = session.syncRequest(new WriteResultRequest() { @Override public Resource perform(WriteGraph graph) throws DatabaseException { if(!updateDependencies) Layer0Utils.setDependenciesIndexingDisabled(graph, true); return createTemporaryRoot(graph); } }); IImportAdvisor baseAdvisor = MigrationUtils.getProperty(this, MigrationStateKeys.IMPORT_ADVISOR, new DefaultPasteImportAdvisor(indexRoot)); IImportAdvisor2 advisor = new WrapperAdvisor(baseAdvisor) { @Override public Resource getTarget() { return indexRoot; } @Override public void beforeWrite(WriteOnlyGraph graph, TransferableGraphImporter process) throws DatabaseException { super.beforeWrite(graph, process); if(!updateDependencies) Layer0Utils.setDependenciesIndexingDisabled(graph, true); } @Override public void afterWrite(WriteOnlyGraph graph, TransferableGraphImporter process) throws DatabaseException { super.afterWrite(graph, process); Boolean storeResources = probeProperty(MigrationStateKeys.GET_RESOURCE_IDS); if(storeResources != null && storeResources) { long[] ids = process.getResourceIds(session.getService(SerialisationSupport.class)); setProperty(MigrationStateKeys.RESOURCE_IDS, ids); } } }; // Make sure that the supplied advisor is redirected to temp advisor.redirect(indexRoot); String task = "Importing model into database"; monitor.subTask(task); ImportResult ir = TransferableGraphs.importGraph1(session, tgs, advisor, new TGImportMonitor(monitor, task)); monitor.subTask(""); setProperty(MigrationStateKeys.IMPORT_RESULT, ir); setProperty(MigrationStateKeys.CURRENT_RESOURCE, indexRoot); setProperty(MigrationStateKeys.CURRENT_ROOT_RESOURCES, new ArrayList<>(advisor.getRoots())); setProperty(MigrationStateKeys.DATABASE_REVISION_AFTER_TG_IMPORT, session.getService(ManagementSupport.class).getHeadRevisionId()); TimeLogger.log(MigrationStateImpl.class, "imported TGS " + tgs); } static class TGImportMonitor implements TGStatusMonitor { private final IProgressMonitor monitor; private final String message; public TGImportMonitor(IProgressMonitor monitor, String message) { this.monitor = monitor; this.message = message; } @Override public void status(int percentage) { monitor.subTask(message + " (" + percentage + "%)"); } @Override public boolean isCanceled() { return monitor.isCanceled(); } } static class TGExportMonitor extends TGProgressMonitor { private final String message; public TGExportMonitor(IProgressMonitor monitor, String message) { super(monitor); this.message = message; } @Override protected void workDone(int percentage) { monitor.subTask(message + " (" + percentage + "%)"); } } }