/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.tdb.base.file; import java.io.ByteArrayOutputStream ; import java.io.File ; import java.io.FileNotFoundException ; import java.io.IOException ; import java.io.InputStream ; import java.io.PrintStream ; import java.util.Comparator ; import java.util.Objects; import java.util.Properties ; import java.util.SortedSet ; import java.util.TreeSet ; import org.apache.jena.atlas.lib.* ; import org.apache.jena.tdb.TDBException ; import org.apache.jena.tdb.sys.Names ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; /** Abstraction and many convenience operations on metadata. * Metadata is recorded in Java properties style - not RDF - because it's relative to the file or context used. * Keys and values are always strings. */ public class MetaFile implements Sync, Closeable { private static Comparator<String> comparator = new ComparatorKeys() ; private static Logger log = LoggerFactory.getLogger(MetaFile.class) ; private String metaFilename = null ; private Properties properties = null ; private String label = null ; private boolean changed = false ; private boolean closed = false ; /** Create a MetaFile * * @param label Convenience label. * @param fn On disk filename @link{Names.mem} for in-memory */ public MetaFile(String label, String fn) { this.label = label ; this.metaFilename = fn ; if ( fn == null || Names.isMem(fn) ) // In-memory. return ; // Make absolute (current directory may change later) if ( ! fn.endsWith(Names.extMeta) ) fn = fn+"."+Names.extMeta ; File f = new File(fn) ; this.metaFilename = f.getAbsolutePath() ; // Does not load the details yet. // JDI ensureInit() ; } private void ensureInit() { if ( properties == null ) { properties = new PropertiesSorted(comparator) ; if ( metaFilename != null ) loadProperties() ; } } /** Does this metafile exist on disk? (In-memory MetaFiles always exist) */ public boolean existsMetaData() { if ( isMem() ) return true ; File f = new File(metaFilename) ; if ( f.isDirectory() ) log.warn("Metadata file clashes with a directory") ; return f.exists() && f.isFile() ; } public String getFilename() { return metaFilename ; } /** Test for the presence of a property */ public boolean hasProperty(String key) { return _getProperty(key, null) != null ; } /** Get the property value or null. */ public String getProperty(String key) { return _getProperty(key, null) ; } /** Get the property value or return supplied default. */ public String getProperty(String key, String defaultString) { return _getProperty(key, defaultString) ; } /** Get the property value and parse as an integer */ public int getPropertyAsInteger(String key) { return Integer.parseInt(_getProperty(key, null)) ; } /** Get the property value and parse as an integer or return default value. */ public int getPropertyAsInteger(String key, int defaultValue) { String x = getProperty(key) ; if ( x == null ) return defaultValue ; return Integer.parseInt(x) ; } /** Get property as a string and split on ",". */ public String[] getPropertySplit(String key) { String str = getProperty(key) ; if ( str == null ) return null ; return str.split(",") ; } /** Get property as a string and split on ",", using the default string if not present in the MetaFile. */ public String[] getPropertySplit(String key, String defaultString) { String str = getProperty(key, defaultString) ; return str.split(",") ; } /** Set property */ public void setProperty(String key, String value) { _setProperty(key, value) ; } /** Set property, turning integer into a string. */ public void setProperty(String key, int value) { _setProperty(key, Integer.toString(value)) ; } /** Test whether a property has a value. Null tests equal to not present. */ public boolean propertyEquals(String key, String value) { return Objects.equals(getProperty(key), value) ; } /** Set property if not already set. */ public void ensurePropertySet(String key, String expected) { getOrSetDefault(key, expected) ; } /** Get property or the default value - also set the default value if not present */ public String getOrSetDefault(String key, String expected) { String x = getProperty(key) ; if ( x == null ) { setProperty(key, expected) ; x = expected ; } return x ; } /** Check property is an expected value or set if missing */ public void checkOrSetMetadata(String key, String expected) { String x = getProperty(key) ; if ( x == null ) { setProperty(key, expected) ; return ; } if ( x.equals(expected) ) return ; inconsistent(key, x, expected) ; } /** Check property has the value given - throw exception if not. */ public void checkMetadata(String key, String expected) { String value = getProperty(key) ; if ( ! Objects.equals(value, value) ) inconsistent(key, value, expected) ; } private static void inconsistent(String key, String actual, String expected) { String msg = String.format("Inconsistent: key=%s value=%s expected=%s", key, (actual==null?"<null>":actual), (expected==null?"<null>":expected) ) ; throw new MetaFileException(msg) ; } /** Clear all properties. */ public void clear() { _clear() ; } // ---- All get/set access through these operations private String _getProperty(String key, String dft) { ensureInit() ; return properties.getProperty(key, dft) ; } private void _setProperty(String key, String value) { ensureInit() ; properties.setProperty(key, value) ; changedEvent() ; } /** Clear all properties. */ private void _clear() { ensureInit() ; properties.clear() ; changedEvent() ; } private void changedEvent() { changed = true ; } // ---- private boolean isMem() { return Names.isMem(metaFilename) ; } /** Write to backing file if changed */ public void flush() { if ( log.isDebugEnabled() ) log.debug("Flush metadata ("+changed+"): "+this.label) ; if ( ! changed ) return ; if ( log.isDebugEnabled() ) { ByteArrayOutputStream out = new ByteArrayOutputStream() ; PrintStream ps = new PrintStream(out) ; properties.list(ps) ; ps.flush() ; log.debug("\n"+out.toString()) ; } //properties.list(System.out) ; saveProperties() ; changed = false ; } private void saveProperties() { if ( isMem() ) return ; String str = label ; if ( str == null ) str = metaFilename ; str = "Metadata: "+str ; try { PropertyUtils.storeToFile(properties, str, metaFilename) ; } catch (IOException ex) { log.error("Failed to store properties: "+metaFilename, ex) ; } } private void loadProperties() { if ( isMem() ) { properties = new Properties() ; return ; } if ( properties == null ) properties = new Properties() ; // if ( metaFilename == null ) InputStream in = null ; try { // Copes with UTF-8 for Java5. PropertyUtils.loadFromFile(properties, metaFilename) ; } catch (FileNotFoundException ex) {} catch (IOException ex) { log.error("Failed to load properties: "+metaFilename, ex) ; } } /** Debugging */ public void dump(PrintStream output) { output.println("Metafile: "+metaFilename) ; output.println("Label: "+label) ; output.println("Status: "+(changed?"changed":"unchanged")) ; if ( properties == null ) { output.println("#<null>") ; return ; } // properties.list() ; SortedSet<Object> x = new TreeSet<>() ; x.addAll(properties.keySet()) ; for ( Object k : x ) { String key = (String)k ; String value = properties.getProperty(key) ; output.print(key) ; output.print("=") ; output.print(value) ; output.println() ; } } @Override public void sync() { flush() ; } @Override public void close() { flush() ; closed = true ; metaFilename = null ; properties = null ; } private static class ComparatorKeys implements Comparator<String> { @Override public int compare(String o1, String o2) { return - o1.compareTo(o2) ; } } private static class MetaFileException extends TDBException { MetaFileException(String msg) { super(msg) ; } } }