Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/Oracle10R1FileSystem.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/Oracle10R1FileSystem.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/Oracle10R1FileSystem.java (revision 0) @@ -0,0 +1,345 @@ +/* + * 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.fs.db; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.lang.reflect.Method; +import java.sql.Blob; +import java.sql.Connection; + +import org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.core.fs.FileSystemException; +import org.apache.jackrabbit.core.fs.FileSystemPathUtil; +import org.apache.jackrabbit.core.fs.RandomAccessOutputStream; +import org.apache.jackrabbit.util.TransientFileFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@inheritDoc} + *

+ * Oracle10R1FileSystem works for Oracle database version up to, and including, 10 R1. + */ +public class Oracle10R1FileSystem extends OracleBaseFileSystem { + /** + * Logger instance + */ + private static Logger log = LoggerFactory.getLogger(Oracle10R1FileSystem.class); + + private Class blobClass; + private Integer durationSessionConstant; + private Integer modeReadWriteConstant; + + //-----------------------------------------< DatabaseFileSystem overrides > + /** + * {@inheritDoc} + *

+ * Retrieve the oracle.sql.BLOB class via reflection, and + * initialize the values for the DURATION_SESSION and + * MODE_READWRITE constants defined there. + * @see oracle.sql.BLOB#DURATION_SESSION + * @see oracle.sql.BLOB#MODE_READWRITE + */ + public void init() throws FileSystemException { + super.init(); + + // initialize oracle.sql.BLOB class & constants + + // use the Connection object for using the exact same + // class loader that the Oracle driver was loaded with + try { + 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)); + } catch (Exception e) { + String msg = "failed to load/introspect oracle.sql.BLOB"; + log.error(msg, e); + throw new FileSystemException(msg, e); + } + } + + /** + * {@inheritDoc} + *

+ * Overridden because we need to use oracle.sql.BLOB + * and PreparedStatement#setBlob instead of just + * PreparedStatement#setBinaryStream. + */ + public OutputStream getOutputStream(final String filePath) throws FileSystemException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + FileSystemPathUtil.checkFormat(filePath); + + final String parentDir = FileSystemPathUtil.getParentDir(filePath); + final String name = FileSystemPathUtil.getName(filePath); + + if (!isFolder(parentDir)) { + throw new FileSystemException("path not found: " + parentDir); + } + + if (isFolder(filePath)) { + throw new FileSystemException("path denotes folder: " + filePath); + } + + try { + TransientFileFactory fileFactory = TransientFileFactory.getInstance(); + final File tmpFile = fileFactory.createTransientFile("bin", null, null); + + return new FilterOutputStream(new FileOutputStream(tmpFile)) { + + public void close() throws IOException { + super.close(); + + InputStream in = null; + Blob blob = null; + try { + if (isFile(filePath)) { + synchronized (updateDataSQL) { + long length = tmpFile.length(); + in = new FileInputStream(tmpFile); + blob = createTemporaryBlob(in); + executeStmt(updateDataSQL, + new Object[]{ + blob, + new Long(System.currentTimeMillis()), + new Long(length), + parentDir, + name + }); + } + } else { + synchronized (insertFileSQL) { + long length = tmpFile.length(); + in = new FileInputStream(tmpFile); + blob = createTemporaryBlob(in); + executeStmt(insertFileSQL, + new Object[]{ + parentDir, + name, + blob, + new Long(System.currentTimeMillis()), + new Long(length) + }); + } + } + } catch (Exception e) { + IOException ioe = new IOException(e.getMessage()); + ioe.initCause(e); + throw ioe; + } finally { + if (blob != null) { + try { + freeTemporaryBlob(blob); + } catch (Exception e1) { + } + } + IOUtils.closeQuietly(in); + // temp file can now safely be removed + tmpFile.delete(); + } + } + }; + } catch (Exception e) { + String msg = "failed to open output stream to file: " + filePath; + log.error(msg, e); + throw new FileSystemException(msg, e); + } + } + + /** + * {@inheritDoc} + */ + public RandomAccessOutputStream getRandomAccessOutputStream( + final String filePath) + throws FileSystemException, UnsupportedOperationException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + FileSystemPathUtil.checkFormat(filePath); + + final String parentDir = FileSystemPathUtil.getParentDir(filePath); + final String name = FileSystemPathUtil.getName(filePath); + + if (!isFolder(parentDir)) { + throw new FileSystemException("path not found: " + parentDir); + } + + if (isFolder(filePath)) { + throw new FileSystemException("path denotes folder: " + filePath); + } + + try { + TransientFileFactory fileFactory = TransientFileFactory.getInstance(); + final File tmpFile = fileFactory.createTransientFile("bin", null, null); + + // @todo FIXME use java.sql.Blob + + if (isFile(filePath)) { + // file entry exists, spool contents to temp file first + InputStream in = getInputStream(filePath); + OutputStream out = new FileOutputStream(tmpFile); + try { + IOUtils.copy(in, out); + } finally { + out.close(); + in.close(); + } + } + + return new RandomAccessOutputStream() { + private final RandomAccessFile raf = + new RandomAccessFile(tmpFile, "rw"); + + public void close() throws IOException { + raf.close(); + + InputStream in = null; + Blob blob = null; + try { + if (isFile(filePath)) { + synchronized (updateDataSQL) { + long length = tmpFile.length(); + in = new FileInputStream(tmpFile); + blob = createTemporaryBlob(in); + executeStmt(updateDataSQL, + new Object[]{ + blob, + new Long(System.currentTimeMillis()), + new Long(length), + parentDir, + name + }); + } + } else { + synchronized (insertFileSQL) { + long length = tmpFile.length(); + in = new FileInputStream(tmpFile); + blob = createTemporaryBlob(in); + executeStmt(insertFileSQL, + new Object[]{ + parentDir, + name, + blob, + new Long(System.currentTimeMillis()), + new Long(length) + }); + } + } + } catch (Exception e) { + IOException ioe = new IOException(e.getMessage()); + ioe.initCause(e); + throw ioe; + } finally { + if (blob != null) { + try { + freeTemporaryBlob(blob); + } catch (Exception e1) { + } + } + IOUtils.closeQuietly(in); + // temp file can now safely be removed + tmpFile.delete(); + } + } + + public void seek(long position) throws IOException { + raf.seek(position); + } + + public void write(int b) throws IOException { + raf.write(b); + } + + public void flush() /*throws IOException*/ { + // nop + } + + public void write(byte[] b) throws IOException { + raf.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + raf.write(b, off, len); + } + }; + } catch (Exception e) { + String msg = "failed to open output stream to file: " + filePath; + log.error(msg, e); + throw new FileSystemException(msg, e); + } + } + + //----------------------------------------< 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[]{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, null); + 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, null); + 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, null); + } +} Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleBaseFileSystem.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleBaseFileSystem.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleBaseFileSystem.java (revision 0) @@ -0,0 +1,282 @@ +/* + * 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.fs.db; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.Statement; + +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OracleBaseFileSystem is a JDBC-based FileSystem + * base implementation for Jackrabbit that persists file system entries in an + * Oracle database. + *

+ * It is configured through the following properties: + *

+ * See also {@link DbFileSystem}. + *

+ * The following is a fragment from a sample configuration: + *

+ *   <FileSystem class="org.apache.jackrabbit.core.fs.db.Oracle*FileSystem">
+ *       <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="rep_"/>
+ *       <param name="tableSpace" value="default"/>
+ *  </FileSystem>
+ * 
+ */ +public class OracleBaseFileSystem extends DbFileSystem { + /** + * Logger instance + */ + private static Logger log = LoggerFactory.getLogger(OracleBaseFileSystem.class); + + /** 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 OracleBaseFileSystem instance. + */ + protected OracleBaseFileSystem() { + // preset some attributes to reasonable defaults + schema = "oracle"; + driver = "oracle.jdbc.OracleDriver"; + schemaObjectPrefix = ""; + 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; + } + } + + /** + * {@inheritDoc} + *

+ * 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 + "FSENTRY"; + 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 = OracleFileSystem.class.getResourceAsStream(schema + ".ddl"); + 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(); + } + } finally { + IOUtils.closeQuietly(in); + closeStatement(stmt); + } + } + } + + /** + * Builds the SQL statements + *

+ * Since Oracle treats emtpy strings and BLOBs as null values the SQL + * statements had to be adapated accordingly. The following changes were + * necessary: + *

+ */ + protected void buildSQLStatements() { + insertFileSQL = "insert into " + + schemaObjectPrefix + "FSENTRY " + + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " + + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " + + "values (?, ?, ?, ?, ?)"; + + insertFolderSQL = "insert into " + + schemaObjectPrefix + "FSENTRY " + + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_LASTMOD, FSENTRY_LENGTH) " + + "values (?, nvl(?, ' '), ?, null)"; + + updateDataSQL = "update " + + schemaObjectPrefix + "FSENTRY " + + "set FSENTRY_DATA = ?, FSENTRY_LASTMOD = ?, FSENTRY_LENGTH = ? " + + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? " + + "and FSENTRY_LENGTH is not null"; + + updateLastModifiedSQL = "update " + + schemaObjectPrefix + "FSENTRY set FSENTRY_LASTMOD = ? " + + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? " + + "and FSENTRY_LENGTH is not null"; + + selectExistSQL = "select 1 from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = nvl(?, ' ')"; + + selectFileExistSQL = "select 1 from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; + + selectFolderExistSQL = "select 1 from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = nvl(?, ' ') and FSENTRY_LENGTH is null"; + + selectFileNamesSQL = "select FSENTRY_NAME from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_LENGTH is not null"; + + selectFolderNamesSQL = "select FSENTRY_NAME from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME != ' ' " + + "and FSENTRY_LENGTH is null"; + + selectFileAndFolderNamesSQL = "select FSENTRY_NAME from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME != ' '"; + + selectChildCountSQL = "select count(FSENTRY_NAME) from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME != ' '"; + + selectDataSQL = "select nvl(FSENTRY_DATA, empty_blob()) from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; + + selectLastModifiedSQL = "select FSENTRY_LASTMOD from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = nvl(?, ' ')"; + + selectLengthSQL = "select nvl(FSENTRY_LENGTH, 0) from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; + + deleteFileSQL = "delete from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; + + deleteFolderSQL = "delete from " + + schemaObjectPrefix + "FSENTRY where " + + "(FSENTRY_PATH = ? and FSENTRY_NAME = nvl(?, ' ') and FSENTRY_LENGTH is null) " + + "or (FSENTRY_PATH = ?) " + + "or (FSENTRY_PATH like ?) "; + + copyFileSQL = "insert into " + + schemaObjectPrefix + "FSENTRY " + + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " + + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " + + "select ?, ?, FSENTRY_DATA, " + + "FSENTRY_LASTMOD, FSENTRY_LENGTH from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; + + copyFilesSQL = "insert into " + + schemaObjectPrefix + "FSENTRY " + + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " + + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " + + "select ?, FSENTRY_NAME, FSENTRY_DATA, " + + "FSENTRY_LASTMOD, FSENTRY_LENGTH from " + + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " + + "and FSENTRY_LENGTH is not null"; + } +} Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java (revision 790376) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java (working copy) @@ -16,579 +16,24 @@ */ package org.apache.jackrabbit.core.fs.db; -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.util.Text; -import org.apache.jackrabbit.util.TransientFileFactory; -import org.apache.jackrabbit.core.fs.FileSystemException; -import org.apache.jackrabbit.core.fs.FileSystemPathUtil; -import org.apache.jackrabbit.core.fs.RandomAccessOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jcr.RepositoryException; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.Statement; -import java.sql.Blob; -import java.sql.Connection; -import java.io.InputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.IOException; -import java.io.File; -import java.io.FilterOutputStream; -import java.io.FileOutputStream; -import java.io.FileInputStream; -import java.io.RandomAccessFile; -import java.lang.reflect.Method; - /** - * OracleFileSystem is a JDBC-based FileSystem - * implementation for Jackrabbit that persists file system entries in an - * Oracle database. + * {@inheritDoc} *

- * It is configured through the following properties: - *

- * See also {@link DbFileSystem}. - *

- * The following is a fragment from a sample configuration: - *

- *   <FileSystem class="org.apache.jackrabbit.core.fs.db.OracleFileSystem">
- *       <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="rep_"/>
- *       <param name="tableSpace" value="default"/>
- *  </FileSystem>
- * 
+ * OracleFileSystem works for Oracle database version 10 R2+. */ -public class OracleFileSystem extends DbFileSystem { - +public class OracleFileSystem extends OracleBaseFileSystem { /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(OracleFileSystem.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 OracleFileSystem instance. + * Creates a new Oracle10FileSystem instance. */ public OracleFileSystem() { - // preset some attributes to reasonable defaults - schema = "oracle"; - driver = "oracle.jdbc.OracleDriver"; - schemaObjectPrefix = ""; - initialized = false; + super(); } - - /** - * 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; - } - } - - //-----------------------------------------< DatabaseFileSystem overrides > - /** - * {@inheritDoc} - *

- * Retrieve the oracle.sql.BLOB class via reflection, and - * initialize the values for the DURATION_SESSION and - * MODE_READWRITE constants defined there. - * @see oracle.sql.BLOB#DURATION_SESSION - * @see oracle.sql.BLOB#MODE_READWRITE - */ - public void init() throws FileSystemException { - super.init(); - - // initialize oracle.sql.BLOB class & constants - - // use the Connection object for using the exact same - // class loader that the Oracle driver was loaded with - try { - 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)); - } catch (Exception e) { - String msg = "failed to load/introspect oracle.sql.BLOB"; - log.error(msg, e); - throw new FileSystemException(msg, e); - } - } - - /** - * {@inheritDoc} - *

- * 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 + "FSENTRY"; - 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 = OracleFileSystem.class.getResourceAsStream(schema + ".ddl"); - 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(); - } - } finally { - IOUtils.closeQuietly(in); - closeStatement(stmt); - } - } - } - - /** - * Builds the SQL statements - *

- * Since Oracle treats emtpy strings and BLOBs as null values the SQL - * statements had to be adapated accordingly. The following changes were - * necessary: - *

- */ - protected void buildSQLStatements() { - insertFileSQL = "insert into " - + schemaObjectPrefix + "FSENTRY " - + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " - + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " - + "values (?, ?, ?, ?, ?)"; - - insertFolderSQL = "insert into " - + schemaObjectPrefix + "FSENTRY " - + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_LASTMOD, FSENTRY_LENGTH) " - + "values (?, nvl(?, ' '), ?, null)"; - - updateDataSQL = "update " - + schemaObjectPrefix + "FSENTRY " - + "set FSENTRY_DATA = ?, FSENTRY_LASTMOD = ?, FSENTRY_LENGTH = ? " - + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? " - + "and FSENTRY_LENGTH is not null"; - - updateLastModifiedSQL = "update " - + schemaObjectPrefix + "FSENTRY set FSENTRY_LASTMOD = ? " - + "where FSENTRY_PATH = ? and FSENTRY_NAME = ? " - + "and FSENTRY_LENGTH is not null"; - - selectExistSQL = "select 1 from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = nvl(?, ' ')"; - - selectFileExistSQL = "select 1 from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; - - selectFolderExistSQL = "select 1 from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = nvl(?, ' ') and FSENTRY_LENGTH is null"; - - selectFileNamesSQL = "select FSENTRY_NAME from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_LENGTH is not null"; - - selectFolderNamesSQL = "select FSENTRY_NAME from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME != ' ' " - + "and FSENTRY_LENGTH is null"; - - selectFileAndFolderNamesSQL = "select FSENTRY_NAME from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME != ' '"; - - selectChildCountSQL = "select count(FSENTRY_NAME) from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME != ' '"; - - selectDataSQL = "select nvl(FSENTRY_DATA, empty_blob()) from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; - - selectLastModifiedSQL = "select FSENTRY_LASTMOD from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = nvl(?, ' ')"; - - selectLengthSQL = "select nvl(FSENTRY_LENGTH, 0) from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; - - deleteFileSQL = "delete from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; - - deleteFolderSQL = "delete from " - + schemaObjectPrefix + "FSENTRY where " - + "(FSENTRY_PATH = ? and FSENTRY_NAME = nvl(?, ' ') and FSENTRY_LENGTH is null) " - + "or (FSENTRY_PATH = ?) " - + "or (FSENTRY_PATH like ?) "; - - copyFileSQL = "insert into " - + schemaObjectPrefix + "FSENTRY " - + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " - + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " - + "select ?, ?, FSENTRY_DATA, " - + "FSENTRY_LASTMOD, FSENTRY_LENGTH from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_NAME = ? and FSENTRY_LENGTH is not null"; - - copyFilesSQL = "insert into " - + schemaObjectPrefix + "FSENTRY " - + "(FSENTRY_PATH, FSENTRY_NAME, FSENTRY_DATA, " - + "FSENTRY_LASTMOD, FSENTRY_LENGTH) " - + "select ?, FSENTRY_NAME, FSENTRY_DATA, " - + "FSENTRY_LASTMOD, FSENTRY_LENGTH from " - + schemaObjectPrefix + "FSENTRY where FSENTRY_PATH = ? " - + "and FSENTRY_LENGTH is not null"; - } - - - /** - * {@inheritDoc} - *

- * Overridden because we need to use oracle.sql.BLOB - * and PreparedStatement#setBlob instead of just - * PreparedStatement#setBinaryStream. - */ - public OutputStream getOutputStream(final String filePath) throws FileSystemException { - if (!initialized) { - throw new IllegalStateException("not initialized"); - } - - FileSystemPathUtil.checkFormat(filePath); - - final String parentDir = FileSystemPathUtil.getParentDir(filePath); - final String name = FileSystemPathUtil.getName(filePath); - - if (!isFolder(parentDir)) { - throw new FileSystemException("path not found: " + parentDir); - } - - if (isFolder(filePath)) { - throw new FileSystemException("path denotes folder: " + filePath); - } - - try { - TransientFileFactory fileFactory = TransientFileFactory.getInstance(); - final File tmpFile = fileFactory.createTransientFile("bin", null, null); - - return new FilterOutputStream(new FileOutputStream(tmpFile)) { - - public void close() throws IOException { - super.close(); - - InputStream in = null; - Blob blob = null; - try { - if (isFile(filePath)) { - synchronized (updateDataSQL) { - long length = tmpFile.length(); - in = new FileInputStream(tmpFile); - blob = createTemporaryBlob(in); - executeStmt(updateDataSQL, - new Object[]{ - blob, - new Long(System.currentTimeMillis()), - new Long(length), - parentDir, - name - }); - } - } else { - synchronized (insertFileSQL) { - long length = tmpFile.length(); - in = new FileInputStream(tmpFile); - blob = createTemporaryBlob(in); - executeStmt(insertFileSQL, - new Object[]{ - parentDir, - name, - blob, - new Long(System.currentTimeMillis()), - new Long(length) - }); - } - } - } catch (Exception e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } finally { - if (blob != null) { - try { - freeTemporaryBlob(blob); - } catch (Exception e1) { - } - } - IOUtils.closeQuietly(in); - // temp file can now safely be removed - tmpFile.delete(); - } - } - }; - } catch (Exception e) { - String msg = "failed to open output stream to file: " + filePath; - log.error(msg, e); - throw new FileSystemException(msg, e); - } - } - - /** - * {@inheritDoc} - */ - public RandomAccessOutputStream getRandomAccessOutputStream( - final String filePath) - throws FileSystemException, UnsupportedOperationException { - if (!initialized) { - throw new IllegalStateException("not initialized"); - } - - FileSystemPathUtil.checkFormat(filePath); - - final String parentDir = FileSystemPathUtil.getParentDir(filePath); - final String name = FileSystemPathUtil.getName(filePath); - - if (!isFolder(parentDir)) { - throw new FileSystemException("path not found: " + parentDir); - } - - if (isFolder(filePath)) { - throw new FileSystemException("path denotes folder: " + filePath); - } - - try { - TransientFileFactory fileFactory = TransientFileFactory.getInstance(); - final File tmpFile = fileFactory.createTransientFile("bin", null, null); - - // @todo FIXME use java.sql.Blob - - if (isFile(filePath)) { - // file entry exists, spool contents to temp file first - InputStream in = getInputStream(filePath); - OutputStream out = new FileOutputStream(tmpFile); - try { - IOUtils.copy(in, out); - } finally { - out.close(); - in.close(); - } - } - - return new RandomAccessOutputStream() { - private final RandomAccessFile raf = - new RandomAccessFile(tmpFile, "rw"); - - public void close() throws IOException { - raf.close(); - - InputStream in = null; - Blob blob = null; - try { - if (isFile(filePath)) { - synchronized (updateDataSQL) { - long length = tmpFile.length(); - in = new FileInputStream(tmpFile); - blob = createTemporaryBlob(in); - executeStmt(updateDataSQL, - new Object[]{ - blob, - new Long(System.currentTimeMillis()), - new Long(length), - parentDir, - name - }); - } - } else { - synchronized (insertFileSQL) { - long length = tmpFile.length(); - in = new FileInputStream(tmpFile); - blob = createTemporaryBlob(in); - executeStmt(insertFileSQL, - new Object[]{ - parentDir, - name, - blob, - new Long(System.currentTimeMillis()), - new Long(length) - }); - } - } - } catch (Exception e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } finally { - if (blob != null) { - try { - freeTemporaryBlob(blob); - } catch (Exception e1) { - } - } - IOUtils.closeQuietly(in); - // temp file can now safely be removed - tmpFile.delete(); - } - } - - public void seek(long position) throws IOException { - raf.seek(position); - } - - public void write(int b) throws IOException { - raf.write(b); - } - - public void flush() /*throws IOException*/ { - // nop - } - - public void write(byte[] b) throws IOException { - raf.write(b); - } - - public void write(byte[] b, int off, int len) throws IOException { - raf.write(b, off, len); - } - }; - } catch (Exception e) { - String msg = "failed to open output stream to file: " + filePath; - log.error(msg, e); - throw new FileSystemException(msg, e); - } - } - - //----------------------------------------< 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[]{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, null); - 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, null); - 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, null); - } }