Added: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleAttribute.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleAttribute.java?rev=762433&view=auto ============================================================================== --- directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleAttribute.java (added) +++ directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleAttribute.java Mon Apr 6 17:40:02 2009 @@ -0,0 +1,331 @@ +/* + * 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.directory.server.partition.impl.oracle; + +import java.io.ByteArrayInputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.sql.SQLData; +import java.sql.SQLException; +import java.sql.SQLInput; +import java.sql.SQLOutput; + +import oracle.jdbc.OracleCallableStatement; +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OraclePreparedStatement; +import oracle.jdbc.OracleResultSet; +import oracle.jdbc.OracleTypes; +import oracle.sql.BLOB; +import oracle.sql.CLOB; + +import org.apache.directory.shared.ldap.util.Base64; + +/** + * This class is used like a one to one mapping for the + * LDAP_ATTRIBUTE pl/sql object. It is encoded and decoded + * directly from the jdbc driver calling the readSQL and + * writeSQL methods of the SQLData interface. + */ +public class OracleAttribute implements SQLData +{ + private String sqlType= "LDAP_ATTRIBUTE"; + private OracleConnection connection; + + private String name; + private String svalue; + private byte[] bvalue; + private String type; + private Long bvalueid; + private Long cvalueid; + + /** + * Used from jdbc driver to instanciate the class + */ + public OracleAttribute () + {} + + /** + * Used from OracleEntry when converting a ServerEntry + * for a string value + */ + public OracleAttribute (String name, String value, String type, OracleConnection connection) + { + this.name= name; + this.svalue= value; + this.type= type; + this.connection= connection; + } + + /** + * Used from OracleEntry when converting a ServerEntry + * for a binary value + */ + public OracleAttribute (String name, byte[] value, String type, OracleConnection connection) + { + this.name= name; + this.bvalue= value; + this.type= type; + this.connection= connection; + } + + /** + * Sets a connection to read LOBs values + * @param connection + */ + public void setConnection(OracleConnection connection) + { + this.connection= connection; + } + + /** + * @see SQLData + */ + public String getSQLTypeName() throws SQLException + { + return sqlType; + } + + /** + * @see SQLData + */ + public void readSQL( SQLInput stream, String sqlType ) throws SQLException + { + this.sqlType= sqlType; + + name= stream.readString(); + type= stream.readString(); + svalue= stream.readString(); + bvalue= stream.readBytes(); + cvalueid= stream.readLong(); + bvalueid= stream.readLong(); + } + + /** + * @see SQLData + */ + public void writeSQL( SQLOutput stream ) throws SQLException + { + stream.writeString(name); + stream.writeString(type); + + boolean clob= (svalue!=null&&svalue.length()>4000); + boolean blob= (bvalue!=null&&bvalue.length>2000); + + if (clob) + stream.writeString(null); + else + stream.writeString(svalue); + + if (blob) + stream.writeBytes(null); + else + stream.writeBytes(bvalue); + + try + { + if (clob) + stream.writeLong(writeClobValue( connection, svalue )); + else + stream.writeLong(0L); + + if (blob) + stream.writeLong(writeBlobValue( connection, bvalue )); + else + stream.writeLong(0L); + } + catch (Exception ex) + { + throw new SQLException(ex); + } + + } + + /** + * + * @return the attribute name + */ + public String getName() + { + return name; + } + + /** + * Get the attribute value + * @return the attribute value (either a String or a byte[]) + * @throws Exception + */ + public Object getValue() + throws Exception + { + if (svalue!=null) + return svalue; + else + if (bvalue!=null) + return bvalue; + else + if (cvalueid!=0L) + return readClobValue( connection, cvalueid ); + else + if (bvalueid!=0L) + return readBlobValue( connection, bvalueid ); + + return null; + } + + /** + * Create an hash for a LOB value to check if we already have one + * in the database + * + * @param val + * @return + * @throws Exception + */ + public static final String hash(byte[] val) + throws Exception + { + MessageDigest md = MessageDigest.getInstance("SHA"); + DigestInputStream digestIn = new DigestInputStream(new ByteArrayInputStream(val), md); + while (digestIn.read() != -1); + byte[] digest = md.digest(); + return "{SHA}"+new String(Base64.encode(digest)); + } + + /** + * Reads a CLOB value (used for >4000 string values) + * + * @param connection + * @param cvalueid + * @return + * @throws Exception + */ + public static final String readClobValue( OracleConnection connection, long cvalueid ) + throws Exception + { + OraclePreparedStatement stmt= ( OraclePreparedStatement ) connection.prepareStatement( "select column_value from table(partition_facade.read_clob(?))" ); + stmt.setLong( 1, cvalueid ); + + OracleResultSet rs= ( OracleResultSet ) stmt.executeQuery(); + + rs.next(); + + String cvalue= rs.getString( 1 ); + + rs.close(); + stmt.close(); + + return cvalue; + } + + /** + * reads a BLOB value (used for >2000 binary values) + * + * @param connection + * @param bvalueid + * @return + * @throws Exception + */ + public static final byte[] readBlobValue( OracleConnection connection, long bvalueid ) + throws Exception + { + OraclePreparedStatement stmt= ( OraclePreparedStatement ) connection.prepareStatement( "select column_value from table(partition_facade.read_blob(?))" ); + stmt.setLong( 1, bvalueid ); + + OracleResultSet rs= ( OracleResultSet ) stmt.executeQuery(); + + rs.next(); + + byte[] bvalue= rs.getBytes( 1 ); + + rs.close(); + stmt.close(); + + return bvalue; + } + + /** + * Writes a new CLOB value into the database only if cannot + * find its hash. + * + * @param connection the connection to use + * @param value the value to write + * @return the value id + * @throws Exception + */ + public static final Long writeClobValue( OracleConnection connection, String value ) + throws Exception + { + String hash= hash(value.getBytes()); + + long cvalueid= 0L; + OracleCallableStatement stmt= ( OracleCallableStatement ) connection.prepareCall( "begin partition_facade.write_clob(?,?,?); end;" ); + stmt.setString( 1, hash ); + stmt.registerOutParameter( 2, OracleTypes.NUMBER); + stmt.registerOutParameter( 3, OracleTypes.CLOB); + + stmt.execute(); + + cvalueid= stmt.getLong( 2 ); + + CLOB c= stmt.getCLOB( 3 ); + c.open( CLOB.MODE_READWRITE ); + c.truncate( 0 ); + c.setAsciiStream( 1 ).write( value.getBytes() ); + c.close(); + + stmt.close(); + + return cvalueid; + } + + /** + * Writes a new BLOB value into the database only if cannot + * find its hash. + * + * @param connection the connection to use + * @param value the value to write + * @return the value id + * @throws Exception + */ + public static final Long writeBlobValue( OracleConnection connection, byte[] value ) + throws Exception + { + String hash= hash(value); + + long bvalueid= 0L; + OracleCallableStatement stmt= ( OracleCallableStatement ) connection.prepareCall( "begin partition_facade.write_blob(?,?,?); end;" ); + stmt.setString( 1, hash ); + stmt.registerOutParameter( 2, OracleTypes.NUMBER); + stmt.registerOutParameter( 3, OracleTypes.BLOB); + + stmt.execute(); + + bvalueid= stmt.getLong( 2 ); + + BLOB b= stmt.getBLOB( 3 ); + b.open( BLOB.MODE_READWRITE ); + b.truncate( 0 ); + b.setBinaryStream( 1 ).write( value ); + b.close(); + + stmt.close(); + + return bvalueid; + } +} Propchange: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleAttribute.java ------------------------------------------------------------------------------ svn:eol-style = native Added: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntry.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntry.java?rev=762433&view=auto ============================================================================== --- directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntry.java (added) +++ directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntry.java Mon Apr 6 17:40:02 2009 @@ -0,0 +1,208 @@ +/* + * 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.directory.server.partition.impl.oracle; + +import java.sql.Array; +import java.sql.SQLData; +import java.sql.SQLException; +import java.sql.SQLInput; +import java.sql.SQLOutput; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import oracle.jdbc.OracleConnection; +import oracle.sql.ARRAY; +import oracle.sql.ArrayDescriptor; + +import org.apache.directory.server.core.entry.DefaultServerEntry; +import org.apache.directory.server.core.entry.ServerEntry; +import org.apache.directory.server.schema.registries.AttributeTypeRegistry; +import org.apache.directory.shared.ldap.entry.EntryAttribute; +import org.apache.directory.shared.ldap.entry.Value; +import org.apache.directory.shared.ldap.name.LdapDN; +import org.apache.directory.shared.ldap.schema.AttributeType; +import org.apache.directory.shared.ldap.schema.UsageEnum; + +/** + * This class is used like a one to one mapping for the + * LDAP_ENTRY pl/sql object. It is encoded and decoded + * directly from the jdbc driver calling the readSQL and + * writeSQL methods of the SQLData interface. + */ +public class OracleEntry implements SQLData +{ + private String sqlType= "LDAP_ENTRY"; + private OracleConnection connection; + + private String reversedDn; + private String upDn; + private ArrayList attrs= new ArrayList(); + + /** + * A connection that can be used to set and get LOBs + * values for this entry + * @param connection + * @throws Exception + */ + public void setConnection(OracleConnection connection) + throws Exception + { + this.connection= connection; + } + + /** + * @see SQLData + */ + public String getSQLTypeName() throws SQLException + { + return sqlType; + } + + /** + * @see SQLData + */ + public void readSQL( SQLInput stream, String sqlType ) throws SQLException + { + this.sqlType= sqlType; + + reversedDn= stream.readString(); + upDn= stream.readString(); + Array a= stream.readArray(); + + Object[] oas= ( Object[] ) a.getArray(); + + for (Object o: oas) + attrs.add( ( OracleAttribute ) o ); + } + + /** + * @see SQLData + */ + public void writeSQL( SQLOutput stream ) throws SQLException + { + stream.writeString( reversedDn ); + stream.writeString( upDn ); + + ArrayDescriptor desc = ArrayDescriptor.createDescriptor("LDAP_ATTRIBUTE_TABLE",connection); + ARRAY attrsArray = new ARRAY(desc, connection, attrs.toArray()); + + stream.writeArray( attrsArray ); + } + + /** + * @return the reversed and normalized entry DN + */ + public String getReversedDn() + { + return reversedDn; + } + + /** + * @return the user provided DN + */ + public String getUpDn() + { + return upDn; + } + + /** + * Set the reversed and normalized entry dn + * @param reversedDn + */ + public void setReversedDn( String reversedDn ) + { + this.reversedDn = reversedDn; + } + + /** + * @return a list of entry attributes + */ + public List getAttrs() + { + return attrs; + } + + /** + * Converts this oracle entry to a ServerEntry + * @param partition the partition of this entry + * @return a ServerEntry version of this entry + * @throws Exception + */ + public ServerEntry toServerEntry(OraclePartition partition) + throws Exception + { + DefaultServerEntry se= new DefaultServerEntry(partition.getRegistries()); + se.setDn( new LdapDN(upDn) ); + AttributeTypeRegistry atr= partition.getRegistries().getAttributeTypeRegistry(); + + for (OracleAttribute attribute: attrs) + { + attribute.setConnection( partition.getConnectionWrapper().getConnection() ); // used for clob and blob value handling + + Object value= attribute.getValue(); + + if (value instanceof byte[]) + se.add( atr.lookup(attribute.getName()), (byte[])attribute.getValue() ); + else + se.add( atr.lookup(attribute.getName()), (String)attribute.getValue() ); + } + + return se; + } + + /** + * Convert a ServerEntry into an OracleEntry + * @param entry the ServerEntry to convert + * @param partition the partition of this entry + * @return an OracleEntry version of the ServerEntry + * @throws Exception + */ + public static OracleEntry fromServerEntry( ServerEntry entry, OraclePartition partition) + throws Exception + { + OracleEntry e= new OracleEntry(); + + e.setConnection( partition.getConnectionWrapper().getConnection() ); + e.reversedDn= OraclePartition.toReversedDn( entry.getDn() ); + e.upDn= entry.getDn().getUpName(); + + + for (AttributeType at: entry.getAttributeTypes()) + { + String type= (at.getUsage().equals(UsageEnum.USER_APPLICATIONS) ? "u" : null); + EntryAttribute ea= entry.get( at ); + Iterator> i= ea.getAll(); + + while (i.hasNext()) + { + Value v= i.next(); + + if (v.get() instanceof byte[]) + e.attrs.add( new OracleAttribute(at.getOid(), (byte[])v.get(), type, e.connection) ); + else + e.attrs.add( new OracleAttribute(at.getOid(), (String)v.get(), type, e.connection) ); + } + } + + return e; + } + +} Propchange: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntry.java ------------------------------------------------------------------------------ svn:eol-style = native Added: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntryCursorAdaptor.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntryCursorAdaptor.java?rev=762433&view=auto ============================================================================== --- directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntryCursorAdaptor.java (added) +++ directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntryCursorAdaptor.java Mon Apr 6 17:40:02 2009 @@ -0,0 +1,174 @@ +/* + * 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.directory.server.partition.impl.oracle; + +import java.sql.SQLException; + +import oracle.jdbc.OracleResultSet; + +import org.apache.directory.server.core.cursor.AbstractCursor; +import org.apache.directory.server.core.entry.ServerEntry; +import org.apache.directory.server.core.interceptor.context.ListOperationContext; +import org.apache.directory.server.core.interceptor.context.SearchOperationContext; +import org.apache.directory.server.partition.impl.oracle.OraclePartition.OracleCursorWrapper; +import org.apache.directory.shared.ldap.constants.SchemaConstants; +import org.apache.directory.shared.ldap.filter.PresenceNode; + +/** + * An adaptor for Oracle based result sets. + * From the fact we use oracle resultsets that contains + * PL/SQL objects, it is pretty simple to implement a cursor + * just wrapping the OracleResultSet + */ +public final class OracleEntryCursorAdaptor extends AbstractCursor +{ + private OraclePartition partition; + private OracleResultSet resultSet; + private OracleCursorWrapper cw; + + /** + * Create a cursor for a list operation context + * @param partition + * @param ctx + * @throws Exception + */ + public OracleEntryCursorAdaptor(OraclePartition partition, ListOperationContext ctx) + throws Exception + { + this.partition= partition; + cw= partition.prepareCursor("select value(o) from table(partition_facade.list(?)) o", OraclePartition.toReversedDn( ctx.getDn() )); + resultSet= cw.getResultSet(); + } + + /** + * Create a cursor for a search operation context + * @param partition + * @param ctx + * @throws Exception + */ + public OracleEntryCursorAdaptor(OraclePartition partition, SearchOperationContext ctx) + throws Exception + { + this.partition= partition; + int searchScope= ctx.getSearchControls().getSearchScope(); + String[] returningAttributes= ctx.getSearchControls().getReturningAttributes(); + returningAttributes= (returningAttributes == null ? new String[]{"*"} : returningAttributes); + long countLimit= ctx.getSearchControls().getCountLimit(); + + String filter= null; + + if (!(ctx.getFilter() instanceof PresenceNode&& + ( ((PresenceNode)ctx.getFilter()).getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) + || ((PresenceNode)ctx.getFilter()).getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT ) + ) + ) + ) + filter= partition.getXStream().toXML(ctx.getFilter()); + + + cw= partition.prepareCursor("select value(o) from table(partition_facade.search(?,?,?,?,?)) o", OraclePartition.toReversedDn( ctx.getDn() ), searchScope, filter, returningAttributes, countLimit); + resultSet= cw.getResultSet(); + } + + public void after( ServerEntry element ) throws Exception + { + resultSet.beforeFirst(); + + while (resultSet.next()) + if (resultSet.getObject( 1 ).equals( element )) + break; + } + + public void afterLast() throws Exception + { + resultSet.afterLast(); + } + + public boolean available() + { + try + { + return !resultSet.isClosed(); + } + catch ( SQLException e ) + { + return false; + } + } + + public void before( ServerEntry element ) throws Exception + { + resultSet.beforeFirst(); + + while (resultSet.next()) + if (resultSet.getObject( 1 ).equals( element )) + break; + + resultSet.previous(); + } + + public void beforeFirst() throws Exception + { + resultSet.beforeFirst(); + } + + public void close() throws Exception + { + resultSet.close(); + } + + public void close( Exception reason ) throws Exception + { + resultSet.close(); + super.close(reason); + } + + public boolean first() throws Exception + { + return resultSet.first(); + } + + public ServerEntry get() throws Exception + { + return ((OracleEntry)resultSet.getObject( 1 )).toServerEntry( partition ); + } + + + public boolean isElementReused() + { + return false; + } + + public boolean last() throws Exception + { + return resultSet.last(); + } + + public boolean next() throws Exception + { + return resultSet.next(); + } + + public boolean previous() throws Exception + { + return resultSet.previous(); + } + +} Propchange: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OracleEntryCursorAdaptor.java ------------------------------------------------------------------------------ svn:eol-style = native Added: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OraclePartition.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OraclePartition.java?rev=762433&view=auto ============================================================================== --- directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OraclePartition.java (added) +++ directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OraclePartition.java Mon Apr 6 17:40:02 2009 @@ -0,0 +1,599 @@ +/* + * 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.directory.server.partition.impl.oracle; + + +import java.sql.ResultSet; +import java.util.Map; + +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; + +import oracle.jdbc.OracleConnection; +import oracle.jdbc.OracleResultSet; +import oracle.jdbc.driver.OraclePreparedStatement; +import oracle.jdbc.pool.OracleDataSource; +import oracle.sql.ARRAY; +import oracle.sql.ArrayDescriptor; + +import org.apache.directory.server.constants.ServerDNConstants; +import org.apache.directory.server.core.DirectoryService; +import org.apache.directory.server.core.entry.ClonedServerEntry; +import org.apache.directory.server.core.entry.ServerEntry; +import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor; +import org.apache.directory.server.core.filtering.EntryFilteringCursor; +import org.apache.directory.server.core.interceptor.context.AddOperationContext; +import org.apache.directory.server.core.interceptor.context.BindOperationContext; +import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; +import org.apache.directory.server.core.interceptor.context.EntryOperationContext; +import org.apache.directory.server.core.interceptor.context.ListOperationContext; +import org.apache.directory.server.core.interceptor.context.LookupOperationContext; +import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; +import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; +import org.apache.directory.server.core.interceptor.context.MoveOperationContext; +import org.apache.directory.server.core.interceptor.context.RenameOperationContext; +import org.apache.directory.server.core.interceptor.context.SearchOperationContext; +import org.apache.directory.server.core.interceptor.context.UnbindOperationContext; +import org.apache.directory.server.core.partition.AbstractPartition; +import org.apache.directory.server.core.partition.Partition; +import org.apache.directory.server.schema.registries.Registries; +import org.apache.directory.shared.ldap.name.LdapDN; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.XStream; + +/** + * A {@link Partition} that stores entries in + * Oracle database. + * + */ +public final class OraclePartition extends AbstractPartition +{ + + private static final Logger log = LoggerFactory.getLogger( OraclePartition.class ); + private static final ThreadLocal connectionWrapper= new ThreadLocal(); + + private int FETCH_SIZE= 3; + private XStream xstream= new XStream(); + private OracleDataSource dataSource; + + private String id; + private String suffix; + private LdapDN normSuffixDN; + private DirectoryService directoryService; + private ServerEntry contextEntry; + + + public OraclePartition () + { + } + + public ServerEntry getContextEntry() + { + return contextEntry; + } + + /* + * NOTE: do not store registries on startup because they will change + * on inizialization + */ + public Registries getRegistries() + { + return directoryService.getRegistries(); + } + + public void setContextEntry( ServerEntry contextEntry ) + { + this.contextEntry = contextEntry; + } + + public void setDirectoryService(DirectoryService ds) + { + directoryService= ds; + configureXStream( xstream ); + } + + /** + * Configures the XStream instance + * used to pass filter infos from java to pl/sql + * + * @param xstream: the stream to configure + */ + public void configureXStream(XStream xstream) + { + xstream.registerConverter(new FilterConverter(directoryService)); + xstream.setMode(XStream.NO_REFERENCES); + xstream.alias("OrNode", org.apache.directory.shared.ldap.filter.OrNode.class); + xstream.alias("AndNode", org.apache.directory.shared.ldap.filter.AndNode.class); + xstream.alias("NotNode", org.apache.directory.shared.ldap.filter.NotNode.class); + xstream.alias("AbstractExprNode", org.apache.directory.shared.ldap.filter.AbstractExprNode.class); + xstream.alias("ApproximateNode", org.apache.directory.shared.ldap.filter.ApproximateNode.class); + xstream.alias("AssertionNode", org.apache.directory.shared.ldap.filter.AssertionNode.class); + xstream.alias("EqualityNode", org.apache.directory.shared.ldap.filter.EqualityNode.class); + xstream.alias("ExtensibleNode", org.apache.directory.shared.ldap.filter.ExtensibleNode.class); + xstream.alias("GreaterEqNode", org.apache.directory.shared.ldap.filter.GreaterEqNode.class); + xstream.alias("LessEqNode", org.apache.directory.shared.ldap.filter.LessEqNode.class); + xstream.alias("SubstringNode", org.apache.directory.shared.ldap.filter.SubstringNode.class); + xstream.alias("PresenceNode", org.apache.directory.shared.ldap.filter.PresenceNode.class); + xstream.alias("ScopeNode", org.apache.directory.shared.ldap.filter.ScopeNode.class); + } + + /** + * + * @return the configured datasource used to connect to the oracle database + */ + public OracleDataSource getDataSource() + { + return dataSource; + } + + /** + * Used by spring configuration to set the OracleDataSource instance + * that this partition has to use to connect to the database + * + * @param dataSource + */ + public void setDataSource( OracleDataSource dataSource ) + { + this.dataSource = dataSource; + } + + /** + * This method returns the connection associated with this thread: + * using MINA the partition code is executed inside a thread pool, + * each thread should have its database connection. Opening and closing + * connections on each partition method results in a serious server + * slowdown. + * + * @return a connection wrapper that closes the wrapped connection + * once this thread ends. + * + * @throws Exception + */ + public OracleConnectionWrapper getConnectionWrapper() + throws Exception + { + OracleConnectionWrapper w= connectionWrapper.get(); + + if (w==null||w.getConnection().isClosed()) + { + w= new OracleConnectionWrapper((OracleConnection)dataSource.getConnection()); + w.getConnection().setAutoCommit(false); + Map typeMap = w.getConnection().getTypeMap(); + typeMap.put( "LDAP_ENTRY", OracleEntry.class ); + typeMap.put( "LDAP_ATTRIBUTE", OracleAttribute.class ); + typeMap.put( "LDAP_ATTRIBUTE_TABLE", OracleAttribute[].class ); + typeMap.put( "VC_ARR", String[].class ); + + connectionWrapper.set(w); + } + + return w; + } + + /** + * + * @return the XStream instance + * used to pass filter infos from java to pl/sql. + */ + public XStream getXStream() + { + return xstream; + } + + /** + * Normalize and reverse the dn to use it in a database index: + * using a dn "as is" will not use the database index in a good + * way and will create block contention, + * because a lot of DNs (uid=1222,ou=People,dc=example,dc=com) are likely to + * end up in the same block. Using a reverse index will create somenthing + * like this (moc=cd,elpmaxe=cd,elpoeP=uo,2221=diu) that is better but + * still a little ugly because values come first. Reversing the dn by rdn + * will generate a pretty indexable value instead (dc=com,dc=example,ou=People,uid=1222). + * + * + * @param dn an {@link LdapDN} + * @return the dn normalized and reversed + */ + public static final String toReversedDn(LdapDN dn) + { + StringBuffer sb= new StringBuffer(); + String[] pieces= dn.getNormName().split(","); + + for (int i = pieces.length-1; i > -1 ; i--) + sb.append(","+pieces[i]); + + + return sb.toString().substring(1); + } + + /** + * @see Partition + */ + public void add( final AddOperationContext ctx ) throws Exception + { + // FIXME: bypass error on 2nd startup: DefaultPatitionNexus tries to add an existing entry (check issue DIRSERVER-1344) + if (ServerDNConstants.SYSTEM_DN.equals( ctx.getDn().getUpName() )&&lookup( ctx.getDn() ) != null) return; + + executeDml("begin partition_facade.add(?); end;",OracleEntry.fromServerEntry((ServerEntry)((ClonedServerEntry)ctx.getEntry()).getClonedEntry(),this)); + } + + + /** + * @see Partition + */ + public void bind( BindOperationContext ctx ) throws Exception + { + + if (lookup(ctx.getDn())==null) + throw new NamingException( "Unknown dn: "+ctx.getDn().getUpName() ); + + } + + + /** + * @see Partition + */ + public void delete(final DeleteOperationContext ctx ) throws Exception + { + executeDml("begin partition_facade.delete(?); end;",toReversedDn( ctx.getDn() )); + } + + + /** + * @see Partition + */ + public int getCacheSize() + { + return 0; + } + + + /** + * @see Partition + */ + public String getId() + { + return id; + } + + + /** + * @see Partition + */ + public String getSuffix() + { + return suffix; + } + + + /** + * @see Partition + */ + public LdapDN getSuffixDn() throws Exception + { + return normSuffixDN; + } + + + /** + * @see Partition + */ + public LdapDN getUpSuffixDn() throws Exception + { + return new LdapDN(suffix); + } + + + /** + * @see Partition + */ + public EntryFilteringCursor list(final ListOperationContext ctx ) throws Exception + { + return new BaseEntryFilteringCursor( new OracleEntryCursorAdaptor(this, ctx), ctx ); + } + + /** + * + * @param dn + * @return the looked up {@link OracleEntry} + * @throws Exception + */ + private OracleEntry lookup(LdapDN dn) + throws Exception + { + OracleCursorWrapper cw= prepareCursor( "select partition_facade.lookup_dn(?) from dual", toReversedDn( dn ) ); + OracleResultSet rs= cw.getResultSet(); + + while (rs.next()) + return (OracleEntry)rs.getObject( 1 ); + + return null; + } + + /** + * @see Partition + */ + public ClonedServerEntry lookup( LookupOperationContext ctx ) throws Exception + { + return new ClonedServerEntry(lookup(ctx.getDn()).toServerEntry( this )); + } + + /** + * @see Partition + */ + public ClonedServerEntry lookup( Long id ) throws Exception + { + return null; + } + + /** + * A partition implementation that aims to be used for ou=system must implement this method because + * it is called before the DirectoryServer start and the default AbstractPartition implementation + * goes through some logic that checks the server to be started + * + * @see org.apache.directory.server.core.partition.AbstractPartition#hasEntry(org.apache.directory.server.core.interceptor.context.EntryOperationContext) + */ + public boolean hasEntry( EntryOperationContext ctx ) throws Exception + { + try + { + return lookup( ctx.getDn() ) != null; + } + catch ( NameNotFoundException e ) + { + return false; + } + } + + /** + * @see Partition + */ + public void modify( ModifyOperationContext ctx ) throws Exception + { + executeDml("begin partition_facade.modify(?); end;",OracleEntry.fromServerEntry( ((ServerEntry)ctx.getEntry().getClonedEntry()), this)); + } + + + /** + * @see Partition + */ + public void move( MoveOperationContext ctx ) throws Exception + { + executeDml("begin partition_facade.move(?,?,?); end;", toReversedDn( ctx.getParent() ), + ctx.getDn().getRdn().getUpName()+","+ctx.getParent().getUpName(), + toReversedDn( ctx.getDn() )); + } + + + /** + * @see Partition + */ + public void moveAndRename( MoveAndRenameOperationContext ctx ) throws Exception + { + executeDml("begin partition_facade.move_and_rename(?,?,?,?,?); end;", toReversedDn( ctx.getParent() ), + ctx.getNewRdn().getNormName(), + ctx.getParent().getUpName(), + ctx.getNewRdn().getUpName(), + toReversedDn( ctx.getDn() )); + } + + + /** + * @see Partition + */ + public void rename( RenameOperationContext ctx ) throws Exception + { + executeDml("begin partition_facade.rename(?,?,?); end;", ctx.getNewRdn().getNormName(), + ctx.getNewRdn().getUpName(), + toReversedDn( ctx.getDn() )); + } + + + /** + * @see Partition + */ + public EntryFilteringCursor search( SearchOperationContext ctx ) throws Exception + { + return new BaseEntryFilteringCursor( new OracleEntryCursorAdaptor(this, ctx), ctx ); + } + + + /** + * @see Partition + */ + public void setCacheSize( int cacheSize ) + {} + + + /** + * @see Partition + */ + public void setId( String id ) + { + this.id= id; + } + + + /** + * @see Partition + */ + public void setSuffix( String suffix ) + { + this.suffix= suffix; + + // NOTE: needed from partition nexus bootstrap to find system partition for uid=admin + try + { + this.normSuffixDN= LdapDN.normalize( new LdapDN(suffix), getRegistries().getAttributeTypeRegistry().getNormalizerMapping() ); + } + catch ( Exception e ) + { + throw new RuntimeException(); + } + } + + + /** + * @see Partition + */ + public void unbind( UnbindOperationContext ctx ) throws Exception + {} + + + /** + * Used to query database data + * + * NOTE: Each database access pass through the partition_facade pl/sql package + * to map Partition functions one by one on the database and make + * the tuning easier for Oracle DBAs + * + * @param statement the sql or plsql statement to prepare and execute + * @param objects bind variables to bind on + * @return a resultset wrapper that closes it on instance finalization + * @throws Exception + */ + public OracleCursorWrapper prepareCursor(String statement, Object... objects) + throws Exception + { + OracleConnection connection= getConnectionWrapper().getConnection(); + OraclePreparedStatement stmt= ( OraclePreparedStatement ) connection.prepareStatement(statement,OracleResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); + + stmt.setFetchSize( FETCH_SIZE ); + stmt.setFetchDirection( OracleResultSet.FETCH_UNKNOWN ); + + int idx= 1; + + for (Object obj: objects) + if (obj instanceof String[]) + { + ArrayDescriptor desc = ArrayDescriptor.createDescriptor("VC_ARR",connection); + ARRAY array = new ARRAY(desc, connection, obj); + stmt.setARRAY(idx++,array); + } + else + stmt.setObject(idx++,obj); + + return new OracleCursorWrapper(stmt,( OracleResultSet ) stmt.executeQuery()); + } + + /** + * + * Used to make changes to database data + * + * NOTE: Each database access pass through the partition_facade pl/sql package + * to map Partition functions one by one on the database and make + * the tuning easier for Oracle DBAs + * + * @param statement the DML statement + * @param objects + * @throws Exception + */ + public void executeDml(String statement, Object... objects) + throws Exception + { + OraclePreparedStatement stmt= ( OraclePreparedStatement ) getConnectionWrapper().getConnection().prepareStatement(statement); + + int idx= 1; + + for (Object obj: objects) + stmt.setObject(idx++,obj); + + stmt.execute(); + stmt.close(); + } + + /** + * A commodity method to lookup attribute OID + * + * @param directoryService + * @param att the attribute to lookup + * @return the attribute OID + * @throws Exception + */ + public static final String normAtt(DirectoryService directoryService, String att) + throws Exception + { + return directoryService.getRegistries().getAttributeTypeRegistry().lookup(att).getOid(); + } + + /** + * A commodity class to wrap oracle connections + * and close them only when needed + */ + public static class OracleConnectionWrapper + { + private OracleConnection connection; + + public OracleConnectionWrapper(OracleConnection connection) + { + this.connection= connection; + } + + public OracleConnection getConnection() + { + return connection; + } + + @Override + protected void finalize() throws Throwable + { + try { connection.close(); } catch (Exception e) {} + super.finalize(); + } + + } + + /** + * A commodity class to wrap oracle resultsets + * and close them only when needed + */ + public static class OracleCursorWrapper + { + private OraclePreparedStatement statement; + private OracleResultSet resultSet; + + public OracleCursorWrapper(OraclePreparedStatement statement, OracleResultSet resultSet) + { + this.statement= statement; + this.resultSet= resultSet; + } + + public OraclePreparedStatement getStatement() + { + return statement; + } + + public OracleResultSet getResultSet() + { + return resultSet; + } + + @Override + protected void finalize() throws Throwable + { + try { resultSet.close(); } catch (Exception e) {} + try { statement.close(); } catch (Exception e) {} + super.finalize(); + } + } + +} Propchange: directory/apacheds/branches/ldif-partition/oracle-partition/src/main/java/org/apache/directory/server/partition/impl/oracle/OraclePartition.java ------------------------------------------------------------------------------ svn:eol-style = native Added: directory/apacheds/branches/ldif-partition/oracle-partition/src/test/java/org/apache/directory/server/AppTest.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/oracle-partition/src/test/java/org/apache/directory/server/AppTest.java?rev=762433&view=auto ============================================================================== --- directory/apacheds/branches/ldif-partition/oracle-partition/src/test/java/org/apache/directory/server/AppTest.java (added) +++ directory/apacheds/branches/ldif-partition/oracle-partition/src/test/java/org/apache/directory/server/AppTest.java Mon Apr 6 17:40:02 2009 @@ -0,0 +1,38 @@ +package org.apache.directory.server; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} Propchange: directory/apacheds/branches/ldif-partition/oracle-partition/src/test/java/org/apache/directory/server/AppTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: directory/apacheds/branches/ldif-partition/xdbm-base/src/test/java/org/apache/directory/server/xdbm/TableTest.java URL: http://svn.apache.org/viewvc/directory/apacheds/branches/ldif-partition/xdbm-base/src/test/java/org/apache/directory/server/xdbm/TableTest.java?rev=762433&r1=762432&r2=762433&view=diff ============================================================================== --- directory/apacheds/branches/ldif-partition/xdbm-base/src/test/java/org/apache/directory/server/xdbm/TableTest.java (original) +++ directory/apacheds/branches/ldif-partition/xdbm-base/src/test/java/org/apache/directory/server/xdbm/TableTest.java Mon Apr 6 17:40:02 2009 @@ -51,9 +51,9 @@ */ public void testNoDuplicatesPutGet() throws Exception { - Assume.assumeNotNull( factory ); + //Assume.assumeNotNull( factory ); - Table table = factory.createTable(); + //Table table = factory.createTable(); } }