/*
* 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.jackrabbit.core.persistence.pool;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import org.apache.jackrabbit.core.util.StringIndex;
import org.apache.jackrabbit.core.util.db.ConnectionHelper;
import org.apache.jackrabbit.core.util.db.DbUtility;
/**
* Implements a {@link StringIndex} that stores and retrieves the names from a
* table in a database.
* <p>
* Note that this class is not threadsafe by itself. it needs to be synchronized
* by the using application.
* <p>
* Due to a bug with oracle that treats empty strings a null values
* (see JCR-815), all empty strings are replaced by a ' '. since names never
* start with a space, this it not problematic yet.
*/
public class DbNameIndex implements StringIndex {
protected final ConnectionHelper conHelper;
// name index statements
protected String nameSelectSQL;
protected String indexSelectSQL;
protected String nameInsertSQL;
// caches
private final HashMap<String, Integer> string2Index = new HashMap<String, Integer>();
private final HashMap<Integer, String> index2String = new HashMap<Integer, String>();
/**
* Creates a new index that is stored in a db.
* @param conHlpr the {@link ConnectionHelper}
* @param schemaObjectPrefix the prefix for table names
* @throws SQLException if the statements cannot be prepared.
*/
public DbNameIndex(ConnectionHelper conHlpr, String schemaObjectPrefix)
throws SQLException {
conHelper = conHlpr;
init(schemaObjectPrefix);
}
/**
* Inits this index and prepares the statements.
*
* @param schemaObjectPrefix the prefix for table names
* @throws SQLException if the statements cannot be prepared.
*/
protected void init(String schemaObjectPrefix)
throws SQLException {
nameSelectSQL = "select NAME from " + schemaObjectPrefix + "NAMES where ID = ?";
indexSelectSQL = "select ID from " + schemaObjectPrefix + "NAMES where NAME = ?";
nameInsertSQL = "insert into " + schemaObjectPrefix + "NAMES (NAME) values (?)";
}
/**
* Closes this index and releases it's resources.
*/
public void close() {
// closing the database resources is done by the owning
// BundleDbPersistenceManager that created this index
}
/**
* {@inheritDoc}
*/
public int stringToIndex(String string) {
// check cache
Integer index = string2Index.get(string);
if (index == null) {
String dbString = string.length() == 0 ? " " : string;
int idx = getIndex(dbString);
if (idx == -1) {
idx = insertString(dbString);
}
index = Integer.valueOf(idx);
string2Index.put(string, index);
index2String.put(index, string);
return idx;
} else {
return index.intValue();
}
}
/**
* {@inheritDoc}
*/
public String indexToString(int idx) throws IllegalArgumentException {
// check cache
Integer index = Integer.valueOf(idx);
String s = index2String.get(index);
if (s == null) {
s = getString(idx);
if (s.equals(" ")) {
s = "";
}
index2String.put(index, s);
string2Index.put(s, index);
}
return s;
}
/**
* Inserts a string into the database and returns the new index.
*
* @param string the string to insert
* @return the new index.
*/
protected int insertString(String string) {
// assert index does not exist
int result = -1;
ResultSet rs = null;
try {
rs = conHelper.exec(nameInsertSQL, new Object[] { string }, true, 0);
if (rs.next()) {
result = rs.getInt(1);
}
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException(
"Unable to insert index for string: " + string);
ise.initCause(e);
throw ise;
} finally {
DbUtility.close(rs);
}
if (result != -1) {
return result;
} else {
// Could not get the index with getGeneratedKeys, try with SELECT
return getIndex(string);
}
}
/**
* Retrieves the index from the database for the given string.
* @param string the string to retrieve the index for
* @return the index or -1 if not found.
*/
protected int getIndex(String string) {
ResultSet rs = null;
try {
rs = conHelper.exec(indexSelectSQL, new Object[] { string }, false, 0);
if (rs.next()) {
return rs.getInt(1);
} else {
return -1;
}
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException(
"Unable to read index for string: " + string);
ise.initCause(e);
throw ise;
} finally {
DbUtility.close(rs);
}
}
/**
* Retrieves the string from the database for the given index.
* @param index the index to retrieve the string for.
* @return the string
* @throws IllegalArgumentException if the string is not found
*/
protected String getString(int index)
throws IllegalArgumentException, IllegalStateException {
String result = null;
ResultSet rs = null;
try {
rs = conHelper.exec(nameSelectSQL, new Object[] { Integer.valueOf(index) }, false, 0);
if (rs.next()) {
result = rs.getString(1);
}
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException(
"Unable to read name for index: " + index);
ise.initCause(e);
throw ise;
} finally {
DbUtility.close(rs);
}
if (result == null) {
throw new IllegalArgumentException("Index not found: " + index);
}
return result;
}
}