JDBC implementation of the MDR storage contributed by J. Sichi integrated
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/mdr/extras/jdbcstorage/README Wed Feb 04 06:38:10 2004 +0000
1.3 @@ -0,0 +1,22 @@
1.4 +JdbcStorage for MDR
1.5 +
1.6 +----------------------------------------------------------------------
1.7 +
1.8 +/*
1.9 + * Sun Public License Notice
1.10 + *
1.11 + * The contents of this file are subject to the Sun Public License
1.12 + * Version 1.0 (the "License"). You may not use this file except in
1.13 + * compliance with the License. A copy of the License is available at
1.14 + * http://www.sun.com/
1.15 + *
1.16 + * The Original Code is NetBeans. The Initial Developer of the Original
1.17 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
1.18 + * Microsystems, Inc. All Rights Reserved.
1.19 + */
1.20 +
1.21 +----------------------------------------------------------------------
1.22 +
1.23 +Please see org/netbeans/mdr/persistence/jdbcimpl/package.html for an
1.24 +overview. Send all questions/comments to John Sichi
1.25 +(jsichi@yahoo.com).
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcCollection.java Wed Feb 04 06:38:10 2004 +0000
2.3 @@ -0,0 +1,59 @@
2.4 +/*
2.5 + * Sun Public License Notice
2.6 + *
2.7 + * The contents of this file are subject to the Sun Public License
2.8 + * Version 1.0 (the "License"). You may not use this file except in
2.9 + * compliance with the License. A copy of the License is available at
2.10 + * http://www.sun.com/
2.11 + *
2.12 + * The Original Code is NetBeans. The Initial Developer of the Original
2.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
2.14 + * Microsystems, Inc. All Rights Reserved.
2.15 + */
2.16 +package org.netbeans.mdr.persistence.jdbcimpl;
2.17 +
2.18 +import org.netbeans.mdr.persistence.*;
2.19 +
2.20 +import java.util.*;
2.21 +
2.22 +/**
2.23 + * JdbcCollection is a read-only Collection backed by queries.
2.24 + *
2.25 + * @author John V. Sichi
2.26 + * @version $Id$
2.27 + */
2.28 +class JdbcCollection extends AbstractCollection
2.29 +{
2.30 + // delegate to JdbcSet for everything
2.31 + private JdbcSet set;
2.32 +
2.33 + JdbcCollection(
2.34 + JdbcStorage storage,
2.35 + Storage.EntryType entryType,
2.36 + LazyPreparedStatement sqlIterator,
2.37 + LazyPreparedStatement sqlSize,
2.38 + LazyPreparedStatement sqlContains)
2.39 + {
2.40 + set = new JdbcSet(storage,entryType,sqlIterator,sqlSize,sqlContains);
2.41 + }
2.42 +
2.43 + // override AbstractSet
2.44 + public boolean contains(Object obj)
2.45 + {
2.46 + return set.contains(obj);
2.47 + }
2.48 +
2.49 + // implement AbstractCollection
2.50 + public Iterator iterator()
2.51 + {
2.52 + return set.iterator();
2.53 + }
2.54 +
2.55 + // implement AbstractCollection
2.56 + public int size()
2.57 + {
2.58 + return set.size();
2.59 + }
2.60 +}
2.61 +
2.62 +// End JdbcCollection.java
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcIndex.java Wed Feb 04 06:38:10 2004 +0000
3.3 @@ -0,0 +1,162 @@
3.4 +/*
3.5 + * Sun Public License Notice
3.6 + *
3.7 + * The contents of this file are subject to the Sun Public License
3.8 + * Version 1.0 (the "License"). You may not use this file except in
3.9 + * compliance with the License. A copy of the License is available at
3.10 + * http://www.sun.com/
3.11 + *
3.12 + * The Original Code is NetBeans. The Initial Developer of the Original
3.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
3.14 + * Microsystems, Inc. All Rights Reserved.
3.15 + */
3.16 +package org.netbeans.mdr.persistence.jdbcimpl;
3.17 +
3.18 +import org.netbeans.mdr.persistence.*;
3.19 +import org.netbeans.mdr.util.*;
3.20 +
3.21 +import java.util.*;
3.22 +import java.io.*;
3.23 +
3.24 +/**
3.25 + * JdbcIndex implements the MDR Index interface using JDBC.
3.26 + *
3.27 + * @author John V. Sichi
3.28 + * @version $Id$
3.29 + */
3.30 +abstract class JdbcIndex implements Index
3.31 +{
3.32 + protected JdbcStorage storage;
3.33 + protected String tableName;
3.34 + protected String name;
3.35 + protected String keyColName;
3.36 + protected String valColName;
3.37 + protected Storage.EntryType keyType;
3.38 + protected Storage.EntryType valueType;
3.39 + protected boolean needSurrogate;
3.40 +
3.41 + protected LazyPreparedStatement sqlKeySetIterator;
3.42 + protected LazyPreparedStatement sqlKeySetSize;
3.43 + protected LazyPreparedStatement sqlKeySetContains;
3.44 + protected LazyPreparedStatement sqlInsert;
3.45 + protected LazyPreparedStatement sqlDelete;
3.46 + protected LazyPreparedStatement sqlFind;
3.47 +
3.48 + void init(
3.49 + JdbcStorage storage,
3.50 + String tableName,
3.51 + String name,
3.52 + String keyColName,
3.53 + String valColName,
3.54 + Storage.EntryType keyType,
3.55 + Storage.EntryType valueType,
3.56 + boolean needSurrogate)
3.57 + {
3.58 + this.storage = storage;
3.59 + this.tableName = tableName;
3.60 + this.name = name;
3.61 + this.keyColName = keyColName;
3.62 + this.valColName = valColName;
3.63 + this.keyType = keyType;
3.64 + this.valueType = valueType;
3.65 + this.needSurrogate = needSurrogate;
3.66 + defineSql();
3.67 + }
3.68 +
3.69 + protected void defineSql()
3.70 + {
3.71 + if (isKeyUnique()) {
3.72 + sqlKeySetIterator = new LazyPreparedStatement(
3.73 + "select " + keyColName + " from " + tableName);
3.74 + sqlKeySetSize = new LazyPreparedStatement(
3.75 + "select count(*) from " + tableName);
3.76 + } else {
3.77 + sqlKeySetIterator = new LazyPreparedStatement(
3.78 + "select distinct " + keyColName
3.79 + + " from " + tableName);
3.80 + sqlKeySetSize = new LazyPreparedStatement(
3.81 + "select count(distinct " + keyColName
3.82 + + ") from " + tableName);
3.83 + }
3.84 +
3.85 + sqlKeySetContains = new LazyPreparedStatement(
3.86 + "select count(*) from " + tableName + " where "
3.87 + + keyColName + " = ?");
3.88 +
3.89 + sqlInsert = new LazyPreparedStatement(
3.90 + "insert into " + tableName
3.91 + + " values(?,?"
3.92 + + (needSurrogate ? ",?" : "")
3.93 + + ")");
3.94 +
3.95 + sqlDelete = new LazyPreparedStatement(
3.96 + "delete from " + tableName
3.97 + + " where " + keyColName + " = ?");
3.98 +
3.99 + sqlFind = new LazyPreparedStatement(
3.100 + "select " + valColName + " from " + tableName
3.101 + + " where " + keyColName + " = ?");
3.102 + }
3.103 +
3.104 + protected boolean isKeyUnique()
3.105 + {
3.106 + return false;
3.107 + }
3.108 +
3.109 + // implement Index
3.110 + public String getName() throws StorageException
3.111 + {
3.112 + return name;
3.113 + }
3.114 +
3.115 + // implement Index
3.116 + public Storage.EntryType getValueType() throws StorageException
3.117 + {
3.118 + return valueType;
3.119 + }
3.120 +
3.121 + // implement Index
3.122 + public Storage.EntryType getKeyType() throws StorageException
3.123 + {
3.124 + return keyType;
3.125 + }
3.126 +
3.127 + // implement Index
3.128 + public Set keySet() throws StorageException
3.129 + {
3.130 + return new JdbcSet(
3.131 + storage,
3.132 + getKeyType(),
3.133 + sqlKeySetIterator,sqlKeySetSize,sqlKeySetContains);
3.134 + }
3.135 +
3.136 + // implement Index
3.137 + public void add(Object key, Object value) throws StorageException
3.138 + {
3.139 + addImpl(key,value);
3.140 + }
3.141 +
3.142 + protected void addImpl(Object key, Object value) throws StorageException
3.143 + {
3.144 + Object [] args;
3.145 + if (needSurrogate) {
3.146 + args = new Object[]{key,value,new Long(storage.getSerialNumber())};
3.147 + } else {
3.148 + args = new Object[]{key,value};
3.149 + }
3.150 + storage.executeUpdate(sqlInsert,args);
3.151 + }
3.152 +
3.153 + // implement Index
3.154 + public boolean remove(Object key) throws StorageException
3.155 + {
3.156 + return removeImpl(key);
3.157 + }
3.158 +
3.159 + protected boolean removeImpl(Object key) throws StorageException
3.160 + {
3.161 + return storage.executeUpdate(sqlDelete,new Object[]{key}) > 0;
3.162 + }
3.163 +}
3.164 +
3.165 +// End JdbcIndex.java
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcMultivaluedIndex.java Wed Feb 04 06:38:10 2004 +0000
4.3 @@ -0,0 +1,214 @@
4.4 +/*
4.5 + * Sun Public License Notice
4.6 + *
4.7 + * The contents of this file are subject to the Sun Public License
4.8 + * Version 1.0 (the "License"). You may not use this file except in
4.9 + * compliance with the License. A copy of the License is available at
4.10 + * http://www.sun.com/
4.11 + *
4.12 + * The Original Code is NetBeans. The Initial Developer of the Original
4.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
4.14 + * Microsystems, Inc. All Rights Reserved.
4.15 + */
4.16 +package org.netbeans.mdr.persistence.jdbcimpl;
4.17 +
4.18 +import org.netbeans.mdr.persistence.*;
4.19 +import org.netbeans.mdr.util.*;
4.20 +
4.21 +import java.util.*;
4.22 +import java.io.*;
4.23 +
4.24 +/**
4.25 + * JdbcMultivaluedIndex implements the MDR MultivaluedIndex interface using
4.26 + * JDBC.
4.27 + *
4.28 + * @author John V. Sichi
4.29 + * @version $Id$
4.30 + */
4.31 +class JdbcMultivaluedIndex
4.32 + extends JdbcIndex implements MultivaluedIndex
4.33 +{
4.34 + protected LazyPreparedStatement sqlDeleteWithValue;
4.35 + protected LazyPreparedStatement sqlFindCount;
4.36 + protected LazyPreparedStatement sqlFindWithValue;
4.37 +
4.38 + protected void defineSql()
4.39 + {
4.40 + super.defineSql();
4.41 +
4.42 + sqlDeleteWithValue = new LazyPreparedStatement(
4.43 + "delete from " + tableName
4.44 + + " where " + keyColName + " = ?"
4.45 + + " and " + valColName + " = ?");
4.46 +
4.47 + sqlFindCount = new LazyPreparedStatement(
4.48 + "select count(*) from " + tableName
4.49 + + " where " + keyColName + " = ?");
4.50 +
4.51 + sqlFindWithValue = new LazyPreparedStatement(
4.52 + "select count(*) from " + tableName
4.53 + + " where " + keyColName + " = ?"
4.54 + + " and " + valColName + " = ?");
4.55 + }
4.56 +
4.57 + // implement MultivaluedIndex
4.58 + public Collection getItems(Object key) throws StorageException
4.59 + {
4.60 + return new ItemCollection(key,null);
4.61 + }
4.62 +
4.63 + // implement MultivaluedIndex
4.64 + public Collection getObjects(
4.65 + Object key, SinglevaluedIndex repos) throws StorageException
4.66 + {
4.67 + if (keyType == Storage.EntryType.MOFID) {
4.68 + return new ItemCollection(key,repos);
4.69 + } else {
4.70 + return getItems(key);
4.71 + }
4.72 + }
4.73 +
4.74 + // implement MultivaluedIndex
4.75 + public boolean isUnique() throws StorageException
4.76 + {
4.77 + return !needSurrogate;
4.78 + }
4.79 +
4.80 + // override JdbcIndex
4.81 + public void add(Object key, Object value) throws StorageException
4.82 + {
4.83 + if (!needSurrogate) {
4.84 + // REVIEW: Values are supposed to be unique. Apparently this means
4.85 + // duplicates are discarded rather than causing errors, since
4.86 + // during MOF boot duplicate keys are generated. It would be most
4.87 + // efficient to get the INSERT statement to detect the problem and
4.88 + // suppress the resulting exception; however, that requires
4.89 + // detailed backend knowledge. Instead, we prevent the error by
4.90 + // checking first. What I don't understand is why this isn't an
4.91 + // issue with BtreeStorage, which also enforces constraints.
4.92 + int n = storage.getSingletonInt(
4.93 + sqlFindWithValue,
4.94 + new Object[]{key,value});
4.95 + if (n == 1) {
4.96 + return;
4.97 + }
4.98 + // TODO: assert n == 0
4.99 + }
4.100 + super.add(key,value);
4.101 + }
4.102 +
4.103 + // implement MultivaluedIndex
4.104 + public boolean remove(Object key, Object value) throws StorageException
4.105 + {
4.106 + // REVIEW: spec says we're only supposed to delete the "first"
4.107 + // one; does this matter?
4.108 + return storage.executeUpdate(
4.109 + sqlDeleteWithValue,new Object[]{key,value}) > 0;
4.110 + }
4.111 +
4.112 + // implement MultivaluedIndex
4.113 + public Collection queryByKeyPrefix(
4.114 + Object prefix, SinglevaluedIndex repos) throws StorageException
4.115 + {
4.116 + // TODO:
4.117 + throw new RuntimeException("oops, not yet implemented");
4.118 + }
4.119 +
4.120 + private class ItemCollection extends AbstractCollection
4.121 + {
4.122 + private Object key;
4.123 + private SinglevaluedIndex repos;
4.124 +
4.125 + ItemCollection(Object key,SinglevaluedIndex repos)
4.126 + {
4.127 + this.key = key;
4.128 + this.repos = repos;
4.129 + }
4.130 +
4.131 + // implement AbstractCollection
4.132 + public Iterator iterator()
4.133 + {
4.134 + try {
4.135 + return new ItemCollectionIter(
4.136 + storage.getResultSetIterator(
4.137 + sqlFind,
4.138 + new Object[]{key},
4.139 + getValueType()),
4.140 + repos,
4.141 + key);
4.142 + } catch (StorageException ex) {
4.143 + throw new RuntimeStorageException(ex);
4.144 + }
4.145 + }
4.146 +
4.147 + public int size()
4.148 + {
4.149 + try {
4.150 + return storage.getSingletonInt(sqlFindCount,new Object[]{key});
4.151 + } catch (StorageException ex) {
4.152 + throw new RuntimeStorageException(ex);
4.153 + }
4.154 + }
4.155 +
4.156 + // implement AbstractCollection
4.157 + public boolean add(Object value)
4.158 + {
4.159 + try {
4.160 + JdbcMultivaluedIndex.this.add(key,value);
4.161 + return true;
4.162 + } catch (StorageException ex) {
4.163 + throw new RuntimeStorageException(ex);
4.164 + }
4.165 + }
4.166 + }
4.167 +
4.168 + private class ItemCollectionIter implements Iterator
4.169 + {
4.170 + private Iterator iter;
4.171 + private SinglevaluedIndex repos;
4.172 + private Object key;
4.173 + private Object value;
4.174 +
4.175 + ItemCollectionIter(Iterator iter,SinglevaluedIndex repos,Object key)
4.176 + {
4.177 + this.iter = iter;
4.178 + this.repos = repos;
4.179 + this.key = key;
4.180 + }
4.181 +
4.182 + // implement Iterator
4.183 + public boolean hasNext()
4.184 + {
4.185 + return iter.hasNext();
4.186 + }
4.187 +
4.188 + // implement Iterator
4.189 + public Object next()
4.190 + {
4.191 + value = iter.next();
4.192 + if (repos != null) {
4.193 + try {
4.194 + value = repos.get(value);
4.195 + } catch (StorageException ex) {
4.196 + throw new RuntimeStorageException(ex);
4.197 + }
4.198 + }
4.199 + return value;
4.200 + }
4.201 +
4.202 + // implement Iterator
4.203 + public void remove()
4.204 + {
4.205 + // REVIEW: if values aren't unique, this will remove
4.206 + // all matching values; does this matter? if so, need
4.207 + // to remember the surrogate and use it to narrow delete
4.208 + try {
4.209 + JdbcMultivaluedIndex.this.remove(key,value);
4.210 + } catch (StorageException ex) {
4.211 + throw new RuntimeStorageException(ex);
4.212 + }
4.213 + }
4.214 + }
4.215 +}
4.216 +
4.217 +// End JdbcMultivaluedIndex.java
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcMultivaluedOrderedIndex.java Wed Feb 04 06:38:10 2004 +0000
5.3 @@ -0,0 +1,312 @@
5.4 +/*
5.5 + * Sun Public License Notice
5.6 + *
5.7 + * The contents of this file are subject to the Sun Public License
5.8 + * Version 1.0 (the "License"). You may not use this file except in
5.9 + * compliance with the License. A copy of the License is available at
5.10 + * http://www.sun.com/
5.11 + *
5.12 + * The Original Code is NetBeans. The Initial Developer of the Original
5.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
5.14 + * Microsystems, Inc. All Rights Reserved.
5.15 + */
5.16 +package org.netbeans.mdr.persistence.jdbcimpl;
5.17 +
5.18 +import org.netbeans.mdr.persistence.*;
5.19 +import org.netbeans.mdr.util.*;
5.20 +
5.21 +import java.sql.*;
5.22 +import java.util.*;
5.23 +import java.io.*;
5.24 +
5.25 +/**
5.26 + * JdbcMultivaluedOrderedIndex implements the MDR MultivaluedOrderedIndex
5.27 + * using JDBC.
5.28 + *
5.29 + * @author John V. Sichi
5.30 + * @version $Id$
5.31 + */
5.32 +class JdbcMultivaluedOrderedIndex
5.33 + extends JdbcMultivaluedIndex implements MultivaluedOrderedIndex
5.34 +{
5.35 + protected LazyPreparedStatement sqlInsertWithOrdinal;
5.36 + protected LazyPreparedStatement sqlUpdateWithOrdinal;
5.37 + protected LazyPreparedStatement sqlDeleteWithOrdinal;
5.38 + protected LazyPreparedStatement sqlShiftOrdinals;
5.39 + protected LazyPreparedStatement sqlFindOrdered;
5.40 +
5.41 + protected void defineSql()
5.42 + {
5.43 + super.defineSql();
5.44 +
5.45 + // TODO: assert needSurrogate
5.46 +
5.47 + sqlInsertWithOrdinal = new LazyPreparedStatement(
5.48 + "insert into " + tableName
5.49 + + " values(?,?,?,?)");
5.50 +
5.51 + sqlUpdateWithOrdinal = new LazyPreparedStatement(
5.52 + "update " + tableName + " set " + valColName
5.53 + + " = ? where " + keyColName + " = ? and "
5.54 + + storage.ORDINAL_COL_NAME + " = ?");
5.55 +
5.56 + sqlDeleteWithOrdinal = new LazyPreparedStatement(
5.57 + "delete from " + tableName
5.58 + + " where " + keyColName + " = ?"
5.59 + + " and " + storage.ORDINAL_COL_NAME + " = ?");
5.60 +
5.61 + sqlShiftOrdinals = new LazyPreparedStatement(
5.62 + "update " + tableName + " set "
5.63 + + storage.ORDINAL_COL_NAME + " = " + storage.ORDINAL_COL_NAME
5.64 + + " + ? where " + keyColName + " = ? and "
5.65 + + storage.ORDINAL_COL_NAME + " >= ?");
5.66 +
5.67 + // NOTE: keyColName is redundant in ORDER BY, but maybe it improves
5.68 + // the chances that even a stupid optimizer will realize it can use the
5.69 + // primary key index for ordering
5.70 + sqlFindOrdered = new LazyPreparedStatement(
5.71 + "select " + valColName + " from " + tableName
5.72 + + " where " + keyColName + " = ? order by "
5.73 + + keyColName + ", " + storage.ORDINAL_COL_NAME);
5.74 + }
5.75 +
5.76 + // implement MultivaluedOrderedIndex
5.77 + public List getItemsOrdered(Object key) throws StorageException
5.78 + {
5.79 + return new ItemList(key,null);
5.80 + }
5.81 +
5.82 + // implement MultivaluedOrderedIndex
5.83 + public Collection getObjectsOrdered(
5.84 + Object key, SinglevaluedIndex repos) throws StorageException
5.85 + {
5.86 + if (keyType == Storage.EntryType.MOFID) {
5.87 + return new ItemList(key,repos);
5.88 + } else {
5.89 + return getItemsOrdered(key);
5.90 + }
5.91 + }
5.92 +
5.93 + // implement MultivaluedOrderedIndex
5.94 + public void add(Object key, int index, Object value)
5.95 + throws StorageException
5.96 + {
5.97 + addImpl(key,index,value,true);
5.98 + }
5.99 +
5.100 + // override JdbcIndex
5.101 + public void add(Object key, Object value) throws StorageException
5.102 + {
5.103 + // Have to query to get the current size as the new index. But as an
5.104 + // optimization, there's no need to shift the ordinals of the existing
5.105 + // entries.
5.106 + int index = getItemsOrdered(key).size();
5.107 + addImpl(key,index,value,false);
5.108 + }
5.109 +
5.110 + private void addImpl(
5.111 + Object key, int index, Object value,boolean shiftOrdinals)
5.112 + throws StorageException
5.113 + {
5.114 + if (shiftOrdinals) {
5.115 + storage.executeUpdate(
5.116 + sqlShiftOrdinals,
5.117 + new Object[]{new Integer(1),key,new Integer(index)});
5.118 + }
5.119 +
5.120 + Object [] args = new Object[]{
5.121 + key,value,new Integer(index),
5.122 + new Long(storage.getSerialNumber())};
5.123 + storage.executeUpdate(sqlInsertWithOrdinal,args);
5.124 + }
5.125 +
5.126 + // implement MultivaluedOrderedIndex
5.127 + public boolean remove(Object key, int index)
5.128 + throws StorageException
5.129 + {
5.130 + return removeImpl(key,index,true);
5.131 + }
5.132 +
5.133 + private boolean removeImpl(Object key, int index,boolean shiftOrdinals)
5.134 + throws StorageException
5.135 + {
5.136 + storage.executeUpdate(
5.137 + sqlDeleteWithOrdinal,
5.138 + new Object[]{key,new Integer(index)});
5.139 +
5.140 + if (shiftOrdinals) {
5.141 + storage.executeUpdate(
5.142 + sqlShiftOrdinals,
5.143 + new Object[]{new Integer(-1),key,new Integer(index)});
5.144 + }
5.145 +
5.146 + return true;
5.147 + }
5.148 +
5.149 + // implement MultivaluedOrderedIndex
5.150 + public void replace(Object key, int index, Object element)
5.151 + throws StorageException
5.152 + {
5.153 + storage.executeUpdate(
5.154 + sqlUpdateWithOrdinal,
5.155 + new Object[]{element,key,new Integer(index)});
5.156 + }
5.157 +
5.158 + private class ItemList extends AbstractSequentialList
5.159 + {
5.160 + private Object key;
5.161 + private SinglevaluedIndex repos;
5.162 +
5.163 + ItemList(Object key,SinglevaluedIndex repos)
5.164 + {
5.165 + this.key = key;
5.166 + this.repos = repos;
5.167 + }
5.168 +
5.169 + // implement AbstractSequentialList
5.170 + public ListIterator listIterator(int index)
5.171 + {
5.172 + try {
5.173 + ListIterator iter = new ItemListIter(
5.174 + storage.getResultSetIterator(
5.175 + sqlFindOrdered,
5.176 + new Object[]{key},
5.177 + getValueType()),
5.178 + repos,
5.179 + key);
5.180 + while (iter.nextIndex() != index) {
5.181 + iter.next();
5.182 + }
5.183 + return iter;
5.184 + } catch (StorageException ex) {
5.185 + throw new RuntimeStorageException(ex);
5.186 + }
5.187 + }
5.188 +
5.189 + // implement AbstractSequentialList
5.190 + public int size()
5.191 + {
5.192 + try {
5.193 + return storage.getSingletonInt(sqlFindCount,new Object[]{key});
5.194 + } catch (StorageException ex) {
5.195 + throw new RuntimeStorageException(ex);
5.196 + }
5.197 + }
5.198 + }
5.199 +
5.200 + private class ItemListIter implements ListIterator
5.201 + {
5.202 + private ListIterator iter;
5.203 + private SinglevaluedIndex repos;
5.204 + private Object key;
5.205 + private int lastPos;
5.206 +
5.207 + ItemListIter(
5.208 + ListIterator iter,SinglevaluedIndex repos,Object key)
5.209 + {
5.210 + this.iter = iter;
5.211 + this.repos = repos;
5.212 + this.key = key;
5.213 +
5.214 + lastPos = -1;
5.215 + }
5.216 +
5.217 + // implement ListIterator
5.218 + public void add(Object obj)
5.219 + {
5.220 + try {
5.221 + int index = nextIndex();
5.222 + // if at end, no need to shift ordinals
5.223 + addImpl(key,index,obj,iter.hasNext());
5.224 + } catch (StorageException ex) {
5.225 + throw new RuntimeStorageException(ex);
5.226 + }
5.227 + iter.add(obj);
5.228 + lastPos = -1;
5.229 + }
5.230 +
5.231 + // implement ListIterator
5.232 + public boolean hasNext()
5.233 + {
5.234 + return iter.hasNext();
5.235 + }
5.236 +
5.237 + // implement ListIterator
5.238 + public boolean hasPrevious()
5.239 + {
5.240 + return iter.hasPrevious();
5.241 + }
5.242 +
5.243 + private Object getValue(Object obj)
5.244 + {
5.245 + if (repos != null) {
5.246 + try {
5.247 + return repos.get(obj);
5.248 + } catch (StorageException ex) {
5.249 + throw new RuntimeStorageException(ex);
5.250 + }
5.251 + } else {
5.252 + return obj;
5.253 + }
5.254 + }
5.255 +
5.256 + // implement ListIterator
5.257 + public Object next()
5.258 + {
5.259 + lastPos = iter.nextIndex();
5.260 + return getValue(iter.next());
5.261 + }
5.262 +
5.263 + // implement ListIterator
5.264 + public int nextIndex()
5.265 + {
5.266 + return iter.nextIndex();
5.267 + }
5.268 +
5.269 + // implement ListIterator
5.270 + public Object previous()
5.271 + {
5.272 + lastPos = iter.previousIndex();
5.273 + return getValue(iter.previous());
5.274 + }
5.275 +
5.276 + // implement ListIterator
5.277 + public int previousIndex()
5.278 + {
5.279 + return iter.previousIndex();
5.280 + }
5.281 +
5.282 + // implement ListIterator
5.283 + public void remove()
5.284 + {
5.285 + if (lastPos == -1) {
5.286 + throw new IllegalStateException();
5.287 + }
5.288 + // remove from iter first, in case we just did a previous() from end
5.289 + iter.remove();
5.290 + try {
5.291 + // if at end, no need to shift ordinals
5.292 + removeImpl(
5.293 + key,lastPos,iter.hasNext());
5.294 + } catch (StorageException ex) {
5.295 + throw new RuntimeStorageException(ex);
5.296 + }
5.297 + lastPos = -1;
5.298 + }
5.299 +
5.300 + // implement ListIterator
5.301 + public void set(Object obj)
5.302 + {
5.303 + iter.set(obj);
5.304 + try {
5.305 + JdbcMultivaluedOrderedIndex.this.replace(
5.306 + key,lastPos,obj);
5.307 + } catch (StorageException ex) {
5.308 + throw new RuntimeStorageException(ex);
5.309 + }
5.310 + lastPos = -1;
5.311 + }
5.312 + }
5.313 +}
5.314 +
5.315 +// End JdbcMultivaluedOrderedIndex.java
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcPrimaryIndex.java Wed Feb 04 06:38:10 2004 +0000
6.3 @@ -0,0 +1,238 @@
6.4 +/*
6.5 + * Sun Public License Notice
6.6 + *
6.7 + * The contents of this file are subject to the Sun Public License
6.8 + * Version 1.0 (the "License"). You may not use this file except in
6.9 + * compliance with the License. A copy of the License is available at
6.10 + * http://www.sun.com/
6.11 + *
6.12 + * The Original Code is NetBeans. The Initial Developer of the Original
6.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
6.14 + * Microsystems, Inc. All Rights Reserved.
6.15 + */
6.16 +package org.netbeans.mdr.persistence.jdbcimpl;
6.17 +
6.18 +import org.netbeans.mdr.persistence.btreeimpl.btreestorage.*;
6.19 +import org.netbeans.mdr.persistence.*;
6.20 +
6.21 +import java.io.*;
6.22 +import java.text.*;
6.23 +import java.util.*;
6.24 +
6.25 +/**
6.26 + * JdbcPrimaryIndex implements primary index storage using JDBC.
6.27 + *
6.28 + * @author John V. Sichi
6.29 + * @version $Id$
6.30 + */
6.31 +class JdbcPrimaryIndex
6.32 + extends JdbcSinglevaluedIndex implements MDRCache.OverflowHandler
6.33 +{
6.34 + private final MDRCache cache;
6.35 +
6.36 + // NOTE: most of this class was ripped from BtreeDatabase
6.37 + static final int MDR_CACHE_SIZE = 2048;
6.38 + static final int MDR_CACHE_THRESHHOLD = 1000;
6.39 +
6.40 + JdbcPrimaryIndex()
6.41 + {
6.42 + cache = new MDRCache(MDR_CACHE_SIZE, this, MDR_CACHE_THRESHHOLD);
6.43 + }
6.44 +
6.45 + // implement MDRCache.OverflowHandler
6.46 + public void cacheThreshholdReached(MDRCache cache, int size)
6.47 + throws StorageException
6.48 + {
6.49 + flushChanges();
6.50 + }
6.51 +
6.52 + void flushChanges()
6.53 + throws StorageException
6.54 + {
6.55 + // TODO: optimize with batched JDBC calls where supported
6.56 + Iterator iter = cache.iterateDeleted();
6.57 + while (iter.hasNext()) {
6.58 + super.removeImpl(iter.next());
6.59 + }
6.60 + iter = cache.getDirty().iterator();
6.61 + while (iter.hasNext()) {
6.62 + Object key = iter.next();
6.63 + super.replaceImpl(key,cache.get(key));
6.64 + }
6.65 + iter = cache.iterateNew();
6.66 + while (iter.hasNext()) {
6.67 + Object key = iter.next();
6.68 + super.addImpl(key,cache.get(key));
6.69 + }
6.70 + cache.clearLists();
6.71 + }
6.72 +
6.73 + private MOFID makeMOFID(Object key) throws StorageException
6.74 + {
6.75 + if (key instanceof MOFID) {
6.76 + return (MOFID) key;
6.77 + } else {
6.78 + throw new IllegalArgumentException(
6.79 + "Argument must be of org.netbeans.mdr.persistence.MOFID type");
6.80 + }
6.81 + }
6.82 +
6.83 + void objectStateChanged(Object key) throws StorageException
6.84 + {
6.85 + cache.setDirty(makeMOFID(key));
6.86 + }
6.87 +
6.88 + private boolean exists(MOFID key) throws StorageException
6.89 + {
6.90 + if (cache.get(key) != null) {
6.91 + return true;
6.92 + } else if (cache.isDeleted(key)) {
6.93 + return false;
6.94 + } else {
6.95 + return super.getIfExists(key) != null;
6.96 + }
6.97 + }
6.98 +
6.99 + private void noSuchRecord(Object key) throws StorageException
6.100 + {
6.101 + throw new StorageBadRequestException(
6.102 + MessageFormat.format("No record exists with key {0}",
6.103 + new Object[] {key} ) );
6.104 + }
6.105 +
6.106 + private void addToCache(MOFID key, Object value) throws StorageException
6.107 + {
6.108 + cache.put(key, value);
6.109 + cache.setNew(key);
6.110 + }
6.111 +
6.112 + private void replaceInCache(MOFID key, Object value)
6.113 + throws StorageException
6.114 + {
6.115 + boolean isNew = cache.isNew(key);
6.116 +
6.117 + // TODO: should be cache.replace(key, value);
6.118 + AccessHack.cacheReplace(cache,key,value);
6.119 +
6.120 + if (isNew) {
6.121 + cache.setNew(key);
6.122 + } else {
6.123 + cache.setDirty(key);
6.124 + }
6.125 + }
6.126 +
6.127 + // override JdbcSinglevaluedIndex
6.128 + public synchronized boolean put(
6.129 + Object key,Object value) throws StorageException
6.130 + {
6.131 + MOFID mKey = makeMOFID(key);
6.132 + if (!exists(mKey)) {
6.133 + addToCache(mKey,value);
6.134 + return false;
6.135 + } else {
6.136 + replaceInCache(mKey,value);
6.137 + return true;
6.138 + }
6.139 + }
6.140 +
6.141 + // override JdbcSinglevaluedIndex
6.142 + public synchronized void replace(Object key, Object value)
6.143 + throws StorageException, StorageBadRequestException
6.144 + {
6.145 + MOFID mKey = makeMOFID(key);
6.146 +
6.147 + if (!exists(mKey)) {
6.148 + noSuchRecord(mKey);
6.149 + }
6.150 +
6.151 + replaceInCache(mKey, value);
6.152 + }
6.153 +
6.154 + // override JdbcSinglevaluedIndex
6.155 + public Object get(Object key)
6.156 + throws StorageException, StorageBadRequestException
6.157 + {
6.158 + Object retval = getIfExists(key);
6.159 + if (retval == null) {
6.160 + noSuchRecord(key);
6.161 + }
6.162 +
6.163 + return retval;
6.164 + }
6.165 +
6.166 + // override JdbcSinglevaluedIndex
6.167 + public Object getObject(Object key, SinglevaluedIndex repos)
6.168 + throws StorageException
6.169 + {
6.170 + return get(key);
6.171 + }
6.172 +
6.173 + // override JdbcSinglevaluedIndex
6.174 + public synchronized Object getIfExists(Object key) throws StorageException
6.175 + {
6.176 + MOFID mKey = makeMOFID(key);
6.177 + Object retval;
6.178 + retval = cache.get(mKey);
6.179 + if (retval == null) {
6.180 + if (cache.isDeleted(mKey)) {
6.181 + return null;
6.182 + }
6.183 + retval = super.getIfExists(mKey);
6.184 + if (retval != null) {
6.185 + cache.put(mKey, retval);
6.186 + }
6.187 + }
6.188 +
6.189 + return retval;
6.190 + }
6.191 +
6.192 + // override JdbcSinglevaluedIndex
6.193 + public Object getObjectIfExists(Object key, SinglevaluedIndex repos)
6.194 + throws StorageException
6.195 + {
6.196 + return getIfExists(key);
6.197 + }
6.198 +
6.199 + // NOTE: it's a pain to implement keySet() and values() correctly,
6.200 + // and as far as I can tell they're never used for primary indexes
6.201 +
6.202 + // override JdbcSinglevaluedIndex
6.203 + public Collection values()
6.204 + throws StorageException
6.205 + {
6.206 + throw new RuntimeException("oops, not yet implemented");
6.207 + }
6.208 +
6.209 + // override JdbcIndex
6.210 + public Set keySet() throws StorageException
6.211 + {
6.212 + throw new RuntimeException("oops, not yet implemented");
6.213 + }
6.214 +
6.215 + // override JdbcIndex
6.216 + public synchronized void add(
6.217 + Object key, Object value) throws StorageException
6.218 + {
6.219 + MOFID mKey = makeMOFID(key);
6.220 + if (exists(mKey)) {
6.221 + throw new StorageBadRequestException(
6.222 + MessageFormat.format("Record with key {0} already exists",
6.223 + new Object[] {mKey} ) );
6.224 + }
6.225 + addToCache(mKey, value);
6.226 + }
6.227 +
6.228 + // override JdbcIndex
6.229 + public synchronized boolean remove(Object key) throws StorageException
6.230 + {
6.231 + MOFID mKey = makeMOFID(key);
6.232 + if (!exists(mKey)) {
6.233 + return false;
6.234 + } else {
6.235 + cache.remove(mKey);
6.236 + return true;
6.237 + }
6.238 + }
6.239 +}
6.240 +
6.241 +// End JdbcPrimaryIndex.java
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcSet.java Wed Feb 04 06:38:10 2004 +0000
7.3 @@ -0,0 +1,86 @@
7.4 +/*
7.5 + * Sun Public License Notice
7.6 + *
7.7 + * The contents of this file are subject to the Sun Public License
7.8 + * Version 1.0 (the "License"). You may not use this file except in
7.9 + * compliance with the License. A copy of the License is available at
7.10 + * http://www.sun.com/
7.11 + *
7.12 + * The Original Code is NetBeans. The Initial Developer of the Original
7.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
7.14 + * Microsystems, Inc. All Rights Reserved.
7.15 + */
7.16 +package org.netbeans.mdr.persistence.jdbcimpl;
7.17 +
7.18 +import org.netbeans.mdr.persistence.*;
7.19 +
7.20 +import java.util.*;
7.21 +
7.22 +/**
7.23 + * JdbcSet is a read-only Set backed by queries.
7.24 + *
7.25 + * @author John V. Sichi
7.26 + * @version $Id$
7.27 + */
7.28 +class JdbcSet extends AbstractSet
7.29 +{
7.30 + private final JdbcStorage storage;
7.31 + private final Storage.EntryType entryType;
7.32 + private final LazyPreparedStatement sqlIterator;
7.33 + private final LazyPreparedStatement sqlSize;
7.34 + private final LazyPreparedStatement sqlContains;
7.35 +
7.36 + JdbcSet(
7.37 + JdbcStorage storage,
7.38 + Storage.EntryType entryType,
7.39 + LazyPreparedStatement sqlIterator,
7.40 + LazyPreparedStatement sqlSize,
7.41 + LazyPreparedStatement sqlContains)
7.42 + {
7.43 + this.storage = storage;
7.44 + this.entryType = entryType;
7.45 + this.sqlIterator = sqlIterator;
7.46 + this.sqlSize = sqlSize;
7.47 + this.sqlContains = sqlContains;
7.48 + }
7.49 +
7.50 + // implement AbstractSet
7.51 + public Iterator iterator()
7.52 + {
7.53 + try {
7.54 + return storage.getResultSetIterator(sqlIterator,null,entryType);
7.55 + } catch (StorageException ex) {
7.56 + throw new RuntimeStorageException(ex);
7.57 + }
7.58 + }
7.59 +
7.60 + // override AbstractSet
7.61 + public boolean contains(Object obj)
7.62 + {
7.63 + try {
7.64 + int n = storage.getSingletonInt(
7.65 + sqlContains,new Object[]{obj});
7.66 + return n > 0;
7.67 + } catch (StorageException ex) {
7.68 + throw new RuntimeStorageException(ex);
7.69 + }
7.70 + }
7.71 +
7.72 + // implement AbstractSet
7.73 + public int size()
7.74 + {
7.75 + try {
7.76 + if (sqlSize != null) {
7.77 + return storage.getSingletonInt(sqlSize,null);
7.78 + } else {
7.79 + // this could be used to compensate for a DBMS without
7.80 + // COUNT(DISTINCT)
7.81 + return storage.getResultSetCount(sqlIterator,null);
7.82 + }
7.83 + } catch (StorageException ex) {
7.84 + throw new RuntimeStorageException(ex);
7.85 + }
7.86 + }
7.87 +}
7.88 +
7.89 +// End JdbcSet.java
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcSinglevaluedIndex.java Wed Feb 04 06:38:10 2004 +0000
8.3 @@ -0,0 +1,170 @@
8.4 +/*
8.5 + * Sun Public License Notice
8.6 + *
8.7 + * The contents of this file are subject to the Sun Public License
8.8 + * Version 1.0 (the "License"). You may not use this file except in
8.9 + * compliance with the License. A copy of the License is available at
8.10 + * http://www.sun.com/
8.11 + *
8.12 + * The Original Code is NetBeans. The Initial Developer of the Original
8.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
8.14 + * Microsystems, Inc. All Rights Reserved.
8.15 + */
8.16 +package org.netbeans.mdr.persistence.jdbcimpl;
8.17 +
8.18 +import org.netbeans.mdr.persistence.*;
8.19 +import org.netbeans.mdr.util.*;
8.20 +
8.21 +import java.util.*;
8.22 +import java.io.*;
8.23 +
8.24 +/**
8.25 + * JdbcSinglevaluedIndex implements the MDR SinglevaluedIndex interface
8.26 + * using JDBC.
8.27 + *
8.28 + * @author John V. Sichi
8.29 + * @version $Id$
8.30 + */
8.31 +class JdbcSinglevaluedIndex
8.32 + extends JdbcIndex implements SinglevaluedIndex
8.33 +{
8.34 + protected LazyPreparedStatement sqlValuesIterator;
8.35 + protected LazyPreparedStatement sqlValuesSize;
8.36 + protected LazyPreparedStatement sqlValuesContains;
8.37 + protected LazyPreparedStatement sqlUpdate;
8.38 +
8.39 + protected void defineSql()
8.40 + {
8.41 + super.defineSql();
8.42 +
8.43 + sqlValuesIterator = new LazyPreparedStatement(
8.44 + "select " + valColName + " from " + tableName);
8.45 +
8.46 + sqlValuesSize = new LazyPreparedStatement(
8.47 + "select count(*) from " + tableName);
8.48 +
8.49 + sqlValuesContains = new LazyPreparedStatement(
8.50 + "select count(*) from " + tableName + " where "
8.51 + + valColName + " = ?");
8.52 +
8.53 + sqlUpdate = new LazyPreparedStatement(
8.54 + "update " + tableName + " set " + valColName
8.55 + + " = ? where " + keyColName + " = ?");
8.56 + }
8.57 +
8.58 + protected boolean isKeyUnique()
8.59 + {
8.60 + return true;
8.61 + }
8.62 +
8.63 + // implement SinglevaluedIndex
8.64 + public boolean put(Object key,Object value) throws StorageException
8.65 + {
8.66 + return putImpl(key,value);
8.67 + }
8.68 +
8.69 + protected boolean putImpl(Object key,Object value) throws StorageException
8.70 + {
8.71 + // REVIEW: this optimizes for update rather than insert; could
8.72 + // use UPSERT on databases that support it
8.73 + int rowCount = storage.executeUpdate(
8.74 + sqlUpdate,new Object[]{value,key});
8.75 + // TODO: assert rowCount == 0 or 1
8.76 + if (rowCount == 0) {
8.77 + // key did not exist; insert instead
8.78 + addImpl(key,value);
8.79 + return false;
8.80 + } else {
8.81 + // key existed and has already been updated
8.82 + return true;
8.83 + }
8.84 + }
8.85 +
8.86 + // implement SinglevaluedIndex
8.87 + public void replace(Object key, Object value)
8.88 + throws StorageException, StorageBadRequestException
8.89 + {
8.90 + replaceImpl(key,value);
8.91 + }
8.92 +
8.93 + protected void replaceImpl(Object key, Object value)
8.94 + throws StorageException, StorageBadRequestException
8.95 + {
8.96 + if (!putImpl(key,value)) {
8.97 + throw new StorageBadRequestException(
8.98 + "Cannot replace item that does not exist in the index.");
8.99 + }
8.100 + }
8.101 +
8.102 + // implement SinglevaluedIndex
8.103 + public Object get(Object key)
8.104 + throws StorageException, StorageBadRequestException
8.105 + {
8.106 + Object obj = getIfExists(key);
8.107 + if (obj == null) {
8.108 + throw new StorageBadRequestException ("Item not found: " + key);
8.109 + }
8.110 + return obj;
8.111 + }
8.112 +
8.113 + // implement SinglevaluedIndex
8.114 + public Object getObject(Object key, SinglevaluedIndex repos)
8.115 + throws StorageException
8.116 + {
8.117 + // NOTE: If JdbcPrimaryIndex did no caching, we could optimize this
8.118 + // with an SQL join. But we can't join against the cache. Same goes
8.119 + // for all of the other calls that take a repos argument.
8.120 + if (keyType == Storage.EntryType.MOFID) {
8.121 + return repos.get(get(key));
8.122 + } else {
8.123 + return get(key);
8.124 + }
8.125 + }
8.126 +
8.127 + // implement SinglevaluedIndex
8.128 + public Object getIfExists(Object key) throws StorageException
8.129 + {
8.130 + Iterator iter = storage.getResultSetIterator(
8.131 + sqlFind,new Object[]{key},getValueType());
8.132 + if (!iter.hasNext()) {
8.133 + return null;
8.134 + }
8.135 + return iter.next();
8.136 + }
8.137 +
8.138 + // implement SinglevaluedIndex
8.139 + public Object getObjectIfExists(Object key, SinglevaluedIndex repos)
8.140 + throws StorageException
8.141 + {
8.142 + Object val = getIfExists(key);
8.143 + if (val == null) {
8.144 + return null;
8.145 + } else {
8.146 + if (keyType == Storage.EntryType.MOFID) {
8.147 + return repos.get(val);
8.148 + } else {
8.149 + return val;
8.150 + }
8.151 + }
8.152 + }
8.153 +
8.154 + // implement SinglevaluedIndex
8.155 + public Collection values()
8.156 + throws StorageException
8.157 + {
8.158 + return new JdbcCollection(
8.159 + storage,
8.160 + getValueType(),
8.161 + sqlValuesIterator,sqlValuesSize,sqlValuesContains);
8.162 + }
8.163 +
8.164 + // implement SinglevaluedIndex
8.165 + public Collection queryByKeyPrefix(
8.166 + Object prefix, SinglevaluedIndex repos) throws StorageException
8.167 + {
8.168 + // TODO: optimize with LIKE
8.169 + throw new RuntimeException("oops, not yet implemented");
8.170 + }
8.171 +}
8.172 +
8.173 +// End JdbcSinglevaluedIndex.java
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcStorage.java Wed Feb 04 06:38:10 2004 +0000
9.3 @@ -0,0 +1,934 @@
9.4 +/*
9.5 + * Sun Public License Notice
9.6 + *
9.7 + * The contents of this file are subject to the Sun Public License
9.8 + * Version 1.0 (the "License"). You may not use this file except in
9.9 + * compliance with the License. A copy of the License is available at
9.10 + * http://www.sun.com/
9.11 + *
9.12 + * The Original Code is NetBeans. The Initial Developer of the Original
9.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
9.14 + * Microsystems, Inc. All Rights Reserved.
9.15 + */
9.16 +package org.netbeans.mdr.persistence.jdbcimpl;
9.17 +
9.18 +import org.netbeans.mdr.persistence.*;
9.19 +import org.netbeans.mdr.util.*;
9.20 +
9.21 +import java.sql.*;
9.22 +import java.util.*;
9.23 +import java.io.*;
9.24 +
9.25 +/**
9.26 + * JdbcStorage implements the MDR Storage interface using JDBC.
9.27 + *
9.28 + * @author John V. Sichi
9.29 + * @version $Id$
9.30 + */
9.31 +class JdbcStorage implements Storage
9.32 +{
9.33 + public static final String MOFID_SEQ_TABLE_NAME = "MOFID_SEQ";
9.34 +
9.35 + public static final String MOFID_SEQ_COL_NAME = "MOFID_SEQ_NEXT";
9.36 +
9.37 + public static final String KEY_COL_PREFIX = "IDX_KEY";
9.38 +
9.39 + public static final String SINGLE_VAL_COL_PREFIX = "IDX_SVAL";
9.40 +
9.41 + public static final String MULTI_VAL_COL_PREFIX = "IDX_MVAL";
9.42 +
9.43 + public static final String ORDINAL_COL_NAME = "IDX_ORD";
9.44 +
9.45 + public static final String SURROGATE_COL_NAME = "IDX_SUR";
9.46 +
9.47 + public static final String PRIMARY_INDEX_NAME = "PRIMARY_INDEX";
9.48 +
9.49 + private final Connection jdbcConnection;
9.50 +
9.51 + private final DatabaseMetaData dbMetaData;
9.52 +
9.53 + private final Statement jdbcStmt;
9.54 +
9.55 + private final String idQuote;
9.56 +
9.57 + private final String schemaName;
9.58 +
9.59 + private final String userName;
9.60 +
9.61 + private final String storageId;
9.62 +
9.63 + private final boolean realSchema;
9.64 +
9.65 + private final Map entryTypeToDataTypeMap;
9.66 +
9.67 + private ResultSet jdbcResultSet;
9.68 +
9.69 + private long nextSerialNumber;
9.70 +
9.71 + private Map nameToIndexMap;
9.72 +
9.73 + private LazyPreparedStatement sqlUpdateSerialNumber;
9.74 +
9.75 + // REVIEW: if this gets too bloated for a big model, may need to turn this
9.76 + // into a cache instead of a map
9.77 + private Map sqlToPreparedStatementMap;
9.78 +
9.79 + private JdbcPrimaryIndex primaryIndex;
9.80 +
9.81 + JdbcStorage(
9.82 + Properties properties,
9.83 + String storageId)
9.84 + throws StorageException
9.85 + {
9.86 + this.storageId = storageId;
9.87 +
9.88 + schemaName = properties.getProperty(
9.89 + JdbcStorageFactory.STORAGE_SCHEMA_NAME);
9.90 + userName = properties.getProperty(
9.91 + JdbcStorageFactory.STORAGE_USER_NAME);
9.92 +
9.93 + entryTypeToDataTypeMap = new HashMap();
9.94 + createTypeMap(properties);
9.95 +
9.96 + String url = properties.getProperty(
9.97 + JdbcStorageFactory.STORAGE_URL);
9.98 + String password = properties.getProperty(
9.99 + JdbcStorageFactory.STORAGE_PASSWORD);
9.100 + boolean success = false;
9.101 +
9.102 + try {
9.103 + // REVIEW: maybe connect/disconnect should correspond to
9.104 + // open/close instead of constructor/shutdown?
9.105 + jdbcConnection =
9.106 + DriverManager.getConnection(url,userName,password);
9.107 + jdbcConnection.setAutoCommit(false);
9.108 + jdbcStmt = jdbcConnection.createStatement();
9.109 + dbMetaData = jdbcConnection.getMetaData();
9.110 + realSchema = dbMetaData.supportsSchemasInTableDefinitions();
9.111 + idQuote = dbMetaData.getIdentifierQuoteString();
9.112 + success = true;
9.113 + } catch (SQLException ex) {
9.114 + throw new JdbcStorageException(ex);
9.115 + } finally {
9.116 + if (!success) {
9.117 + shutDown();
9.118 + }
9.119 + }
9.120 + }
9.121 +
9.122 + private void createTypeMap(Properties properties)
9.123 + {
9.124 + entryTypeToDataTypeMap.put(
9.125 + EntryType.MOFID,
9.126 + properties.getProperty(
9.127 + JdbcStorageFactory.STORAGE_DATATYPE_MOFID,
9.128 + "BIGINT"));
9.129 +
9.130 + entryTypeToDataTypeMap.put(
9.131 + EntryType.STREAMABLE,
9.132 + properties.getProperty(
9.133 + JdbcStorageFactory.STORAGE_DATATYPE_STREAMABLE,
9.134 + "LONGVARBINARY"));
9.135 +
9.136 + entryTypeToDataTypeMap.put(
9.137 + EntryType.STRING,
9.138 + properties.getProperty(
9.139 + JdbcStorageFactory.STORAGE_DATATYPE_STRING,
9.140 + "VARCHAR(2000)"));
9.141 +
9.142 + entryTypeToDataTypeMap.put(
9.143 + EntryType.INT,
9.144 + properties.getProperty(
9.145 + JdbcStorageFactory.STORAGE_DATATYPE_INT,
9.146 + "BIGINT"));
9.147 + }
9.148 +
9.149 + // implement Storage
9.150 + public String getName()
9.151 + {
9.152 + return schemaName + ".jdbc";
9.153 + }
9.154 +
9.155 + // implement Storage
9.156 + public String getStorageId ()
9.157 + {
9.158 + return storageId;
9.159 + }
9.160 +
9.161 + private void writeSerialNumber(long serialNumber)
9.162 + throws StorageException
9.163 + {
9.164 + executeUpdate(
9.165 + sqlUpdateSerialNumber,
9.166 + new Object[]{new Long(serialNumber)});
9.167 + }
9.168 +
9.169 + // implement Storage
9.170 + public synchronized long getSerialNumber()
9.171 + {
9.172 + return nextSerialNumber++;
9.173 + }
9.174 +
9.175 + // implement Storage
9.176 + public MOFID readMOFID (java.io.InputStream inputStream)
9.177 + throws StorageException
9.178 + {
9.179 + // NOTE: ripped from memoryimpl
9.180 + try {
9.181 + String storageId = IOUtils.readString(inputStream);
9.182 + if (storageId == null) {
9.183 + storageId = this.storageId;
9.184 + }
9.185 + long serial = IOUtils.readLong(inputStream);
9.186 + return new MOFID(serial, storageId);
9.187 + } catch (java.io.IOException ioException) {
9.188 + throw new StorageIOException(ioException);
9.189 + }
9.190 + }
9.191 +
9.192 + // implement Storage
9.193 + public void writeMOFID (java.io.OutputStream outputStream, MOFID mofid)
9.194 + throws StorageException
9.195 + {
9.196 + // NOTE: ripped from memoryimpl
9.197 + try {
9.198 + if (storageId.equals(mofid.getStorageID())) {
9.199 + IOUtils.writeString(outputStream, null);
9.200 + } else {
9.201 + IOUtils.writeString(outputStream, mofid.getStorageID());
9.202 + }
9.203 + IOUtils.writeLong(outputStream, mofid.getSerialNumber());
9.204 + } catch (IOException ioException) {
9.205 + throw new StorageIOException(ioException);
9.206 + }
9.207 + }
9.208 +
9.209 + private String getQualifiedTableName(String tableName)
9.210 + {
9.211 + if (realSchema) {
9.212 + return idQuote + schemaName + idQuote + "." + idQuote
9.213 + + tableName + idQuote;
9.214 + } else {
9.215 + return idQuote + schemaName + "_" + tableName + idQuote;
9.216 + }
9.217 + }
9.218 +
9.219 + private String getQualifiedSchemaName()
9.220 + {
9.221 + return idQuote + schemaName + idQuote;
9.222 + }
9.223 +
9.224 + private void rollbackConnection()
9.225 + {
9.226 + if (jdbcResultSet != null) {
9.227 + closeResultSet(jdbcResultSet);
9.228 + }
9.229 + try {
9.230 + jdbcConnection.rollback();
9.231 + } catch (SQLException ex) {
9.232 + // TODO: trace
9.233 + }
9.234 + }
9.235 +
9.236 + private long readSerialNumber()
9.237 + {
9.238 + try {
9.239 + jdbcResultSet = jdbcStmt.executeQuery(
9.240 + "select * from "+getQualifiedTableName(MOFID_SEQ_TABLE_NAME));
9.241 + jdbcResultSet.next();
9.242 + long x = jdbcResultSet.getLong(1);
9.243 + return x;
9.244 + } catch (SQLException ex) {
9.245 + return -1;
9.246 + }
9.247 + }
9.248 +
9.249 + // implement Storage
9.250 + public synchronized boolean exists() throws StorageException
9.251 + {
9.252 + long x = readSerialNumber();
9.253 + return x != -1;
9.254 + }
9.255 +
9.256 + // implement Storage
9.257 + public synchronized boolean delete() throws StorageException
9.258 + {
9.259 + rollbackConnection();
9.260 + try {
9.261 + if (realSchema) {
9.262 + jdbcStmt.execute(
9.263 + "drop schema " + getQualifiedSchemaName() + " cascade");
9.264 + } else {
9.265 + jdbcResultSet = dbMetaData.getTables(
9.266 + null,null,schemaName + "%",null);
9.267 + List tables = new ArrayList();
9.268 + while (jdbcResultSet.next()) {
9.269 + tables.add(jdbcResultSet.getString("TABLE_NAME"));
9.270 + }
9.271 + closeResultSet(jdbcResultSet);
9.272 + Iterator iter = tables.iterator();
9.273 + while (iter.hasNext()) {
9.274 + String tableName = (String) iter.next();
9.275 + jdbcStmt.execute(
9.276 + "drop table " + idQuote + tableName + idQuote);
9.277 + }
9.278 + }
9.279 + jdbcConnection.commit();
9.280 + return true;
9.281 + } catch (SQLException ex) {
9.282 + rollbackConnection();
9.283 + return false;
9.284 + }
9.285 + }
9.286 +
9.287 + private boolean isBlank(String s)
9.288 + {
9.289 + return (s == null) || (s.length() == 0);
9.290 + }
9.291 +
9.292 + // implement Storage
9.293 + public synchronized void create(boolean replace, ObjectResolver resolver)
9.294 + throws StorageException
9.295 + {
9.296 + try {
9.297 + if (replace) {
9.298 + delete();
9.299 + }
9.300 + rollbackConnection();
9.301 + if (realSchema) {
9.302 + String sql = "create schema " + getQualifiedSchemaName();
9.303 + if (!isBlank(userName)) {
9.304 + sql = sql + " authorization " + userName;
9.305 + }
9.306 + jdbcStmt.execute(sql);
9.307 + }
9.308 + String intType = getDataType(EntryType.INT);
9.309 + jdbcStmt.execute(
9.310 + "create table " + getQualifiedTableName(MOFID_SEQ_TABLE_NAME)
9.311 + + "(" + MOFID_SEQ_COL_NAME + " " + intType
9.312 + + " not null primary key)");
9.313 + jdbcStmt.executeUpdate(
9.314 + "insert into " + getQualifiedTableName(MOFID_SEQ_TABLE_NAME)
9.315 + + " values(1)");
9.316 + nextSerialNumber = 1;
9.317 + openImpl();
9.318 + createSinglevaluedIndex(
9.319 + PRIMARY_INDEX_NAME,
9.320 + EntryType.MOFID,
9.321 + EntryType.STREAMABLE);
9.322 + loadPrimaryIndex();
9.323 + jdbcConnection.commit();
9.324 + } catch (SQLException ex) {
9.325 + rollbackConnection();
9.326 + throw new JdbcStorageException(ex);
9.327 + }
9.328 + }
9.329 +
9.330 + // implement Storage
9.331 + public synchronized void open(
9.332 + boolean createOnNoExist, ObjectResolver resolver)
9.333 + throws StorageException
9.334 + {
9.335 + nextSerialNumber = readSerialNumber();
9.336 + if (nextSerialNumber == -1) {
9.337 + if (createOnNoExist) {
9.338 + create(false,resolver);
9.339 + return;
9.340 + } else {
9.341 + throw new StorageBadRequestException(
9.342 + "Storage " + getName() + " does not exist.");
9.343 + }
9.344 + }
9.345 + try {
9.346 + openImpl();
9.347 + loadPrimaryIndex();
9.348 + } catch (SQLException ex) {
9.349 + throw new JdbcStorageException(ex);
9.350 + }
9.351 + }
9.352 +
9.353 + private void openImpl() throws SQLException
9.354 + {
9.355 + nameToIndexMap = new HashMap();
9.356 + sqlToPreparedStatementMap = new HashMap();
9.357 + sqlUpdateSerialNumber = new LazyPreparedStatement(
9.358 + "update " + getQualifiedTableName(MOFID_SEQ_TABLE_NAME)
9.359 + + " set " + MOFID_SEQ_COL_NAME + " = ?");
9.360 + }
9.361 +
9.362 + private void loadPrimaryIndex()
9.363 + throws StorageException
9.364 + {
9.365 + primaryIndex = (JdbcPrimaryIndex) getIndex(PRIMARY_INDEX_NAME);
9.366 + }
9.367 +
9.368 + // implement Storage
9.369 + public synchronized void close() throws StorageException
9.370 + {
9.371 + nameToIndexMap = null;
9.372 + sqlUpdateSerialNumber = null;
9.373 + if (sqlToPreparedStatementMap != null) {
9.374 + Iterator iter = sqlToPreparedStatementMap.values().iterator();
9.375 + while (iter.hasNext()) {
9.376 + PreparedStatement ps = (PreparedStatement) iter.next();
9.377 + closePreparedStatement(ps);
9.378 + }
9.379 + sqlToPreparedStatementMap = null;
9.380 + }
9.381 + rollbackConnection();
9.382 + }
9.383 +
9.384 + private void closePreparedStatement(PreparedStatement ps)
9.385 + {
9.386 + if (ps == null) {
9.387 + return;
9.388 + }
9.389 + try {
9.390 + ps.close();
9.391 + } catch (SQLException ex) {
9.392 + // TODO: trace
9.393 + }
9.394 + }
9.395 +
9.396 + private void closeStatement(Statement s)
9.397 + {
9.398 + if (s == null) {
9.399 + return;
9.400 + }
9.401 + try {
9.402 + s.close();
9.403 + } catch (SQLException ex) {
9.404 + // TODO: trace
9.405 + }
9.406 + }
9.407 +
9.408 + private void closeResultSet(ResultSet rs)
9.409 + {
9.410 + if (rs == null) {
9.411 + return;
9.412 + }
9.413 + try {
9.414 + rs.close();
9.415 + } catch (SQLException ex) {
9.416 + // TODO: trace
9.417 + }
9.418 + }
9.419 +
9.420 + private void closeConnection(Connection c)
9.421 + {
9.422 + if (c == null) {
9.423 + return;
9.424 + }
9.425 + try {
9.426 + c.close();
9.427 + } catch (SQLException ex) {
9.428 + // TODO: trace
9.429 + }
9.430 + }
9.431 +
9.432 + // implement Storage
9.433 + public synchronized SinglevaluedIndex createSinglevaluedIndex(
9.434 + String name, EntryType keyType,
9.435 + EntryType valueType) throws StorageException
9.436 + {
9.437 + createIndex(name,keyType,valueType,true,true,false);
9.438 + return getSinglevaluedIndex(name);
9.439 + }
9.440 +
9.441 + // implement Storage
9.442 + public synchronized MultivaluedOrderedIndex createMultivaluedOrderedIndex(
9.443 + String name, EntryType keyType, EntryType valueType, boolean unique)
9.444 + throws StorageException
9.445 + {
9.446 + // NOTE: ignore unique because it appears to lie
9.447 + createIndex(name,keyType,valueType,false,false,true);
9.448 + return getMultivaluedOrderedIndex(name);
9.449 + }
9.450 +
9.451 + // implement Storage
9.452 + public synchronized MultivaluedIndex createMultivaluedIndex(
9.453 + String name, EntryType keyType, EntryType valueType, boolean unique)
9.454 + throws StorageException
9.455 + {
9.456 + createIndex(name,keyType,valueType,false,unique,false);
9.457 + return getMultivaluedIndex(name);
9.458 + }
9.459 +
9.460 + private String getDataType(EntryType entryType)
9.461 + {
9.462 + return (String) entryTypeToDataTypeMap.get(entryType);
9.463 + }
9.464 +
9.465 + private EntryType getEntryType(ResultSetMetaData md,int i)
9.466 + throws SQLException
9.467 + {
9.468 + String colName = md.getColumnName(i).toUpperCase();
9.469 + int lastUnderscore = colName.lastIndexOf('_');
9.470 + String entryTypeName =
9.471 + colName.substring(lastUnderscore + 1);
9.472 + return EntryType.decodeEntryType(entryTypeName);
9.473 + }
9.474 +
9.475 + private String stripMofId(String indexName)
9.476 + {
9.477 + int i = indexName.indexOf(storageId);
9.478 + if (i == -1) {
9.479 + return indexName;
9.480 + }
9.481 + int j = i + storageId.length();
9.482 + if (indexName.charAt(j) != ':') {
9.483 + return indexName;
9.484 + }
9.485 + int n = indexName.length();
9.486 + for (++j; j < n; ++j) {
9.487 + if (indexName.charAt(j) != '0') {
9.488 + break;
9.489 + }
9.490 + }
9.491 + return indexName.substring(0,i) + indexName.substring(j);
9.492 + }
9.493 +
9.494 + private String getTableNameForIndex(String indexName)
9.495 + {
9.496 + // Assume we're getting something of the form
9.497 + // <prefix>:<mofid1>:<mofid2> for indexName.
9.498 + // Replace this with
9.499 + // <prefix>:<serial1>:<serial2>
9.500 + indexName = stripMofId(indexName);
9.501 + indexName = stripMofId(indexName);
9.502 + return getQualifiedTableName(indexName);
9.503 + }
9.504 +
9.505 + private void createIndex(
9.506 + String name, EntryType keyType, EntryType valueType,
9.507 + boolean singleValued,boolean uniqueValued,boolean ordered)
9.508 + throws StorageException
9.509 + {
9.510 + try {
9.511 + StringBuffer sb = new StringBuffer();
9.512 + sb.append("create table ");
9.513 + sb.append(getTableNameForIndex(name));
9.514 + sb.append("(");
9.515 +
9.516 + String keyColName = KEY_COL_PREFIX + "_" + keyType;
9.517 + sb.append(keyColName);
9.518 + sb.append(" ");
9.519 + sb.append(getDataType(keyType));
9.520 + sb.append(" not null, ");
9.521 +
9.522 + String valColName;
9.523 + if (singleValued) {
9.524 + valColName = SINGLE_VAL_COL_PREFIX;
9.525 + } else {
9.526 + valColName = MULTI_VAL_COL_PREFIX;
9.527 + }
9.528 + valColName = valColName + "_" + valueType;
9.529 + sb.append(valColName);
9.530 + sb.append(" ");
9.531 + sb.append(getDataType(valueType));
9.532 + sb.append(" not null,");
9.533 +
9.534 + String intType = getDataType(EntryType.INT);
9.535 +
9.536 + if (ordered) {
9.537 + sb.append(ORDINAL_COL_NAME);
9.538 + sb.append(" ");
9.539 + sb.append(intType);
9.540 + sb.append(" not null,");
9.541 + }
9.542 +
9.543 + if (!uniqueValued) {
9.544 + sb.append(SURROGATE_COL_NAME);
9.545 + sb.append(" ");
9.546 + sb.append(intType);
9.547 + sb.append(" not null,");
9.548 + }
9.549 +
9.550 + sb.append(" primary key(");
9.551 + sb.append(keyColName);
9.552 + if (singleValued) {
9.553 + // nothing more needed
9.554 + } else if (uniqueValued) {
9.555 + sb.append(",");
9.556 + sb.append(valColName);
9.557 + } else {
9.558 + if (ordered) {
9.559 + // NOTE: could use just (KEY,ORDINAL) as primary key.
9.560 + // However, we have to be able to modify ordinals, and even
9.561 + // PostgreSQL doesn't get the deferred constraint
9.562 + // enforcement right in that case). So, throw in both the
9.563 + // ORDINAL and the SURROGATE so that ORDER BY can use the
9.564 + // index.
9.565 + sb.append(",");
9.566 + sb.append(ORDINAL_COL_NAME);
9.567 + }
9.568 + sb.append(",");
9.569 + sb.append(SURROGATE_COL_NAME);
9.570 + }
9.571 + sb.append(")");
9.572 +
9.573 + sb.append(")");
9.574 + jdbcStmt.execute(sb.toString());
9.575 + } catch (SQLException ex) {
9.576 + throw new JdbcStorageException(ex);
9.577 + }
9.578 + }
9.579 +
9.580 + // implement Storage
9.581 + public synchronized SinglevaluedIndex getPrimaryIndex()
9.582 + throws StorageException
9.583 + {
9.584 + return getSinglevaluedIndex(PRIMARY_INDEX_NAME);
9.585 + }
9.586 +
9.587 + private Index loadIndex(String name)
9.588 + throws StorageException
9.589 + {
9.590 + PreparedStatement ps = null;
9.591 + try {
9.592 + ps = jdbcConnection.prepareStatement(
9.593 + "select * from "+getTableNameForIndex(name));
9.594 + ResultSetMetaData md = null;
9.595 + try {
9.596 + md = ps.getMetaData();
9.597 + } catch (SQLException ex) {
9.598 + // Some drivers don't support metadata pre-execution.
9.599 + // Fall through to recovery below.
9.600 + }
9.601 + if (md == null) {
9.602 + jdbcResultSet = ps.executeQuery();
9.603 + md = jdbcResultSet.getMetaData();
9.604 + }
9.605 + EntryType keyType = getEntryType(md,1);
9.606 + EntryType valueType = getEntryType(md,2);
9.607 + boolean singleValued = false;
9.608 + boolean ordered = false;
9.609 + boolean needSurrogate = false;
9.610 + String keyColName = md.getColumnName(1).toUpperCase();
9.611 + String valColName = md.getColumnName(2).toUpperCase();
9.612 + if (valColName.startsWith(SINGLE_VAL_COL_PREFIX)) {
9.613 + singleValued = true;
9.614 + }
9.615 + for (int i = 3; i <= md.getColumnCount(); ++i) {
9.616 + String colName = md.getColumnName(i).toUpperCase();
9.617 + if (colName.equals(ORDINAL_COL_NAME)) {
9.618 + ordered = true;
9.619 + } else if (colName.equals(SURROGATE_COL_NAME)) {
9.620 + needSurrogate = true;
9.621 + } else {
9.622 + // TODO: assert
9.623 + }
9.624 + }
9.625 + JdbcIndex index;
9.626 + if (singleValued) {
9.627 + if (name.equals(PRIMARY_INDEX_NAME)) {
9.628 + index = new JdbcPrimaryIndex();
9.629 + } else {
9.630 + index = new JdbcSinglevaluedIndex();
9.631 + }
9.632 + } else {
9.633 + if (ordered) {
9.634 + index = new JdbcMultivaluedOrderedIndex();
9.635 + } else {
9.636 + index = new JdbcMultivaluedIndex();
9.637 + }
9.638 + }
9.639 + index.init(
9.640 + this,
9.641 + getTableNameForIndex(name),
9.642 + name,keyColName,valColName,keyType,valueType,
9.643 + needSurrogate);
9.644 + nameToIndexMap.put(name,index);
9.645 + return index;
9.646 + } catch (SQLException ex) {
9.647 + throw new JdbcStorageException(ex);
9.648 + } finally {
9.649 + closeResultSet(jdbcResultSet);
9.650 + closePreparedStatement(ps);
9.651 + }
9.652 + }
9.653 +
9.654 + // implement Storage
9.655 + public synchronized Index getIndex(String name) throws StorageException
9.656 + {
9.657 + synchronized(nameToIndexMap) {
9.658 + Index index = (Index) nameToIndexMap.get(name);
9.659 + if (index == null) {
9.660 + index = loadIndex(name);
9.661 + }
9.662 + return index;
9.663 + }
9.664 + }
9.665 +
9.666 + // implement Storage
9.667 + public synchronized SinglevaluedIndex getSinglevaluedIndex(String name)
9.668 + throws StorageException
9.669 + {
9.670 + return (SinglevaluedIndex) getIndex(name);
9.671 + }
9.672 +
9.673 + // implement Storage
9.674 + public synchronized MultivaluedIndex getMultivaluedIndex(String name)
9.675 + throws StorageException
9.676 + {
9.677 + return (MultivaluedIndex) getIndex(name);
9.678 + }
9.679 +
9.680 + // implement Storage
9.681 + public synchronized MultivaluedOrderedIndex getMultivaluedOrderedIndex(
9.682 + String name) throws StorageException
9.683 + {
9.684 + return (MultivaluedOrderedIndex) getIndex(name);
9.685 + }
9.686 +
9.687 + // implement Storage
9.688 + public synchronized void dropIndex(String name) throws StorageException
9.689 + {
9.690 + try {
9.691 + jdbcStmt.execute("drop table "+getTableNameForIndex(name));
9.692 + } catch (SQLException ex) {
9.693 + throw new JdbcStorageException(ex);
9.694 + }
9.695 + nameToIndexMap.remove(name);
9.696 + }
9.697 +
9.698 + // implement Storage
9.699 + public void objectStateWillChange(Object key) throws StorageException
9.700 + {
9.701 + // ignore
9.702 + }
9.703 +
9.704 + // implement Storage
9.705 + public synchronized void objectStateChanged(Object key)
9.706 + throws StorageException
9.707 + {
9.708 + primaryIndex.objectStateChanged(key);
9.709 + }
9.710 +
9.711 + // implement Storage
9.712 + public synchronized void commitChanges() throws StorageException
9.713 + {
9.714 + try {
9.715 + writeSerialNumber(nextSerialNumber);
9.716 + primaryIndex.flushChanges();
9.717 + jdbcConnection.commit();
9.718 + } catch (SQLException ex) {
9.719 + throw new JdbcStorageException(ex);
9.720 + }
9.721 + }
9.722 +
9.723 + // implement Storage
9.724 + public synchronized void rollBackChanges () throws StorageException
9.725 + {
9.726 + try {
9.727 + jdbcConnection.rollback();
9.728 + } catch (SQLException ex) {
9.729 + throw new JdbcStorageException(ex);
9.730 + }
9.731 + // NOTE: this is taken from BtreeStorage. It seems rather brutal,
9.732 + // but it means that we don't have to worry about state inconsistencies
9.733 + // with nameToIndexMap, primary index cache, etc.
9.734 + close();
9.735 + open(false,null);
9.736 + }
9.737 +
9.738 + // implement Storage
9.739 + public synchronized void shutDown() throws StorageException
9.740 + {
9.741 + closeStatement(jdbcStmt);
9.742 + rollbackConnection();
9.743 + closeConnection(jdbcConnection);
9.744 + }
9.745 +
9.746 + private PreparedStatement prepareStatement(
9.747 + LazyPreparedStatement lps)
9.748 + throws StorageException
9.749 + {
9.750 + if (lps.ps != null) {
9.751 + // lps is already prepared
9.752 + return lps.ps;
9.753 + }
9.754 + PreparedStatement ps = (PreparedStatement)
9.755 + sqlToPreparedStatementMap.get(lps.sql);
9.756 + if (ps != null) {
9.757 + lps.ps = ps;
9.758 + return ps;
9.759 + }
9.760 + try {
9.761 + ps = jdbcConnection.prepareStatement(lps.sql);
9.762 + sqlToPreparedStatementMap.put(lps.sql,ps);
9.763 + lps.ps = ps;
9.764 + return ps;
9.765 + } catch (SQLException ex) {
9.766 + throw new JdbcStorageException(ex);
9.767 + }
9.768 + }
9.769 +
9.770 + private void bindArgs(PreparedStatement ps,Object [] args)
9.771 + throws SQLException, StorageException
9.772 + {
9.773 + if (args == null) {
9.774 + return;
9.775 + }
9.776 + for (int i = 0; i < args.length; ++i) {
9.777 + Object arg = args[i];
9.778 + int iParam = i + 1;
9.779 + if (arg instanceof MOFID) {
9.780 + MOFID mofid = (MOFID) arg;
9.781 + if (!mofid.getStorageID().equals(storageId)) {
9.782 + throw new IllegalArgumentException("Foreign MOFID");
9.783 + }
9.784 + ps.setLong(
9.785 + iParam,
9.786 + mofid.getSerialNumber());
9.787 + } else if (arg instanceof Streamable) {
9.788 + ps.setBytes(
9.789 + iParam,
9.790 + writeByteArray((Streamable) arg));
9.791 + } else {
9.792 + ps.setObject(
9.793 + iParam,
9.794 + arg);
9.795 + }
9.796 + }
9.797 + }
9.798 +
9.799 + private Object getResultObj(ResultSet jdbcResultSet,EntryType entryType)
9.800 + throws StorageException, SQLException
9.801 + {
9.802 + if (entryType == EntryType.MOFID) {
9.803 + return new MOFID(
9.804 + jdbcResultSet.getLong(1),
9.805 + storageId);
9.806 + } else if (entryType == EntryType.STRING) {
9.807 + return jdbcResultSet.getString(1);
9.808 + } else if (entryType == EntryType.INT) {
9.809 + return new Long(jdbcResultSet.getLong(1));
9.810 + } else {
9.811 + return readByteArray(jdbcResultSet.getBytes(1));
9.812 + }
9.813 + }
9.814 +
9.815 + synchronized ListIterator getResultSetIterator(
9.816 + LazyPreparedStatement lps,Object [] args,EntryType entryType)
9.817 + throws StorageException
9.818 + {
9.819 + try {
9.820 + PreparedStatement ps = prepareStatement(lps);
9.821 + bindArgs(ps,args);
9.822 + jdbcResultSet = ps.executeQuery();
9.823 + // TODO: assert exactly one column
9.824 + try {
9.825 + List list = new ArrayList();
9.826 + while (jdbcResultSet.next()) {
9.827 + Object obj = getResultObj(jdbcResultSet,entryType);
9.828 + list.add(obj);
9.829 + }
9.830 + return list.listIterator();
9.831 + } finally {
9.832 + closeResultSet(jdbcResultSet);
9.833 + }
9.834 + } catch (SQLException ex) {
9.835 + throw new JdbcStorageException(ex);
9.836 + }
9.837 + }
9.838 +
9.839 + synchronized int getResultSetCount(
9.840 + LazyPreparedStatement lps,Object [] args)
9.841 + throws StorageException
9.842 + {
9.843 + try {
9.844 + PreparedStatement ps = prepareStatement(lps);
9.845 + bindArgs(ps,args);
9.846 + jdbcResultSet = ps.executeQuery();
9.847 + try {
9.848 + int n = 0;
9.849 + while (jdbcResultSet.next()) {
9.850 + ++n;
9.851 + }
9.852 + return n;
9.853 + } finally {
9.854 + closeResultSet(jdbcResultSet);
9.855 + }
9.856 + } catch (SQLException ex) {
9.857 + throw new JdbcStorageException(ex);
9.858 + }
9.859 + }
9.860 +
9.861 + synchronized int getSingletonInt(
9.862 + LazyPreparedStatement lps,Object [] args)
9.863 + throws StorageException
9.864 + {
9.865 + try {
9.866 + PreparedStatement ps = prepareStatement(lps);
9.867 + bindArgs(ps,args);
9.868 + jdbcResultSet = ps.executeQuery();
9.869 + try {
9.870 + // TODO: assert exactly one row, one column
9.871 + jdbcResultSet.next();
9.872 + return jdbcResultSet.getInt(1);
9.873 + } finally {
9.874 + closeResultSet(jdbcResultSet);
9.875 + }
9.876 + } catch (SQLException ex) {
9.877 + throw new JdbcStorageException(ex);
9.878 + }
9.879 + }
9.880 +
9.881 + private byte [] writeByteArray(Streamable data)
9.882 + throws StorageException
9.883 + {
9.884 + try {
9.885 + ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
9.886 + DataOutputStream dataStream = new DataOutputStream(byteStream);
9.887 + dataStream.writeUTF(data.getClass().getName());
9.888 + data.write(dataStream);
9.889 + dataStream.flush();
9.890 + return byteStream.toByteArray();
9.891 + } catch (IOException ex) {
9.892 + throw new StorageIOException(ex);
9.893 + }
9.894 + }
9.895 +
9.896 + private Streamable readByteArray(byte [] bytes)
9.897 + throws StorageException
9.898 + {
9.899 + ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
9.900 + DataInputStream dataStream = new DataInputStream(byteStream);
9.901 +
9.902 + // TODO: rip classCode stuff from BtreeDatabase
9.903 + String className;
9.904 + Streamable data;
9.905 +
9.906 + try {
9.907 + className = dataStream.readUTF();
9.908 + } catch (IOException ex) {
9.909 + throw new StorageIOException(ex);
9.910 + }
9.911 + try {
9.912 + Class cls = Class.forName(className);
9.913 + data = (Streamable)cls.newInstance();
9.914 + } catch (Exception ex) {
9.915 + throw new StoragePersistentDataException(ex.getMessage());
9.916 + }
9.917 + if (data instanceof StorageClient) {
9.918 + ((StorageClient)data).setStorage(this);
9.919 + }
9.920 + data.read(dataStream);
9.921 + return data;
9.922 + }
9.923 +
9.924 + synchronized int executeUpdate(LazyPreparedStatement lps,Object [] args)
9.925 + throws StorageException
9.926 + {
9.927 + try {
9.928 + PreparedStatement ps = prepareStatement(lps);
9.929 + bindArgs(ps,args);
9.930 + return ps.executeUpdate();
9.931 + } catch (SQLException ex) {
9.932 + throw new JdbcStorageException(ex);
9.933 + }
9.934 + }
9.935 +}
9.936 +
9.937 +// End JdbcStorage.java
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcStorageException.java Wed Feb 04 06:38:10 2004 +0000
10.3 @@ -0,0 +1,80 @@
10.4 +/*
10.5 + * Sun Public License Notice
10.6 + *
10.7 + * The contents of this file are subject to the Sun Public License
10.8 + * Version 1.0 (the "License"). You may not use this file except in
10.9 + * compliance with the License. A copy of the License is available at
10.10 + * http://www.sun.com/
10.11 + *
10.12 + * The Original Code is NetBeans. The Initial Developer of the Original
10.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
10.14 + * Microsystems, Inc. All Rights Reserved.
10.15 + */
10.16 +package org.netbeans.mdr.persistence.jdbcimpl;
10.17 +
10.18 +import org.netbeans.mdr.persistence.*;
10.19 +import java.sql.*;
10.20 +import java.io.*;
10.21 +
10.22 +/**
10.23 + * This is thrown to wrap an SQLException as a StorageException.
10.24 + *
10.25 + * @author John V. Sichi
10.26 + * @version $Id$
10.27 + *
10.28 + * @see StorageIOException
10.29 + */
10.30 +public class JdbcStorageException extends StorageException
10.31 +{
10.32 + /**
10.33 + * The original SQLException that we are converting to a StorageException.
10.34 + */
10.35 + private SQLException sqlException;
10.36 +
10.37 + /** this constructs a JdbcStorageException from an SQLException
10.38 + * @param err the SQLException
10.39 + */
10.40 + public JdbcStorageException(SQLException err)
10.41 + {
10.42 + super(err.getMessage());
10.43 + sqlException = err;
10.44 + }
10.45 +
10.46 + /** return the original SQLException's message */
10.47 + public String getMessage()
10.48 + {
10.49 + return sqlException.getMessage();
10.50 + }
10.51 +
10.52 + /** return the original SQLException's localized message */
10.53 + public String getLocalizedMessage()
10.54 + {
10.55 + return sqlException.getLocalizedMessage();
10.56 + }
10.57 +
10.58 + /** print the original SQLException's stack trace */
10.59 + public void printStackTrace()
10.60 + {
10.61 + sqlException.printStackTrace();
10.62 + }
10.63 +
10.64 + /** print the original SQLException's stack trace */
10.65 + public void printStackTrace(PrintStream ps)
10.66 + {
10.67 + sqlException.printStackTrace(ps);
10.68 + }
10.69 +
10.70 + /** print the original SQLException's stack trace */
10.71 + public void printStackTrace(PrintWriter pw)
10.72 + {
10.73 + sqlException.printStackTrace(pw);
10.74 + }
10.75 +
10.76 + /** get the original SQLException's cause */
10.77 + public Throwable getCause()
10.78 + {
10.79 + return sqlException.getNextException();
10.80 + }
10.81 +}
10.82 +
10.83 +// End JdbcStorageException.java
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/JdbcStorageFactory.java Wed Feb 04 06:38:10 2004 +0000
11.3 @@ -0,0 +1,93 @@
11.4 +/*
11.5 + * Sun Public License Notice
11.6 + *
11.7 + * The contents of this file are subject to the Sun Public License
11.8 + * Version 1.0 (the "License"). You may not use this file except in
11.9 + * compliance with the License. A copy of the License is available at
11.10 + * http://www.sun.com/
11.11 + *
11.12 + * The Original Code is NetBeans. The Initial Developer of the Original
11.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
11.14 + * Microsystems, Inc. All Rights Reserved.
11.15 + */
11.16 +package org.netbeans.mdr.persistence.jdbcimpl;
11.17 +
11.18 +import org.netbeans.mdr.persistence.*;
11.19 +
11.20 +import java.sql.*;
11.21 +import java.util.*;
11.22 +
11.23 +/**
11.24 + * JdbcStorageFactory implements the StorageFactory interface by
11.25 + * creating instances of JdbcStorage.
11.26 + *
11.27 + * @author John V. Sichi
11.28 + * @version $Id$
11.29 + */
11.30 +public class JdbcStorageFactory implements StorageFactory
11.31 +{
11.32 + public static final String STORAGE_URL =
11.33 + "org.netbeans.mdr.persistence.jdbcimpl.url";
11.34 +
11.35 + public static final String STORAGE_SCHEMA_NAME =
11.36 + "org.netbeans.mdr.persistence.jdbcimpl.schemaName";
11.37 +
11.38 + public static final String STORAGE_USER_NAME =
11.39 + "org.netbeans.mdr.persistence.jdbcimpl.userName";
11.40 +
11.41 + public static final String STORAGE_PASSWORD =
11.42 + "org.netbeans.mdr.persistence.jdbcimpl.password";
11.43 +
11.44 + public static final String STORAGE_DRIVER_CLASS_NAME =
11.45 + "org.netbeans.mdr.persistence.jdbcimpl.driverClassName";
11.46 +
11.47 + public static final String STORAGE_DATATYPE_MOFID =
11.48 + "org.netbeans.mdr.persistence.jdbcimpl.datatype.mofid";
11.49 +
11.50 + public static final String STORAGE_DATATYPE_STREAMABLE =
11.51 + "org.netbeans.mdr.persistence.jdbcimpl.datatype.streamable";
11.52 +
11.53 + public static final String STORAGE_DATATYPE_STRING =
11.54 + "org.netbeans.mdr.persistence.jdbcimpl.datatype.string";
11.55 +
11.56 + public static final String STORAGE_DATATYPE_INT =
11.57 + "org.netbeans.mdr.persistence.jdbcimpl.datatype.int";
11.58 +
11.59 + // ripped from memoryimpl
11.60 + private static final String NULL_STORAGE_ID = ".";
11.61 + private static final MOFID NULL_MOFID = new MOFID(0, NULL_STORAGE_ID);
11.62 +
11.63 + // implement StorageFactory
11.64 + public Storage createStorage(Map ignoredProperties) throws StorageException
11.65 + {
11.66 + // TODO: use parameter once Netbeans knows about our properties?
11.67 + Properties properties = System.getProperties();
11.68 +
11.69 + String url = properties.getProperty(STORAGE_URL);
11.70 + String schemaName = properties.getProperty(STORAGE_SCHEMA_NAME);
11.71 + String driverClassName =
11.72 + properties.getProperty(STORAGE_DRIVER_CLASS_NAME);
11.73 + if (url == null || schemaName == null) {
11.74 + throw new StorageBadRequestException(
11.75 + "JdbcStorageFactory requires parameters "
11.76 + + STORAGE_URL + " and " + STORAGE_SCHEMA_NAME);
11.77 + }
11.78 + if (driverClassName != null) {
11.79 + try {
11.80 + Class.forName(driverClassName);
11.81 + } catch (ClassNotFoundException ex) {
11.82 + // let driver manager report "no suitable driver" when
11.83 + // the connection is attempted
11.84 + }
11.85 + }
11.86 + return new JdbcStorage(properties,NULL_STORAGE_ID);
11.87 + }
11.88 +
11.89 + // implement StorageFactory
11.90 + public MOFID createNullMOFID() throws StorageException
11.91 + {
11.92 + return NULL_MOFID;
11.93 + }
11.94 +}
11.95 +
11.96 +// End JdbcStorageFactory.java
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/LazyPreparedStatement.java Wed Feb 04 06:38:10 2004 +0000
12.3 @@ -0,0 +1,35 @@
12.4 +/*
12.5 + * Sun Public License Notice
12.6 + *
12.7 + * The contents of this file are subject to the Sun Public License
12.8 + * Version 1.0 (the "License"). You may not use this file except in
12.9 + * compliance with the License. A copy of the License is available at
12.10 + * http://www.sun.com/
12.11 + *
12.12 + * The Original Code is NetBeans. The Initial Developer of the Original
12.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
12.14 + * Microsystems, Inc. All Rights Reserved.
12.15 + */
12.16 +package org.netbeans.mdr.persistence.jdbcimpl;
12.17 +
12.18 +import java.sql.*;
12.19 +
12.20 +/**
12.21 + * LazyPreparedStatement is a wrapper for a JDBC PreparedStatement
12.22 + * which can be prepared on demand.
12.23 + *
12.24 + * @author John V. Sichi
12.25 + * @version $Id$
12.26 + */
12.27 +class LazyPreparedStatement
12.28 +{
12.29 + final String sql;
12.30 + PreparedStatement ps;
12.31 +
12.32 + public LazyPreparedStatement(String sql)
12.33 + {
12.34 + this.sql = sql;
12.35 + }
12.36 +}
12.37 +
12.38 +// End LazyPreparedStatement.java
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/mdr/extras/jdbcstorage/src/org/netbeans/mdr/persistence/jdbcimpl/package.html Wed Feb 04 06:38:10 2004 +0000
13.3 @@ -0,0 +1,313 @@
13.4 +<!--
13.5 + Sun Public License Notice
13.6 +
13.7 +The contents of this file are subject to the Sun Public License
13.8 +Version 1.0 (the "License"). You may not use this file except in
13.9 +compliance with the License. A copy of the License is available at
13.10 +http://www.sun.com/
13.11 +
13.12 +The Original Code is NetBeans. The Initial Developer of the Original
13.13 +Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
13.14 +Microsystems, Inc. All Rights Reserved.
13.15 +-->
13.16 +
13.17 +<HTML>
13.18 +<body>
13.19 +
13.20 +Provides an implementation for the {@link org.netbeans.mdr.persistence
13.21 +abstract MDR persistence interfaces } in terms of {@link java.sql JDBC
13.22 +}.
13.23 +
13.24 +<h2>JDBC Persistence for MDR</h2>
13.25 +
13.26 +The JdbcStorage implementation relies on a JDBC driver and associated
13.27 +SQL DBMS for transactional persistence. The DBMS can be a
13.28 +lightweight Java database running in the same process as MDR, a
13.29 +heavyweight database running out-of-process on the same machine, or
13.30 +even across the network (although performance may be poor in this
13.31 +configuration).
13.32 +
13.33 +<h3>Parameters</h3>
13.34 +
13.35 +In order to use JdbcStorage, some mandatory properties must be
13.36 +specified:
13.37 +
13.38 +<ul>
13.39 +
13.40 +<li>org.netbeans.mdr.storagemodel.StorageFactoryClassName =
13.41 +org.netbeans.mdr.persistence.jdbcimpl.JdbcStorageFactory
13.42 +
13.43 +<li>org.netbeans.mdr.persistence.jdbcimpl.driverClassName = the fully
13.44 +qualified name of the JDBC driver to use (e.g. org.postgresql.Driver)
13.45 +
13.46 +<li>org.netbeans.mdr.persistence.jdbcimpl.url = the JDBC URL to use to
13.47 +connect to the database (e.g. jdbc:postgresql://localhost/mdrdb)
13.48 +
13.49 +<li>org.netbeans.mdr.persistence.jdbcimpl.schemaName = the name of the
13.50 +schema which will contain the MDR tables; this schema will be created
13.51 +automatically by JdbcStorage, so it should not be created manually
13.52 +
13.53 +</ul>
13.54 +
13.55 +There are also some optional connection parameters:
13.56 +
13.57 +<ul>
13.58 +
13.59 +<li>org.netbeans.mdr.persistence.jdbcimpl.userName =
13.60 +user name for connection; this user must have the right to create the
13.61 +schema and tables in the database
13.62 +
13.63 +<li>org.netbeans.mdr.persistence.jdbcimpl.password =
13.64 +password corresponding to userName
13.65 +
13.66 +The next section defines additional parameters affecting
13.67 +DBMS-specific usage of SQL.
13.68 +
13.69 +</ul>
13.70 +
13.71 +<h3>Index to Table Mapping</h3>
13.72 +
13.73 +JdbcStorage creates one table per MDR index. It also creates one
13.74 +private table (MOFID_SEQ) for implementing the MOFID sequence
13.75 +generator. No metadata tables are created; instead, JdbcStorage
13.76 +relies on DBMS metadata. This avoids redundancy and the
13.77 +possibility for inconsistency, and makes it easier to see the
13.78 +relationship between MDR objects and their SQL implementations.
13.79 +
13.80 +Datatypes for index key and value columns can be controlled via the
13.81 +properties listed below. The defaults may not be supported by every
13.82 +DBMS. For example, PostgreSQL requires usage of type BYTEA in place
13.83 +of LONGVARBINARY. Also, users may wish to override the default
13.84 +precision depending on the application.
13.85 +
13.86 +<ul>
13.87 +
13.88 +<li>org.netbeans.mdr.persistence.jdbcimpl.datatype.mofid:
13.89 +default BIGINT
13.90 +
13.91 +<li>org.netbeans.mdr.persistence.jdbcimpl.datatype.streamable:
13.92 +default LONGVARBINARY
13.93 +
13.94 +<li>org.netbeans.mdr.persistence.jdbcimpl.datatype.string:
13.95 +default VARCHAR(2000)
13.96 +
13.97 +<li>org.netbeans.mdr.persistence.jdbcimpl.datatype.int:
13.98 +default BIGINT
13.99 +</ul>
13.100 +
13.101 +Additional columns may be created as follows (using the datatype
13.102 +specified by the datatype.int property above):
13.103 +
13.104 +<ul>
13.105 +
13.106 +<li>
13.107 +For a MultivaluedOrderedIndex, a 0-based ordinal column is used to
13.108 +implement ordering.
13.109 +
13.110 +<li>
13.111 +For a MultivaluedIndex with non-unique values, an additional surrogate
13.112 +key column is used to ensure that each stored row has a unique key.
13.113 +Surrogate key values are generated from the same sequence as MOFID
13.114 +serial numbers.
13.115 +
13.116 +</ul>
13.117 +
13.118 +The primary key is defined as the key column, plus the ordinal and
13.119 +surrogate key columns (if any).
13.120 +
13.121 +<h3>Table Names</h3>
13.122 +
13.123 +If the DBMS does not support schemas, JdbcStorage fakes them by
13.124 +prepending the specified schema name to the name of each table created
13.125 +(e.g. MDR_MOFID_SEQ). In this case, a distinguishing value should be
13.126 +used for the schema name, since JdbcStorage.delete() will delete all
13.127 +tables whose names have this prefix.
13.128 +
13.129 +<p>
13.130 +
13.131 +Table names are derived from the index names requested by MDR.
13.132 +However, the index name is not used directly, because MDR generates
13.133 +very long index names based on MOFID's, and many DBMS products have a
13.134 +low limit like 31 characters for identifiers. To shorten the name,
13.135 +the StorageId portion of each MOFID is eliminated, and all leading zeros
13.136 +of the serial number are trimmed.
13.137 +
13.138 +<h3>Caching</h3>
13.139 +
13.140 +{@link org.netbeans.mdr.persistence.jdbcimpl.JdbcPrimaryIndex} reuses
13.141 +the streamable object caching strategy from {@link
13.142 +org.netbeans.mdr.persistence.btreeimpl.btreestorage.MDRCache }. For
13.143 +other indexes, no caching is currently performed.
13.144 +
13.145 +<h3>Driver and DBMS Requirements</h3>
13.146 +
13.147 +JdbcStorage has been tested with hsqldb 1.7.1 and PostgreSQL 7.4.1.
13.148 +It does not require a sophisticated JDBC driver or DBMS, but may
13.149 +require some tweaks for other products. Below is a list of the JDBC
13.150 +and SQL features it uses:
13.151 +
13.152 +<ul>
13.153 +
13.154 +<li>
13.155 +CREATE TABLE with NOT NULL and PRIMARY KEY constraints.
13.156 +
13.157 +<li>
13.158 +Quoted identifiers for schema and table names, containing strange characters
13.159 +like "." and ":" allowed.
13.160 +
13.161 +<li>
13.162 +SELECT COUNT, DISTINCT and COUNT(DISTINCT). No joins, subqueries or
13.163 +GROUP BY clauses are used. WHERE clauses are very simple (e.g. WHERE
13.164 +COLUMN = value). ORDER BY is used for accessing
13.165 +MultivaluedOrderedIndexes.
13.166 +
13.167 +<li>
13.168 +INSERT/UPDATE/DELETE.
13.169 +
13.170 +<li>
13.171 +Prepared statements with dynamic parameters.
13.172 +
13.173 +<li>
13.174 +A single JDBC connection is shared by all calls to the repository, so
13.175 +the JDBC driver must allow calls from any thread. However, access to
13.176 +the connection is synchronized internally, so the JDBC driver does not
13.177 +need to support concurrent threads of any kind.
13.178 +
13.179 +<li>
13.180 +The JDBC driver must support transactions. Since MDR only allows one
13.181 +write transaction at a time, isolation level is irrelevant. Some
13.182 +DBMS's don't support mixing DDL and DML within a transaction, and
13.183 +autocommit on DDL instead. In that case, createIndex() will lead to
13.184 +an implicit commit, which could result in inconsistencies after a
13.185 +subsequent rollback.
13.186 +
13.187 +<li>
13.188 +The Storage.delete() method implementation depends on whether the DBMS
13.189 +supports schemas. If schemas are not supported, the JDBC driver must
13.190 +support enumerating tables with a name prefix so they can be dropped
13.191 +individually. If schemas are supported, the DBMS must also support
13.192 +DROP SCHEMA CASCADE.
13.193 +
13.194 +<li>
13.195 +ResultSetMetaData must support getColumnCount() and getColumnName().
13.196 +
13.197 +<li>
13.198 +ResultSet must support getInt(), getString(), getLong(), and
13.199 +getBytes().
13.200 +
13.201 +<li>
13.202 +PreparedStatement must support setLong(), setBytes(), and setObject().
13.203 +
13.204 +</ul>
13.205 +
13.206 +<h3>Limitations and Unresolved Issues</h3>
13.207 +
13.208 +Since this is a first cut, there is lots of room for improvement:
13.209 +
13.210 +<ul>
13.211 +
13.212 +<li>
13.213 +The StorageId prefix "." is used for all MOFID's, and all MOFID's
13.214 +stored via JdbcStorage must have this prefix. MOFID's are stored as
13.215 +integer serial numbers with no prefix. This means a JdbcStorage-based
13.216 +repository cannot be used for federation of any kind. For some
13.217 +applications, this may be acceptable, and using integers for keys is
13.218 +generally good for SQL performance. Probably we should allow the user
13.219 +a choice on a per-storage basis: either store MOFID's as integers and
13.220 +disallow federation, or store MOFID's as strings or byte arrays and
13.221 +allow federation (factoring out some commonality from btreeimpl).
13.222 +
13.223 +<li>
13.224 +Caching for non-primary indexes is probably required for acceptable
13.225 +performance in out-of-process configurations. A very bad case is when
13.226 +values are inserted sequentially into a MultivaluedOrderedIndex
13.227 +without using an iterator; each one requires an extra query in order
13.228 +to generate an ordinal. A subquery in the VALUES clause would save a
13.229 +round-trip, but caching would be better.
13.230 +
13.231 +<li>
13.232 +Using JDBC batched execution support for
13.233 +flushing the cache could significantly improve performance for bulk
13.234 +write operations such as import.
13.235 +
13.236 +<li>
13.237 +Each SQL implementation typically has its own subtle non-standard
13.238 +behavior and performance, so a lot of testing and tweaking will be
13.239 +required in order to make JdbcStorage plug-and-play.
13.240 +
13.241 +<li>
13.242 +Other than datatypes, there is no way to configure the mapping from
13.243 +indexes to tables. For example, some users might wish to store all
13.244 +associations in a single table, rather than creating one table per
13.245 +association. And others might wish to store streamable objects in
13.246 +normalized format for direct SQL querying. Some kind of configurable
13.247 +object mapping rules would be nice.
13.248 +
13.249 +<li>
13.250 +The synchronization imposed for all JDBC access could become a
13.251 +bottleneck in read-mostly multi-threaded apps. Loosening this is
13.252 +tricky because different JDBC drivers have different multi-threading
13.253 +rules (some require multiple connections). Also, it would be nice to
13.254 +take advantage of advanced DBMS concurrency control and allow multiple
13.255 +concurrent read/write transactions. However, I'm not sure if the
13.256 +higher levels of MDR can handle this.
13.257 +
13.258 +</ul>
13.259 +
13.260 +In addition, there are various unresolved issues which came up during
13.261 +development:
13.262 +
13.263 +<ul>
13.264 +
13.265 +<li>
13.266 +JdbcStorageFactory currently reads System.getProperties() directly,
13.267 +rather than using the properties passed to createStorage(). The
13.268 +reason is that MDR doesn't know about the JDBC-specific properties.
13.269 +Once it does, JdbcStorageFactory should stop using System.getProperties().
13.270 +
13.271 +<li>
13.272 +I wasn't clear on the semantics of unique-valued MultivaluedIndexes,
13.273 +and memoryimpl and btreeimpl didn't provide a consistent answer. The
13.274 +MOF boot sequence resulted in some attempts to insert duplicates. To
13.275 +prevent this from causing a primary key violation, I put in an extra
13.276 +existence check on each insert, and skipped the insert if the value
13.277 +already existed. Also, I didn't strictly follow the spec for
13.278 +non-unique values: remove(key,value) removes all, not just the
13.279 +"first" one (since "first" has no meaning for an unordered index), and
13.280 +the same goes for the iter.remove() equivalent.
13.281 +
13.282 +<li>
13.283 +I did not implement keySet() and values() for JdbcPrimaryIndex because
13.284 +in my testing I never saw them get called. Nor did I implement
13.285 +queryByKeyPrefix() for any of the indexes, or fail-fast iterators anywhere.
13.286 +
13.287 +<li>
13.288 +Due to (I'm guessing) an oversight, MDRCache.replace() is not public as
13.289 +it should be. To work around this, I injected
13.290 +{@link org.netbeans.mdr.persistence.btreeimpl.btreestorage.AccessHack}
13.291 +to sneak in the required access.
13.292 +
13.293 +<li>
13.294 +I did not implement the classCode optimization from BtreeDatabase.
13.295 +That should probably be factored out into a reusable class. For now,
13.296 +I just serialized the class name and used Class.forName for
13.297 +resurrection.
13.298 +
13.299 +<li>
13.300 +Currently, each call to JdbcStorageFactory.createStorage() results in
13.301 +a JDBC connection being established and held open until a
13.302 +corresponding call to JdbcStorage.shutDown(). I'm not sure if the
13.303 +lifetime should have been between open()/create() and close() on
13.304 +JdbcStorage instead.
13.305 +
13.306 +<li>
13.307 +JdbcStorage maintains on-demand maps of descriptors and JDBC
13.308 +PreparedStatements for all indexes which have been accessed since the
13.309 +storage was opened. These maps could lead to memory constipation for
13.310 +very large repository models. Perhaps they need to be turned into
13.311 +caches instead.
13.312 +
13.313 +</ul>
13.314 +
13.315 +</body>
13.316 +</HTML>