/******************************************************************************* * Copyright (c) 2013 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.graphfile.util; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.databoard.util.binary.RandomAccessBinary; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.utils.LiteralFileUtil; import org.simantics.db.common.utils.Logger; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.DoesNotContainValueException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.NoSingleResultException; import org.simantics.db.exception.ServiceException; import org.simantics.db.request.Read; import org.simantics.db.service.ClusteringSupport; import org.simantics.db.service.TransferableGraphSupport; import org.simantics.graphfile.ontology.GraphFileResource; import org.simantics.layer0.Layer0; /** * @author Marko Luukkainen */ public class GraphFileUtil { public static boolean USE_RANDOM_ACCESS_BINARY = true; /** * Creates a temp file of a graphFile. * @param res * @return * @throws DatabaseException */ public static File toTempFile(final Resource res)throws DatabaseException { return Simantics.getSession().syncRequest(new Read() { @Override public File perform(ReadGraph graph) throws DatabaseException { return toTempFile(graph, res); } }); } /** * Creates a temp file of a graphFile. * @param graph * @param res * @return * @throws DatabaseException */ public static File toTempFile(ReadGraph graph, Resource res)throws DatabaseException { GraphFileResource gf = GraphFileResource.getInstance(graph); String filename = graph.getPossibleRelatedValue(res, gf.HasResourceName); if (filename == null) filename = graph.getRelatedValue(res, Layer0.getInstance(graph).HasName); int index = filename.lastIndexOf("."); String name = ""; String ext = ""; if (index > 0) { name = filename.substring(0,index); ext = filename.substring(index+1); } else { name = filename; } if (name.length() < 3) { for (int i = name.length(); i < 3; i++) name += "_"; } try { File file = File.createTempFile(name, "."+ ext); writeDataToFile(graph, res, file); return file; } catch (Exception e) { throw new DatabaseException(e); } } public static void writeDataToFile(final Resource res, final File file) throws DatabaseException{ Simantics.getSession().syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { try { writeDataToFile(graph, res, file); } catch (IOException e) { throw new DatabaseException(e); } } }); } /** * Writes contents of a graphFile to file. * @param graph * @param res * @param file * @throws DatabaseException * @throws IOException */ public static void writeDataToFile(ReadGraph graph, Resource res, File file) throws DatabaseException, IOException { GraphFileResource gf = GraphFileResource.getInstance(graph); if (USE_RANDOM_ACCESS_BINARY) { Resource filedata = graph.getSingleObject(res, gf.HasFiledata); LiteralFileUtil.copyRandomAccessBinaryToFile(graph, filedata, file); } else { byte[] data = graph.getRelatedValue(res, gf.HasFiledata); FileOutputStream fos = new FileOutputStream(file); fos.write(data); fos.flush(); fos.close(); } Long lastModified = graph.getPossibleRelatedValue(res, gf.LastModified); if (lastModified != null) file.setLastModified(lastModified); } /** * Updates contents of a graphFile, including the name. * @param filename * @param graphFile * @throws DatabaseException */ public static void toGraph(final String filename, final Resource graphFile)throws DatabaseException { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { try { toGraph(graph, filename,graphFile); } catch (IOException e) { throw new DatabaseException(e); } } }); } /** * Updates contents of a graphFile, including the name. * @param graph * @param filename * @param graphFile * @throws DatabaseException * @throws IOException */ public static void toGraph(WriteGraph graph, String filename, Resource graphFile) throws DatabaseException, IOException { File file = new File(filename); if (!file.exists()) throw new IOException("File " + filename + " not found."); toGraph(graph, file, graphFile); } /** * Updates contents of a graphFile, including the name. * @param graph * @param file * @param graphFile * @throws DatabaseException * @throws IOException */ public static void toGraph(WriteGraph graph, File file, Resource graphFile) throws DatabaseException, IOException { writeDataToGraph(graph, file, graphFile); String name = file.getName(); GraphFileResource gf = GraphFileResource.getInstance(graph); graph.claimLiteral(graphFile, gf.HasResourceName, name); } /** * Writes contents of a file to a graphFile (data and time stamp). * @param graph * @param file * @param graphFile * @throws IOException * @throws ManyObjectsForFunctionalRelationException * @throws ServiceException */ public static void writeDataToGraph(WriteGraph graph, File file, Resource graphFile) throws IOException, DatabaseException{ GraphFileResource gf = GraphFileResource.getInstance(graph); if (USE_RANDOM_ACCESS_BINARY) { Resource fileData = graph.getPossibleObject(graphFile, gf.HasFiledata); RandomAccessBinary rab = null; if (fileData == null) { Layer0 l0 = Layer0.getInstance(graph); ClusteringSupport cs = graph.getService(ClusteringSupport.class); fileData = graph.newResource(cs.createCluster()); graph.claim(fileData, l0.InstanceOf, l0.ByteArray); graph.claim(graphFile, gf.HasFiledata, fileData); rab = graph.createRandomAccessBinary(fileData, Bindings.BYTE_ARRAY.type(), null); } else { rab = graph.getRandomAccessBinary(fileData); } LiteralFileUtil.copyRandomAccessBinaryFromFile(file, rab); } else { FileInputStream stream = new FileInputStream(file); FileChannel chan = stream.getChannel(); long lsize = chan.size(); if (lsize > Integer.MAX_VALUE) throw new IOException("File is too big"); int size = (int)lsize; final byte[] array = new byte[size]; ByteBuffer buf = ByteBuffer.wrap(array); while (size > 0) size -= chan.read(buf); graph.claimLiteral(graphFile, gf.HasFiledata, array); chan.close(); stream.close(); } graph.claimLiteral(graphFile, gf.LastModified, file.lastModified()); } public static void writeDataToGraph(WriteGraph graph, byte data[], Resource graphFile) throws IOException, DatabaseException { GraphFileResource gf = GraphFileResource.getInstance(graph); if (USE_RANDOM_ACCESS_BINARY) { Resource fileData = graph.getPossibleObject(graphFile, gf.HasFiledata); if (fileData == null) { Layer0 l0 = Layer0.getInstance(graph); ClusteringSupport cs = graph.getService(ClusteringSupport.class); fileData = graph.newResource(cs.createCluster()); graph.claim(fileData, l0.InstanceOf, l0.ByteArray); graph.claim(graphFile, gf.HasFiledata, fileData); graph.createRandomAccessBinary(fileData, Bindings.BYTE_ARRAY.type(), data); } else { InputStream input = new ByteArrayInputStream(data); LiteralFileUtil.copyStreamToRandomAccessBinary(graph, input, fileData); } } else { graph.claimLiteral(graphFile, gf.HasFiledata, data); } graph.claimLiteral(graphFile, gf.LastModified, System.currentTimeMillis()); } /** * Writes contents of a file to a graphFile (data and time stamp). * @param file * @param graphFile * @throws DatabaseException */ public static void writeDataToGraph(final File file, final Resource graphFile) throws DatabaseException { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { try { writeDataToGraph(graph, file, graphFile); } catch (IOException e) { throw new DatabaseException(e); } } }); } public static void syncFolderToGraph(WriteGraph g, File folder, Resource folderRes) throws Exception { File subFiles[] = folder.listFiles(); Layer0 l0 = Layer0.getInstance(g); GraphFileResource gf = GraphFileResource.getInstance(g); Collection subFileResources = g.getObjects(folderRes, gf.HasFile); Collection subFolderResources = g.getObjects(folderRes, gf.HasFolder); Map matching = new HashMap(); for (File f : subFiles) { String name = f.getName(); if (f.isDirectory()) { Resource matchingFolder = findWithName(g, subFolderResources, name); if (matchingFolder != null) { if (matching.containsKey(matchingFolder)) throw new Exception("Matching folder already in use" + f.getAbsolutePath() + " " + matchingFolder); matching.put(matchingFolder, f); syncFolderToGraph(g, f, matchingFolder); } else { matchingFolder = g.newResource(); g.claim(matchingFolder, l0.InstanceOf, gf.Folder); g.claimLiteral(matchingFolder, gf.HasResourceName, name); g.claimLiteral(matchingFolder, l0.HasName, name); g.claim(folderRes, gf.HasFolder, matchingFolder); matching.put(matchingFolder, f); syncFolderToGraph(g, f, matchingFolder); } } else { //file Resource fileRes = findWithName(g, subFileResources, name); if (fileRes != null) { if (matching.containsKey(fileRes)) throw new Exception("Matching file already in use" + f.getAbsolutePath() + " " + fileRes); matching.put(fileRes, f); toGraph(g, f, fileRes); } else { fileRes = g.newResource(); g.claim(fileRes, l0.InstanceOf, gf.File); g.claimLiteral(fileRes, gf.HasResourceName, name); g.claimLiteral(fileRes, l0.HasName, name); g.claim(folderRes, gf.HasFile, fileRes); matching.put(fileRes, f); toGraph(g, f, fileRes); } } } // delete resources, which have no matching file (or folder) for (Resource subFolder : subFolderResources) { if (!matching.containsKey(subFolder)) g.deny(subFolder); } for (Resource subFolder : subFileResources) { if (!matching.containsKey(subFolder)) g.deny(subFolder); } } public static void writeFolderToDisk(ReadGraph g, Resource folderRes, File folder) throws DatabaseException, IOException{ GraphFileResource gf = GraphFileResource.getInstance(g); for (Resource subFolder : g.getObjects(folderRes, gf.HasFolder)) { String name = g.getRelatedValue(subFolder, gf.HasResourceName); if (name.length() == 0) throw new DatabaseException("Empty folder name for " + subFolder); File newFolder = new File(folder.getAbsolutePath() + "/" + name); if (!newFolder.mkdir()) { throw new DatabaseException("Could not create folder " + name + " for resource " + subFolder); } writeFolderToDisk(g, subFolder, newFolder); } for (Resource fileRes : g.getObjects(folderRes, gf.HasFile)) { String name = g.getRelatedValue(fileRes, gf.HasResourceName); File file = new File(folder.getAbsolutePath() + "/" + name); writeDataToFile(g, fileRes, file); } } public static void syncFolderToGraph(WriteGraph g, File folder, Resource folderRes, ToGraphHelper helper) throws Exception { File subFiles[] = folder.listFiles(); GraphFileResource gf = GraphFileResource.getInstance(g); Collection subFileResources = g.getObjects(folderRes, gf.HasFile); Collection subFolderResources = g.getObjects(folderRes, gf.HasFolder); Map matching = new HashMap(); for (File f : subFiles) { String name = f.getName(); if (f.isDirectory()) { Resource matchingFolder = helper.findFolder(g, subFolderResources, name); if (matchingFolder != null) { if (matching.containsKey(matchingFolder)) throw new Exception("Matching folder already in use" + f.getAbsolutePath() + " " + matchingFolder); matching.put(matchingFolder, f); syncFolderToGraph(g, f, matchingFolder,helper); } else { matchingFolder = helper.createFolder(g, name); g.claim(folderRes, gf.HasFolder, matchingFolder); matching.put(matchingFolder, f); syncFolderToGraph(g, f, matchingFolder,helper); } } else { //file Resource fileRes = helper.findFile(g, subFileResources, name); if (fileRes != null) { if (matching.containsKey(fileRes)) throw new Exception("Matching file already in use" + f.getAbsolutePath() + " " + fileRes); matching.put(fileRes, f); toGraph(g, f, fileRes); } else { fileRes = helper.createFile(g, name); g.claim(folderRes, gf.HasFile, fileRes); matching.put(fileRes, f); toGraph(g, f, fileRes); } } } // delete resources, which have no matching file (or folder) for (Resource subFolder : subFolderResources) { if (!matching.containsKey(subFolder)) g.deny(subFolder); } for (Resource subFolder : subFileResources) { if (!matching.containsKey(subFolder)) g.deny(subFolder); } } public static interface ToGraphHelper { public Resource findFolder(ReadGraph g, Collection subFolderResources, String name) throws DatabaseException; public Resource createFolder(WriteGraph g, String name) throws DatabaseException; public Resource findFile(ReadGraph g, Collection subFileResources, String name) throws DatabaseException; public Resource createFile(WriteGraph g, String name) throws DatabaseException; } public static interface ToDiskHelper { public String getName(ReadGraph g, Resource systemResource) throws DatabaseException; } public static void writeFolderToDisk(ReadGraph g, Resource folderRes, File folder, ToDiskHelper helper) throws DatabaseException, IOException{ GraphFileResource gf = GraphFileResource.getInstance(g); for (Resource subFolder : g.getObjects(folderRes, gf.HasFolder)) { String name = helper.getName(g, subFolder); if (name.length() == 0) throw new DatabaseException("Empty folder name for " + subFolder); File newFolder = new File(folder.getAbsolutePath() + "/" + name); if (!newFolder.mkdir()) { throw new DatabaseException("Could not create folder " + name + " for resource " + subFolder); } writeFolderToDisk(g, subFolder, newFolder, helper); } for (Resource fileRes : g.getObjects(folderRes, gf.HasFile)) { String name = helper.getName(g, fileRes); File file = new File(folder.getAbsolutePath() + "/" + name); writeDataToFile(g, fileRes, file); } } public static interface ToDiskHelper2 extends ToDiskHelper { public Resource findFolder(ReadGraph g, Collection subFolderResources, String name) throws DatabaseException; public Resource findFile(ReadGraph g, Collection subFileResources, String name) throws DatabaseException; } public static void syncFolderToDisk(ReadGraph g, Resource folderRes, File folder, ToDiskHelper2 helper) throws Exception { File subFiles[] = folder.listFiles(); GraphFileResource gf = GraphFileResource.getInstance(g); Collection subFileResources = g.getObjects(folderRes, gf.HasFile); Collection subFolderResources = g.getObjects(folderRes, gf.HasFolder); Map matching = new HashMap(); for (File f : subFiles) { String name = f.getName(); if (f.isDirectory()) { Resource matchingFolder = helper.findFolder(g, subFolderResources, name); if (matchingFolder != null) { if (matching.containsKey(matchingFolder)) throw new Exception("Matching folder already in use" + f.getAbsolutePath() + " " + matchingFolder); matching.put(matchingFolder, f); syncFolderToDisk(g, matchingFolder, f, helper); } else { deleteDirectoryStructure(f); } } else { //file Resource fileRes = helper.findFile(g, subFileResources, name); if (fileRes != null) { if (matching.containsKey(fileRes)) throw new Exception("Matching file already in use" + f.getAbsolutePath() + " " + fileRes); matching.put(fileRes, f); writeDataToFile(g, fileRes, f); } else { if (!f.delete()) throw new Exception("Cannot delete file " + f.getAbsolutePath()); } } } // create files and folders, which have no matching graphFile (or folder) for (Resource subFolder : subFolderResources) { if (!matching.containsKey(subFolder)) { String name = helper.getName(g, subFolder); if (name.length() == 0) throw new DatabaseException("Empty folder name for " + subFolder); File newFolder = new File(folder.getAbsolutePath() + "/" + name); if (!newFolder.mkdir()) { throw new DatabaseException("Could not create folder " + name + " for resource " + subFolder); } writeFolderToDisk(g, subFolder, newFolder, helper); } } for (Resource fileRes : subFileResources) { if (!matching.containsKey(fileRes)) { String name = helper.getName(g, fileRes); File file = new File(folder.getAbsolutePath() + "/" + name); writeDataToFile(g, fileRes, file); } } } public static Resource findWithName(ReadGraph g, Collection resources, String name) throws ServiceException, NoSingleResultException, DoesNotContainValueException { GraphFileResource gf = GraphFileResource.getInstance(g); for (Resource r : resources) if (name.equals(g.getRelatedValue(r, gf.HasResourceName))) return r; return null; } /** * Deletes the directory and all it contents. * @param dir * @throws Exception */ public static void deleteDirectoryStructure(File dir) throws Exception{ deleteDirectoryStructure(dir, true); } /** * Deletes the directory's contents, but does not delete the directory. * @param dir * @throws Exception */ public static void clearDirectoryStructure(File dir) throws Exception{ deleteDirectoryStructure(dir, false); } private static void deleteDirectoryStructure(File dir, boolean deleteDir) throws Exception{ File subFiles[] = dir.listFiles(); for (File f : subFiles) { if (f.isDirectory()) deleteDirectoryStructure(f,true); else if (!f.delete()) { throw new Exception("Cannot delete file " + f.getAbsolutePath()); } } if (deleteDir) { if (!dir.delete()) { throw new Exception("Cannot delete folder " + dir.getAbsolutePath()); } } } public static Resource createFileReference(final Resource parent, final Path path) throws DatabaseException { return Simantics.getSession().syncRequest(new WriteResultRequest() { @Override public Resource perform(WriteGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); GraphFileResource GF = GraphFileResource.getInstance(graph); Resource file = graph.newResource(); graph.claim(file, L0.PartOf, parent); graph.claim(file, L0.InstanceOf, GF.File); String name = path.getFileName().toString(); graph.claimLiteral(file, L0.HasName, name, Bindings.STRING); graph.claimLiteral(file, GF.SystemPath, path.toAbsolutePath().toString(), Bindings.STRING); return file; } }); } /** * SCL java binding cannot make difference between methods that have two versions, one with Read/WriteGraph, and one without. * Hence we have to introduce a set of methods which have no alternate versions. */ public static void writeDataToGraphSCLhack(WriteGraph graph, File file, Resource graphFile) throws IOException, DatabaseException{ writeDataToGraph(graph, file, graphFile); } public static File toTempFileSCLhack(ReadGraph graph, Resource res)throws DatabaseException { return toTempFile(graph, res); } public static void writeDataToFileSCLhack(ReadGraph graph, Resource res, File file) throws DatabaseException, IOException { writeDataToFile(graph, res, file); } public static void toGraphSCLhack(WriteGraph graph, String filename, Resource graphFile) throws DatabaseException, IOException { toGraph(graph, filename, graphFile); } public static byte[] getData(ReadGraph graph, Resource graphFile) throws DatabaseException{ GraphFileResource GF = GraphFileResource.getInstance(graph); Resource fileData = graph.getSingleObject(graphFile, GF.HasFiledata); TransferableGraphSupport tgs = graph.getService(TransferableGraphSupport.class); InputStream input = tgs.getValueStream(graph, fileData); DataInputStream di = new DataInputStream(input); try { int length = di.readInt(); byte[] content = new byte[length]; di.read(content); return content; } catch (IOException e) { Logger.defaultLogError(e); } return null; } public static String getDataAsString(ReadGraph graph, Resource graphFile) throws DatabaseException{ byte[] content = getData(graph, graphFile); String s = new String(content); return s; } }