/* * 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.sdb.store; import java.io.StringReader; import java.io.StringWriter; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.jena.rdf.model.Model ; import org.apache.jena.rdf.model.ModelFactory ; import org.apache.jena.rdf.model.Resource ; import org.apache.jena.rdf.model.ResourceFactory ; import org.apache.jena.sdb.sql.* ; import org.apache.jena.sparql.util.graph.GraphUtils ; import org.apache.jena.util.FileUtils ; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // Refactor // Name->BlobBytes class // Need to make (more) DB independent // Prepared statements /** * A table that stores small models for small configuration information. * Stores an N-TRIPLES (robust to charsets) graph. * * The objective here is not efficiency - it's stability of design because * this may record version and layout configuration details so it needs to * be a design that will not change. */ public class StoreConfig extends SDBConnectionHolder { private static Logger log = LoggerFactory.getLogger(StoreConfig.class) ; private static final String serializationFormat = FileUtils.langNTriple ; public static final String defaultTag = "config" ; private boolean initialized = false ; private Map<String, Model> cache = new HashMap<String, Model>() ; private boolean caching = true ; TaggedString storage = null ; private Resource rootType = ResourceFactory.createResource() ; public StoreConfig(SDBConnection sdb) { super(sdb) ; storage = new TaggedString(connection()) ; } /** Get the real tables in the database, not what any configuration information may think */ public List<String> tables() { return TableUtils.getTableNames(connection().getSqlConnection()) ; } private Resource getRoot() { Model model = getModel() ; return GraphUtils.getResourceByType(model, ConfigVocab.typeConfig) ; } public void removeModel() { removeModel(defaultTag) ; } public void removeModel(String tag) { init() ; log.trace(".removeModel: "+tag) ; storage.remove(tag) ; } public Model getModel() { return getModel(defaultTag) ; } public Model getModel(String tag) { init() ; log.trace(".getModel: "+tag) ; Model m = null ; if ( caching && cache.containsKey(tag) ) { log.trace(".getModel: cache hit for "+tag) ; return cache.get(tag) ; } log.trace(".getModel: cache miss for "+tag) ; m = readModel(tag) ; if ( m == null ) return null ; if ( caching ) cache.put(tag, m) ; return m ; } public void setModel(Model m) { setModel(defaultTag, m) ; } public void setModel(String tag, Model m) { init() ; log.trace(".setModel: "+tag) ; // Write before caching. writeModel(tag, m) ; if ( caching ) { log.trace(".setModel: cache model for "+tag) ; cache.put(tag, m) ; } } public List<String> getTags() { init() ; return storage.tags() ; } public void reset() { storage.reset() ; } //public void flush() { writeConfigModel() ; } private void init() { if ( initialized ) return ; initialized = true ; if ( storage != null ) return ; // LoggerFactory.getLogger(this.getClass()).warn("TESTING: config storage reset") ; // storage.reset() ; } private Model readModel(String tag) { log.trace(".readModel: "+tag) ; String s = storage.get(tag) ; if ( s == null ) return null ; Model m = ModelFactory.createDefaultModel() ; StringReader r = new StringReader(s) ; m.read(r, null, serializationFormat) ; return m ; } private void writeModel(String tag, Model model) { log.trace(".writeModel: "+tag) ; StringWriter x = new StringWriter() ; model.write(x, serializationFormat) ; storage.set(tag, x.toString()); } } class TaggedString extends SDBConnectionHolder { static final String stringTableName = "Strings" ; static final String columnName = "tag" ; static final String columnData = "data" ; private boolean initialized = false ; TaggedString(SDBConnection sdb) { super(sdb) ; } void reset() { // MySQL. //final String sqlStmt1 = "DROP TABLE IF EXISTS "+stringTableName+" ;" ; final String sqlStmt1 = SQLUtils.sqlStr("DROP TABLE "+stringTableName) ; final String sqlStmt2 = SQLUtils.sqlStr( // Should be good for all databases // TODO Use TEXT? Moderately universal. "CREATE TABLE "+stringTableName, "( "+columnName+" VARCHAR(200) NOT NULL,", " "+columnData+" VARCHAR(20000) NOT NULL ,", " PRIMARY KEY("+columnName+")", ")") ; try { if ( TableUtils.hasTable(connection().getSqlConnection(), stringTableName) ) connection().execUpdate(sqlStmt1); connection().execUpdate(sqlStmt2); } catch (SQLException ex) { throw new SDBExceptionSQL("NamedString.reset", ex) ; } } private void init() { if ( initialized ) return ; // TODO prepare statements } List<String> tags() { ResultSetJDBC rs = null ; try { final String sqlStmt = SQLUtils.sqlStr( "SELECT "+columnName, "FROM "+stringTableName) ; rs = connection().execQuery(sqlStmt) ; List<String> tags = new ArrayList<String>() ; while ( rs.get().next() ) { String x = rs.get().getString(columnName) ; tags.add(x) ; } return tags ; } catch (SQLException ex) { throw new SDBExceptionSQL("getString", ex) ; } finally { RS.close(rs) ; } } void remove(String tag) { try { connection().exec("DELETE FROM "+stringTableName+" WHERE "+columnName+"="+SQLUtils.quoteStr(tag)) ; } catch (SQLException ex) { throw new SDBExceptionSQL(ex) ; } } void set(String tag, String value) { remove(tag) ; value = encode(value) ; try { connection().exec("INSERT INTO "+stringTableName+" VALUES ("+SQLUtils.quoteStr(tag)+", "+SQLUtils.quoteStr(value)+")") ; } catch (SQLException ex) { throw new SDBExceptionSQL("set", ex) ; } } String get(String tag) { boolean b = connection().loggingSQLExceptions() ; connection().setLogSQLExceptions(false) ; ResultSetJDBC rs = null ; try { final String sqlStmt = SQLUtils.sqlStr( "SELECT "+columnData, "FROM "+stringTableName, "WHERE "+columnName+" = "+SQLUtils.quoteStr(tag)) ; rs = connection().execQuery(sqlStmt) ; if ( ! rs.get().next() ) // No row. return null ; String x = rs.get().getString(columnData) ; return decode(x) ; } catch (SQLException ex) { //throw new SDBExceptionSQL("getString", ex) ; return null ; } finally { connection().setLogSQLExceptions(b) ; RS.close(rs) ; } } // Escape non-7bit bytes. e.g. \ u stuff. // Not needed when using N-TRIPLES private String encode(String s) { return s ; } private String decode(String s) { return s ; } // private byte[] encode(String str) // { // // CharsetEncoder // // return byte[] // if ( str.indexOf('"') != -1 ) // str = str.replace("\"", "\\\"") ; // Charset cs = Charset.forName("utf8") ; // byte[] b = cs.encode(str).array() ; // return b ; // } // // private String decode(byte[] b) // { // Charset cs = Charset.forName("utf8") ; // ByteBuffer bb = ByteBuffer.wrap(b) ; // String str = new String(cs.decode(bb).array()) ; // if ( str.indexOf('\\') == -1 ) // return str ; // return str.replace("\\\"", "\"") ; // } }