/*
* 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.db;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.core.persistence.PMContext;
import org.apache.jackrabbit.core.persistence.util.Serializer;
import org.apache.jackrabbit.core.state.NodeReferences;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.util.db.ConnectionFactory;
import org.apache.jackrabbit.util.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.DatabaseMetaData;
import java.sql.Statement;
/**
* <code>OraclePersistenceManager</code> is a JDBC-based
* <code>PersistenceManager</code> for Jackrabbit that persists
* <code>ItemState</code> and <code>NodeReferences</code> objects in Oracle
* database using a simple custom serialization format and a
* very basic non-normalized database schema (in essence tables with one 'key'
* and one 'data' column).
* <p>
* It is configured through the following properties:
* <ul>
* <li><code>driver</code>: the FQN name of the JDBC driver class
* (default: <code>"oracle.jdbc.OracleDriver"</code>)</li>
* <li><code>schema</code>: type of schema to be used
* (default: <code>"oracle"</code>)</li>
* <li><code>url</code>: the database url (e.g.
* <code>"jdbc:oracle:thin:@[host]:[port]:[sid]"</code>)</li>
* <li><code>user</code>: the database user</li>
* <li><code>password</code>: the user's password</li>
* <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
* <li><code>tableSpace</code>: the tablespace to use</li>
* <li><code>externalBLOBs</code>: if <code>true</code> (the default) BINARY
* values (BLOBs) are stored in the local file system;
* if <code>false</code> BLOBs are stored in the database</li>
* </ul>
* See also {@link SimpleDbPersistenceManager}.
* <p>
* The following is a fragment from a sample configuration:
* <pre>
* <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.OraclePersistenceManager">
* <param name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/>
* <param name="user" value="scott"/>
* <param name="password" value="tiger"/>
* <param name="schemaObjectPrefix" value="${wsp.name}_"/>
* <param name="tableSpace" value=""/>
* <param name="externalBLOBs" value="false"/>
* </PersistenceManager>
* </pre>
*
* @deprecated Please migrate to a bundle persistence manager
* (<a href="https://issues.apache.org/jira/browse/JCR-2802">JCR-2802</a>)
*/
@Deprecated
public class OraclePersistenceManager extends SimpleDbPersistenceManager {
/**
* Logger instance
*/
private static Logger log = LoggerFactory.getLogger(OraclePersistenceManager.class);
private Class<?> blobClass;
private Integer durationSessionConstant;
private Integer modeReadWriteConstant;
/** the variable for the Oracle table space */
public static final String TABLE_SPACE_VARIABLE =
"${tableSpace}";
/** the Oracle table space to use */
protected String tableSpace;
/**
* Creates a new <code>OraclePersistenceManager</code> instance.
*/
public OraclePersistenceManager() {
// preset some attributes to reasonable defaults
schema = "oracle";
driver = "oracle.jdbc.OracleDriver";
schemaObjectPrefix = "";
user = "";
password = "";
initialized = false;
}
/**
* Returns the configured Oracle table space.
* @return the configured Oracle table space.
*/
public String getTableSpace() {
return tableSpace;
}
/**
* Sets the Oracle table space.
* @param tableSpace the Oracle table space.
*/
public void setTableSpace(String tableSpace) {
if (tableSpace != null) {
this.tableSpace = tableSpace.trim();
} else {
this.tableSpace = null;
}
}
//---------------------------------< SimpleDbPersistenceManager overrides >
/**
* {@inheritDoc}
* <p>
* Retrieve the <code>oracle.sql.BLOB</code> class via reflection, and
* initialize the values for the <code>DURATION_SESSION</code> and
* <code>MODE_READWRITE</code> constants defined there.
*/
public void init(PMContext context) throws Exception {
super.init(context);
if (!externalBLOBs) {
blobStore = new OracleBLOBStore();
}
// initialize oracle.sql.BLOB class & constants
// use the Connection object for using the exact same
// class loader that the Oracle driver was loaded with
blobClass = con.getClass().getClassLoader().loadClass("oracle.sql.BLOB");
durationSessionConstant =
new Integer(blobClass.getField("DURATION_SESSION").getInt(null));
modeReadWriteConstant =
new Integer(blobClass.getField("MODE_READWRITE").getInt(null));
}
/**
* {@inheritDoc}
*/
public synchronized void store(NodeState state) throws ItemStateException {
if (!initialized) {
throw new IllegalStateException("not initialized");
}
// check if insert or update
boolean update = state.getStatus() != ItemState.STATUS_NEW;
//boolean update = exists((NodeId) state.getId());
String sql = (update) ? nodeStateUpdateSQL : nodeStateInsertSQL;
Blob blob = null;
try {
ByteArrayOutputStream out =
new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
// serialize node state
Serializer.serialize(state, out);
// we are synchronized on this instance, therefore we do not
// not have to additionally synchronize on the sql statement
blob = createTemporaryBlob(new ByteArrayInputStream(out.toByteArray()));
executeStmt(sql, new Object[]{blob, state.getNodeId().toString()});
// there's no need to close a ByteArrayOutputStream
//out.close();
} catch (Exception e) {
String msg = "failed to write node state: " + state.getId();
log.error(msg, e);
throw new ItemStateException(msg, e);
} finally {
if (blob != null) {
try {
freeTemporaryBlob(blob);
} catch (Exception ignore) {
}
}
}
}
/**
* {@inheritDoc}
*/
public synchronized void store(PropertyState state) throws ItemStateException {
if (!initialized) {
throw new IllegalStateException("not initialized");
}
// check if insert or update
boolean update = state.getStatus() != ItemState.STATUS_NEW;
//boolean update = exists((PropertyId) state.getId());
String sql = (update) ? propertyStateUpdateSQL : propertyStateInsertSQL;
Blob blob = null;
try {
ByteArrayOutputStream out =
new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
// serialize property state
Serializer.serialize(state, out, blobStore);
// we are synchronized on this instance, therefore we do not
// not have to additionally synchronize on the sql statement
blob = createTemporaryBlob(new ByteArrayInputStream(out.toByteArray()));
executeStmt(sql, new Object[]{blob, state.getPropertyId().toString()});
// there's no need to close a ByteArrayOutputStream
//out.close();
} catch (Exception e) {
String msg = "failed to write property state: " + state.getId();
log.error(msg, e);
throw new ItemStateException(msg, e);
} finally {
if (blob != null) {
try {
freeTemporaryBlob(blob);
} catch (Exception ignore) {
}
}
}
}
/**
* {@inheritDoc}
*/
public synchronized void store(NodeReferences refs) throws ItemStateException {
if (!initialized) {
throw new IllegalStateException("not initialized");
}
// check if insert or update
boolean update = existsReferencesTo(refs.getTargetId());
String sql = (update) ? nodeReferenceUpdateSQL : nodeReferenceInsertSQL;
Blob blob = null;
try {
ByteArrayOutputStream out =
new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
// serialize references
Serializer.serialize(refs, out);
// we are synchronized on this instance, therefore we do not
// not have to additionally synchronize on the sql statement
blob = createTemporaryBlob(new ByteArrayInputStream(out.toByteArray()));
executeStmt(sql, new Object[]{blob, refs.getTargetId().toString()});
// there's no need to close a ByteArrayOutputStream
//out.close();
} catch (Exception e) {
String msg = "failed to write " + refs;
log.error(msg, e);
throw new ItemStateException(msg, e);
} finally {
if (blob != null) {
try {
freeTemporaryBlob(blob);
} catch (Exception ignore) {
}
}
}
}
/**
* {@inheritDoc}
* <p>
* Overridden in order to support multiple oracle schemas. Note that
* schema names in Oracle correspond to the username of the connection.
* See http://issues.apache.org/jira/browse/JCR-582
*
* @throws Exception if an error occurs
*/
protected void checkSchema() throws Exception {
DatabaseMetaData metaData = con.getMetaData();
String tableName = schemaObjectPrefix + "NODE";
if (metaData.storesLowerCaseIdentifiers()) {
tableName = tableName.toLowerCase();
} else if (metaData.storesUpperCaseIdentifiers()) {
tableName = tableName.toUpperCase();
}
String userName = metaData.getUserName();
ResultSet rs = metaData.getTables(null, userName, tableName, null);
boolean schemaExists;
try {
schemaExists = rs.next();
} finally {
rs.close();
}
if (!schemaExists) {
// read ddl from resources
InputStream in = getSchemaDDL();
if (in == null) {
String msg = "Configuration error: unknown schema '" + schema + "'";
log.debug(msg);
throw new RepositoryException(msg);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Statement stmt = con.createStatement();
try {
String sql = reader.readLine();
while (sql != null) {
// Skip comments and empty lines
if (!sql.startsWith("#") && sql.length() > 0) {
// replace prefix variable
sql = Text.replace(sql, SCHEMA_OBJECT_PREFIX_VARIABLE, schemaObjectPrefix);
// set the tablespace if it is defined
String tspace;
if (tableSpace == null || "".equals(tableSpace)) {
tspace = "";
} else {
tspace = "tablespace " + tableSpace;
}
sql = Text.replace(sql, TABLE_SPACE_VARIABLE, tspace).trim();
// execute sql stmt
stmt.executeUpdate(sql);
}
// read next sql stmt
sql = reader.readLine();
}
// commit the changes
con.commit();
} finally {
IOUtils.closeQuietly(in);
closeStatement(stmt);
}
}
}
//----------------------------------------< oracle-specific blob handling >
/**
* Creates a temporary oracle.sql.BLOB instance via reflection and spools
* the contents of the specified stream.
*/
protected Blob createTemporaryBlob(InputStream in) throws Exception {
/*
BLOB blob = BLOB.createTemporary(con, false, BLOB.DURATION_SESSION);
blob.open(BLOB.MODE_READWRITE);
OutputStream out = blob.getBinaryOutputStream();
...
out.flush();
out.close();
blob.close();
return blob;
*/
Method createTemporary = blobClass.getMethod("createTemporary",
new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE});
Object blob = createTemporary.invoke(null, new Object[]{
ConnectionFactory.unwrap(con),
Boolean.FALSE, durationSessionConstant });
Method open = blobClass.getMethod("open", new Class[]{Integer.TYPE});
open.invoke(blob, new Object[]{modeReadWriteConstant});
Method getBinaryOutputStream =
blobClass.getMethod("getBinaryOutputStream", new Class[0]);
OutputStream out = (OutputStream) getBinaryOutputStream.invoke(blob);
try {
IOUtils.copy(in, out);
} finally {
try {
out.flush();
} catch (IOException ioe) {
}
out.close();
}
Method close = blobClass.getMethod("close", new Class[0]);
close.invoke(blob);
return (Blob) blob;
}
/**
* Frees a temporary oracle.sql.BLOB instance via reflection.
*/
protected void freeTemporaryBlob(Object blob) throws Exception {
// blob.freeTemporary();
Method freeTemporary = blobClass.getMethod("freeTemporary", new Class[0]);
freeTemporary.invoke(blob);
}
//--------------------------------------------------------< inner classes >
class OracleBLOBStore extends DbBLOBStore {
/**
* {@inheritDoc}
*/
public synchronized void put(String blobId, InputStream in, long size)
throws Exception {
Statement stmt = executeStmt(blobSelectExistSQL, new Object[]{blobId});
ResultSet rs = stmt.getResultSet();
// a BLOB exists if the result has at least one entry
boolean exists = rs.next();
closeResultSet(rs);
Blob blob = null;
try {
String sql = (exists) ? blobUpdateSQL : blobInsertSQL;
blob = createTemporaryBlob(in);
executeStmt(sql, new Object[]{blob, blobId});
} finally {
if (blob != null) {
try {
freeTemporaryBlob(blob);
} catch (Exception ignore) {
}
}
}
}
}
}