/******************************************************************************* * Copyright (c) 2007, 2011 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.history.impl; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.simantics.databoard.Accessors; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.ArrayAccessor; import org.simantics.databoard.accessor.StreamAccessor; import org.simantics.databoard.accessor.error.AccessorConstructionException; import org.simantics.databoard.accessor.error.AccessorException; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.adapter.Adapter; import org.simantics.databoard.adapter.AdapterConstructionException; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingConstructionException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.serialization.SerializerConstructionException; import org.simantics.databoard.type.ArrayType; import org.simantics.databoard.type.Component; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.NumberType; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.util.Bean; import org.simantics.databoard.util.ObjectUtils; import org.simantics.databoard.util.URIUtil; import org.simantics.databoard.util.binary.BinaryMemory; import org.simantics.databoard.util.binary.ByteBufferWriteable; import org.simantics.history.HistoryException; import org.simantics.history.HistoryManager; import org.simantics.utils.FileUtils; /** * File history uses workarea (directory) to manage items. * There are two files for every item: stream file and metadata file. * Metadata file is JSON ascii file. * itemname.data * itemname.txt * * @author toni.kalajainen * */ public class FileHistory implements HistoryManager { private static final boolean PROFILE = false; private static final boolean DEBUG = false; /** Logger */ static Logger logger = Logger.getLogger(FileHistory.class.getName()); static FilenameFilter txtFilter; static ArrayType INDEX_TYPE = Bindings.LONG_ARRAY.type(); File workarea; /** Support async usage of the data */ public boolean asyncUsage = true; public FileHistory(File workarea) { this.workarea = workarea; } public File getWorkarea() { return workarea; } @Override public void create(Bean... items) throws HistoryException { try { for (Bean item : items) { if (DEBUG) System.out.println("create(" + item + ")"); // if ( !item.getFieldBinding("format").type().equals( Bindings.getBindingUnchecked(Datatype.class).type() ) ) // System.err.println("Error"); // Write meta data writeMetadata( item ); // Create stream file String id = (String) item.getField("id"); File dataFile = toDataFile( id ); dataFile.createNewFile(); Datatype type = (Datatype) item.getField("format"); if ( isVariableWidth(type) ) { File indexFile = toIndexFile( id ); indexFile.createNewFile(); } // if ( !dataFile.createNewFile() ) { // throw new HistoryException("Could not create file "+dataFile); // } } } catch (BindingException e) { throw new HistoryException(e); } catch (IOException e) { throw new HistoryException(e); } } @Override public void delete(String... itemIds) throws HistoryException { for (String itemId : itemIds ) { File meta = toMetaFile( itemId ); File data = toDataFile( itemId ); File index = toIndexFile( itemId ); if ( meta.exists() ) { if ( !meta.delete() ) { throw new HistoryException("Failed to delete "+meta); } } if ( data.exists() ) { if ( !data.delete() ) { throw new HistoryException("Failed to delete "+data); } } if ( index.exists() ) { if ( !index.delete() ); } } } @Override public void modify(Bean... items) throws HistoryException { try { for ( Bean item : items ) { if (DEBUG) System.out.println("modify(" + item + ")"); // if ( !item.getFieldBinding("format").type().equals( Bindings.getBindingUnchecked(Datatype.class).type() ) ) // System.err.println("Error"); String id = (String) item.getField("id"); File metaFile = toMetaFile( id ); if ( !metaFile.exists() ) { create( item ); } else { Bean oldItem = getItem( id ); File dataFile = toDataFile( id ); if ( dataFile.exists() ) { boolean enabled = item.hasField("enabled") ? (Boolean) item.getFieldUnchecked("enabled") : true; Datatype oldFormat = (Datatype) oldItem.getField( "format" ); Datatype newFormat = (Datatype) item.getField( "format" ); if (DEBUG) System.out.println("formats: " + oldFormat + " -> " + newFormat); Datatype unitStrippedOldFormat = stripUnitAnnotations(oldFormat); Datatype unitStrippedNewFormat = stripUnitAnnotations(newFormat); if (DEBUG) System.out.println("formats after unit strip: " + unitStrippedOldFormat + " -> " + unitStrippedNewFormat); if ( enabled && !unitStrippedOldFormat.equals(unitStrippedNewFormat) ) { try { Binding oldBinding = Bindings.getBeanBinding(unitStrippedOldFormat); Binding newBinding = Bindings.getBeanBinding(unitStrippedNewFormat); Serializer oldS = Bindings.getSerializer(oldBinding); Serializer newS = Bindings.getSerializer(newBinding); if (oldS.getConstantSize()==null || newS.getConstantSize()==null || oldS.getConstantSize()!=newS.getConstantSize()) throw new HistoryException("Changing of file format is not supported to: "+dataFile); Adapter adapter = Bindings.getAdapter(oldBinding, newBinding); Object oldSample = oldBinding.createDefault(); Object newSample = newBinding.createDefault(); StreamAccessor sa = openStream(id, "rw"); try { int c = sa.size(); for (int i=0; i result = new ArrayList(); File[] files = workarea.listFiles(txtFilter); if ( files != null ) { for (File file : files) { result.add( getItem(file) ); } } return result.toArray( new Bean[ result.size() ] ); } @Override public void close() { // Nothing to do. } @Override public StreamAccessor openStream(String itemId, String mode) throws HistoryException { try { Bean bean = getItem(itemId); Datatype format = (Datatype) bean.getField("format"); ArrayType arrayType = new ArrayType(format); File dataFile = toDataFile( itemId ); if ( isVariableWidth(format) ) { File indexFile = toIndexFile( itemId ); ArrayAccessor index = Accessors.openStream(indexFile, INDEX_TYPE, mode); return (StreamAccessor) Accessors.openStream(dataFile, arrayType, mode, index); } else { return (StreamAccessor) Accessors.openStream(dataFile, arrayType, mode); } } catch (AccessorConstructionException e) { throw new HistoryException(e); } catch (BindingException e) { throw new HistoryException(e); } } @Override public boolean exists(String itemId) throws HistoryException { return toMetaFile(itemId).exists(); } @Override public int hashCode() { return workarea.hashCode(); } @Override public boolean equals(Object obj) { if ( obj==null ) return false; if ( obj instanceof FileHistory == false ) return false; FileHistory other = (FileHistory) obj; return other.workarea.equals(workarea); } @Override public String toString() { return "FileHistory: "+workarea; } private boolean equalsWithoutState(Bean i1, Bean i2) { Component[] components1 = i1.getBinding().type().getComponents(); Component[] components2 = i2.getBinding().type().getComponents(); int components = Math.min(components1.length, components2.length); for (int c = 0; c < components; ++c) { Object o1 = i1.getFieldUnchecked(c); Object o2 = i2.getFieldUnchecked(c); if ("collectorState".equals(components1[c].name) && (o1 == null || o2 == null)) continue; if (!ObjectUtils.objectEquals(o1, o2)) return false; } return true; } static { txtFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".txt"); } }; } private static Datatype stripUnitAnnotations(Datatype datatype) { if (datatype instanceof NumberType) { NumberType nt = (NumberType) datatype; if (nt.getUnit() != null) { Binding dtb = Bindings.getBindingUnchecked(Datatype.class); datatype = nt = (NumberType) Bindings.cloneUnchecked(datatype, dtb, dtb); nt.setUnit(null); } } else if (datatype instanceof ArrayType) { ArrayType at = (ArrayType) datatype; Datatype ct = at.componentType(); Datatype component = stripUnitAnnotations(ct); if (component != ct) { Binding dtb = Bindings.getBindingUnchecked(Datatype.class); datatype = at = (ArrayType) Bindings.cloneUnchecked(datatype, dtb, dtb); at.setComponentType(component); } } else if (datatype instanceof RecordType) { RecordType rt = (RecordType) datatype; int componentCount = rt.getComponentCount(); Component[] newComponents = new Component[componentCount]; for (int i = 0; i < componentCount; ++i) { Component c = rt.getComponent(i); Datatype ct = c.type; Datatype sct = stripUnitAnnotations(ct); newComponents[i] = new Component(c.name, sct); } return new RecordType(rt.isReferable(), newComponents); } return datatype; } }