Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,715 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.UnknownHostException; +import java.net.ConnectException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import javax.jdo.JDOFatalException; +import javax.jdo.JDOFatalInternalException; +import javax.jdo.JDOFatalDataStoreException; +import javax.jdo.JDOUserException; +import javax.jdo.JDOFatalUserException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.pm.Accessor; +import org.apache.jdo.util.I18NHelper; +import org.apache.jdo.util.Pool; + +/** + * A connection factory for FOStore. Allows both same- and remote-address + * space connections. For the same address space-connections, the URL must + * not include the Host (Server) parameter. For remote address space + * connections, the URL's protocol is ignored. + * <p> + * This class is <code>public</code> so that clients can create instances of it + * with <code>new</code>. + * + * @author Dave Bristor + */ +public class FOStoreConnectionFactory implements Serializable { + private String url; + private String userName; + private String password; + private String driverName; + private boolean create; + + private FOStorePMF pmf; + + private int loginTimeout; + + private transient PrintWriter logWriter; + + /** Connections are created by the FOStoreURLStreamHandler. + */ + private final FOStoreURLStreamHandler streamHandler = + FOStoreURLStreamHandler.getInstance(); + + /** Connections are pooled. Each unique combination of url, + * user, password has its own pool. The hashmap associates + * a FOStoreConnectionId with its pool of connections. + */ + private final HashMap connectionMap = new HashMap(); + + /** For now, set the pool size to 1. + */ + // XXX this needs to be configurable... + private static final int poolSize = 1; + + private FOStoreConnectionId defaultConnectionId; + + private FOStoreConnectionId userConnectionId; + + /** True until setConfigured has been invoked. Allows properties to be + * set if true. + */ + private boolean configurable = true; + + /** This table maps from names to CFAccessors. The names are the same as the + * persistence manager factory's property names, but with + * org.apache.jdo.FOStoreConnectionFactory.option prepended. + */ + protected static HashMap CFpropsAccessors = new HashMap(9); + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + /** + * First time a FOStoreConnectionFactory is created, initialize accessors + * which are used to store/save instances via JNDI. + */ + public FOStoreConnectionFactory() { + if (logger.isDebugEnabled()) logger.debug("FOCF()"); // NOI18N + initPropsAccessors(); + } + + /** Set the url, user, and password into the ConnectionIds for this + * connection factory. + */ + private void setConfigured() { + if (logger.isDebugEnabled()) { + logger.debug("FOCF.setConfigured: URL: " + url); // NOI18N + } + configurable = false; + defaultConnectionId = + new FOStoreConnectionId(url, userName, password, create); + userConnectionId = new FOStoreConnectionId(url, null, null, false); + } + + private void assertConfigurable() { + if (configurable) return; + throw new JDOUserException ( + msg.msg("EXC_AssertConfigurableFailure")); // NOI18N + } + + /** + * Provides a connection to the database using the given userName and + * password. The first time a connection is made, the + * factory can no longer be configured. + * @return A FOStoreClientConnection + */ + public synchronized FOStoreClientConnection getConnection( + String user, String password) { + setConfigured(); // initializes userConnectionId with url. + FOStoreConnection rc = null; + + if (logger.isDebugEnabled()) { + logger.debug("FOCF.getConnection(" + user + ", " + + password + "): " + hashCode()); // NOI18N + } + // We reuse the same userConnectionId until we need to create a new + // Pool for it. The we create a new one for next time. + FOStoreConnectionId connectionId = userConnectionId; + connectionId.setUser (user); + connectionId.setPassword (password); + + // Try to find the existing connection in the pool. + // First time we get here, database is not yet open and connections + // are not yet created/in-pool. + Pool pool = (Pool) connectionMap.get (connectionId); + if (null == pool) { + pool = createPool (connectionId); + userConnectionId = new FOStoreConnectionId (url, null, null); + } + try { + // blocks until a connection is available. + return (FOStoreClientConnection) pool.get(); + } catch (InterruptedException ex) { + throw new JDOFatalInternalException( + msg.msg("ERR_PoolGet"), ex); // NOI18N + } + } + + /** Create a new pool of connections for this combination of url, user, + * and password. This might be either the default or for a specific user. + */ + Pool createPool (FOStoreConnectionId id) { + Pool pool = new Pool(poolSize); + if (connectionMap.put (id, pool) != null) { + throw new JDOFatalInternalException ( + msg.msg("ERR_DuplicatePool")); // NOI18N + } + try { + if (logger.isDebugEnabled()) { + logger.debug("FOCF: first time; filling pool"); // NOI18N + } + for (int i = 0; i < poolSize; i++) { + FOStoreClientConnection connection = + createConnection(id); + pool.put(connection); + } + } catch (InterruptedException ex) { + throw new JDOFatalInternalException( + msg.msg("ERR_PoolPutFill"), ex); // NOI18N + } + return pool; + } + + /** + * Provides a connection to the database using the configured userName, + * password, and url. The first time a connection is made, the + * factory can no longer be configured. + * @return A FOStoreClientConnection + */ + public synchronized FOStoreClientConnection getConnection() { + setConfigured(); + FOStoreConnection rc = null; + + if (logger.isDebugEnabled()) { + logger.debug("FOCF.getConnection(): " + hashCode()); // NOI18N + } + + // First time we get here, database is not yet open and connections + // are not yet created/in-pool. + + Pool pool = (Pool) connectionMap.get (defaultConnectionId); + if (null == pool) { + pool = createPool (defaultConnectionId); + } + try { + // blocks until a connection is available. + return (FOStoreClientConnection) pool.get(); + } catch (InterruptedException ex) { + throw new JDOFatalInternalException( + msg.msg("ERR_PoolGet"), ex); // NOI18N + } + } + + /** + * This method requires permission to perform the following requests: + * Create new URL with the specified StreamHandler. + * Delete old database id create flag is set to true. + */ + private FOStoreClientConnection createConnection( + final FOStoreConnectionId id) { + final FOStoreConnectionFactory cf = this; + return (FOStoreClientConnection) AccessController.doPrivileged ( + new PrivilegedAction () { + public Object run () { + URL uRL = null; + try { + uRL = new URL (null, url, streamHandler); + FOStoreClientConnection connection = + (FOStoreClientConnection) streamHandler.openConnection(uRL); + connection.setConnectionFactory(cf); + connection.setConnectionId(id); + connection.connect(); + return connection; + } catch (SecurityException se) { + throw new JDOFatalUserException( + msg.msg("EXC_CannotSpecifyStreamHandler"), se); //NOI18N + } catch (UnknownHostException ioe) { + throw new JDOFatalUserException ( + msg.msg("EXC_UnknownHostException", uRL.getHost()), ioe); // NOI18N + } catch (ConnectException ioe) { + int port = uRL.getPort(); + if (port == -1) + port = FOStoreRemoteConnection.DEFAULT_PORT; + throw new JDOFatalUserException( + msg.msg("EXC_ConnectException", // NOI18N + uRL.getHost(), new Integer(port)), + ioe); + } catch (IOException ioe) { + throw new JDOFatalUserException ( + msg.msg("EXC_CannotCreateConnection", url), ioe); // NOI18N + } + } + } + ); + } + + + /** + * Returns a connection to the pool + * @param connection Connection to be returned to the pool. + */ + void closeConnection(FOStoreClientConnection connection) { + FOStoreConnectionId id = connection.getConnectionId(); + Pool pool = (Pool) connectionMap.get(id); + try { + pool.put(connection); + } catch (InterruptedException ex) { + throw new JDOFatalInternalException( + msg.msg("ERR_CloseConnectionpoolPut"), ex); // NOI18N + } + } + + /** + * Close the database. This really means close all connections that + * have been opened. Closing the last connection on a database actually + * closes the database, whether local or remote. + */ + public synchronized void closeDatabase() { + Object timer = Tester.startTime(); + try { + for (Iterator hmi = connectionMap.values().iterator(); + hmi.hasNext();) { + + Pool dbPool = (Pool) hmi.next(); + // we know that there are poolSize entries in each pool + for (int i = 0; i < poolSize; ++i) { + FOStoreClientConnection focci = + (FOStoreClientConnection) dbPool.get(); + focci.closeDatabase(); + } + } + if (logger.isTraceEnabled()) { + Tester.printTime(timer, "Time to close database"); // NOI18N + } + } catch (FOStoreDatabaseException ex) { + throw new FOStoreFatalInternalException( + this.getClass(), + "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N + } catch (IOException ex) { + throw new FOStoreFatalInternalException( + this.getClass(), + "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N + } catch (InterruptedException ex) { + throw new FOStoreFatalInternalException( + this.getClass(), + "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N + } + if (logger.isDebugEnabled()) { + logger.debug("FOCF: closed " + url); // NOI18N + } + } + + /** + * Sets name of the driver for connections + * @param driverName driver name + */ + public void setDriverName(String driverName){ + assertConfigurable(); + this.driverName = driverName; + } + + /** + * Provides PersistenceManagerFactory for connections + * @return PMF + */ + public FOStorePMF getPMF() { + return pmf; + } + + /** + * Sets PersistenceManagerFactory for connections + * @param pmf PersistenceManagerFactory + */ + public void setPMF(FOStorePMF pmf){ + assertConfigurable(); + this.pmf = pmf; + } + + /** + * Provides name of driver used for connections + * @return driver name + */ + public String getDriverName() { + return driverName; + } + + /** + * Sets connection URL + * @param url connection URL + */ + public void setURL(String url) { + assertConfigurable(); + this.url = url; + } + + /** + * Returns connection URL + * @return connection URL + */ + public String getURL() { + return url; + } + + /** + * Sets database user + * @param userName database user + */ + public void setUserName(String userName) { + assertConfigurable(); + this.userName = userName; + } + + /** + * Returns database user name + * @return current database user name + */ + public String getUserName() { + return userName; + } + + /** + * Sets database user password + * @param password database user password + */ + public void setPassword(String password) { + assertConfigurable(); + this.password = password; + } + + /** + * Sets minimum number of connections in the connection pool + * @param minPool minimum number of connections + */ + public void setMinPool(int minPool) { + assertConfigurable(); + } + + /** + * Returns minimum number of connections in the connection pool + * @return connection minPool + */ + public int getMinPool() { + return 1; + } + + /** + * Sets maximum number of connections in the connection pool + * @param maxPool maximum number of connections + */ + public void setMaxPool(int maxPool) { + assertConfigurable(); + } + + /** + * Returns maximum number of connections in the connection pool + * @return connection maxPool + */ + public int getMaxPool() { + return 1; + } + + /** + * Sets the amount of time, in milliseconds, between the connection + * manager's attempts to get a pooled connection. + * @param msInterval the interval between attempts to get a database + * connection, in milliseconds. + * + */ + public void setMsInterval(int msInterval) { } + + /** + * Returns the amount of time, in milliseconds, between the connection + * manager's attempts to get a pooled connection. + * @return the length of the interval between tries in milliseconds + */ + public int getMsInterval() { + return 0; + } + + /** + * Sets the number of milliseconds to wait for an available connection + * from the connection pool before throwing an exception + * @param msWait number in milliseconds + */ + public void setMsWait(int msWait) { + assertConfigurable(); + } + + /** + * Returns the number of milliseconds to wait for an available connection + * from the connection pool before throwing an exception + * @return number in milliseconds + */ + public int getMsWait() { + return 0; + } + + /** + * Sets the LogWriter to which messages should be sent + * @param logWriter logWriter + */ + public void setLogWriter(PrintWriter logWriter) { + this.logWriter = logWriter; + } + + /** + * Returns the LogWriter to which messages should be sent + * @return logWriter + */ + public PrintWriter getLogWriter() { + return logWriter; + } + + /** + * Sets the number of seconds to wait for a new connection to be + * established to the data source + * @param loginTimeout wait time in seconds + */ + public void setLoginTimeout(int loginTimeout) { + assertConfigurable(); + this.loginTimeout = loginTimeout; + } + + /** + * Returns the number of seconds to wait for a new connection to be + * established to the data source + * @return wait time in seconds + */ + public int getLoginTimeout() { + return loginTimeout; + } + + /** + * Sets whether to create the database. + * @param create whether to create the database. + */ + public void setCreate(boolean create) { + assertConfigurable(); + this.create = create; + } + + /** + * Sets whether to create the database. + * @param create whether to create the database. + */ + public void setCreate(String create) { + assertConfigurable(); + this.create = Boolean.valueOf (create).booleanValue(); + } + + /** + * Returns whether to create the database. + * @return whether to create the database + */ + public boolean getCreate() { + return create; + } + + // + // Support for JNDI: we want to save/restore a FOStoreConnectionFactory + // via JNDI, and have the stored representation be via properties. + // + + /** CFAccessor implementation instances allow copying values to/from a + * FOStoreConnectionFactory + * and a Properties. They do the proper type translation too. + */ + interface CFAccessor extends Accessor { + /** @return String form of a value in a FOStoreConnectionFactory. + */ + public String get(FOStoreConnectionFactory focf); + + /** @return String form of a value in a FOStoreConnectionFactory if + * is not a default value. + */ + public String getNonDefault(FOStoreConnectionFactory focf); + + /** @param s String form of a value in a FOStoreConnectionFactory. + */ + public void set(FOStoreConnectionFactory focf, String s); + } + + // Initialize the property accessors map. + protected static void initPropsAccessors() { + synchronized (CFpropsAccessors) { + if (CFpropsAccessors.size() == 0) { + + CFpropsAccessors.put( + "org.apache.jdo.FOStoreConnectionFactory.option.URL", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return focf.getURL(); } + public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getURL(); } + public String getDefault() {return null;} + public void set(FOStoreConnectionFactory focf, String s) { focf.setURL(s); } + }); + CFpropsAccessors.put( + "org.apache.jdo.FOStoreConnectionFactory.option.UserName", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return focf.getUserName(); } + public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getUserName(); } + public String getDefault() {return null;} + public void set(FOStoreConnectionFactory focf, String s) { focf.setUserName(s); } + }); + CFpropsAccessors.put( + "org.apache.jdo.FOStoreConnectionFactory.option.Password", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return FOStorePMF.doEncrypt(focf.password); } + public String getNonDefault(FOStoreConnectionFactory focf) { return FOStorePMF.doEncrypt(focf.password); } + public String getDefault() {return null;} + public void set(FOStoreConnectionFactory focf, String s) { focf.setPassword(FOStorePMF.doDecrypt(s)); } + }); + CFpropsAccessors.put( + "org.apache.jdo.FOStoreConnectionFactory.option.DriverName", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return focf.getDriverName(); } + public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getDriverName(); } + public String getDefault() {return null;} + public void set(FOStoreConnectionFactory focf, String s) { focf.setDriverName(s); } + }); + CFpropsAccessors.put( + "org.apache.jdo.FOStoreConnectionFactory.option.LoginTimeout", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return focf.getDriverName(); } + public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getDriverName(); } + public String getDefault() {return null;} + public void set(FOStoreConnectionFactory focf, String s) { focf.setDriverName(s); } + }); + CFpropsAccessors.put( + "javax.jdo.FOStoreConnectionFactory.option.MinPool", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMinPool()); } + public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMinPool()==1)?null:Integer.toString(focf.getMinPool()); } + public String getDefault() { return "1"; } // NOI18N + public void set(FOStoreConnectionFactory focf, String s) { focf.setMinPool(toInt(s)); } + }); + CFpropsAccessors.put( + "javax.jdo.FOStoreConnectionFactory.option.MaxPool", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMaxPool()); } + public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMaxPool()==1)?null:Integer.toString(focf.getMaxPool()); } + public String getDefault() { return "1"; } // NOI18N + public void set(FOStoreConnectionFactory focf, String s) { focf.setMaxPool(toInt(s)); } + }); + CFpropsAccessors.put( + "javax.jdo.FOStoreConnectionFactory.option.MsWait", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMsWait()); } + public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMsWait()==0)?null:Integer.toString(focf.getMsWait()); } + public String getDefault() { return "0"; } // NOI18N + public void set(FOStoreConnectionFactory focf, String s) { focf.setMsWait(toInt(s)); } + }); + CFpropsAccessors.put( + "javax.jdo.FOStoreConnectionFactory.option.Create", // NOI18N + new CFAccessor() { + public String get(FOStoreConnectionFactory focf) { return new Boolean(focf.getCreate()).toString(); } + public String getNonDefault(FOStoreConnectionFactory focf) { return (!focf.getCreate())?null:"true"; } // NOI18N + public String getDefault() { return "false"; } // NOI18N + public void set(FOStoreConnectionFactory focf, String s) { focf.setCreate(Boolean.valueOf(s).booleanValue()); } + }); + } + } + } + + /** + * It should *never* be the case that our translation process encounters + * a NumberFormatException. If so, tell the user in the JDO-approved + * manner. + */ + private static int toInt(String s) { + int rc = 0; + try { + rc = new Integer(s).intValue(); + } catch (NumberFormatException ex) { + throw new JDOFatalInternalException( + msg.msg("ERR_Badformat")); // NOI18N + } + return rc; + } + + /** + * Sets properties as per the property values in the connection factory. + * For each CFAccessor in the given HashMap, gets the corresponding value + * from the FOStoreConnectionFactory and puts it in the given + * Properties object. + */ + void setProperties(Properties p) { + Set s = CFpropsAccessors.entrySet(); + for (Iterator i = s.iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry)i.next(); + String key = (String)e.getKey(); + CFAccessor a = (CFAccessor)e.getValue(); + String value = (String)a.getNonDefault(this); + if (null != value) { + p.put(key, value); + } + } + } + + /** + * Configures a FOStoreConnectionFactory from the given Properties. + * For each Accessor in the given HashMap, gets the corresponding value + * from the Properties and sets that value in the PMF. + * This is public so that a test program can create a + * FOSToreConnectionFactory, and configure it from a Properties object. + */ + public void setFromProperties(Properties p) { + Set s = CFpropsAccessors.entrySet(); + for (Iterator i = s.iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry)i.next(); + String key = (String)e.getKey(); + String value = p.getProperty(key); + if (null != value) { + CFAccessor a = (CFAccessor)e.getValue(); + if (logger.isDebugEnabled()) { + logger.debug("FOStoreConnectionFactory setting property: " + key + " to: " + value); // NOI18N + } + a.set(this, value); + } + } + } + + /** + * Returns true if this connection factory has been configured with a URL. + */ + public boolean isConfigured() { + if (logger.isDebugEnabled()) + logger.debug("FOStoreConnectionFactory url is: " + url); // NOI18N + return (url != null); + } + + public String toString() { + return "" + // NOI18N + "FOCF.url: " + url + "\n" + // NOI18N + "FOCF.userName: " + userName + "\n" + // NOI18N + "FOCF.password: " + password + "\n" + // NOI18N + "FOCF.driverName: " + driverName + "\n" + // NOI18N + "FOCF.loginTimeout: " + loginTimeout; // NOI18N + } +}
Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,180 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* + * FOStoreConnectionId.java + * + * Created on June 15, 2001, 1:30 PM + */ + +package org.apache.jdo.impl.fostore; + +/** + * Represents the information required to connect to a database. + * @author Craig Russell + * @version 1.0 + */ +class FOStoreConnectionId { + + /** The URL of this connection. Must not be null. + */ + private String url; + + /** The user id of this connection. May be null. + */ + private String user; + + /** The password of this connection. May be null. + */ + private String password; + + /** The flag telling whether to create. + */ + private boolean create; + + /** Creates new FOStoreConnectionId. + * @param url the URL of the connection. + * @param user the user id of the connection. + * @param password the password of the connection. + * @param create the flag whether to create the database. + */ + public FOStoreConnectionId(String url, String user, String password, boolean create) { + if (url == null) { + throw new NullPointerException(); + } + this.url = url; + this.user = user; + this.password = password; + this.create = create; + } + + + /** Creates new FOStoreConnectionId. + * @param url the URL of the connection. + * @param user the user id of the connection. + * @param password the password of the connection. + */ + public FOStoreConnectionId(String url, String user, String password) { + if (url == null) { + throw new NullPointerException(); + } + this.url = url; + this.user = user; + this.password = password; + this.create = false; + } + + public void setUrl (String url) { + if (url == null) { + throw new NullPointerException(); + } + this.url = url; + } + + public String getUrl () { + return url; + } + + public void setUser (String user) { + this.user = user; + } + + public String getUser() { + return user; + } + + public void setPassword (String password) { + this.password = password; + } + + public String getPassword () { + return password; + } + + public void setCreate (boolean create) { + this.create = create; + } + + public boolean getCreate() { + return create; + } + + /** Combine the hashCodes of URL, user, and password. + * @return the combined hashCode. + * Note that the create flag is not part of the hashCode. + */ + public int hashCode () { + int urlHashCode = url.hashCode(); + int userHashCode = user==null?0:user.hashCode(); + int passwordHashCode = password==null?0:password.hashCode(); + return urlHashCode + userHashCode + passwordHashCode; + } + + /** Returns true if this represents the same URL, + * user, and password as the other. + * @param other another connection id. + * @return true if this represents the same URL, + * user, and password as the other. + * Note that the create flag is not part of the identity. + */ + public boolean equals (Object other) { + if (this.getClass() != other.getClass()) return false; + FOStoreConnectionId foci = (FOStoreConnectionId) other; + return this.url.equals(foci.url) + & ((this.user!=null&foci.user!=null)?this.user.equals(foci.user):this.user==foci.user) + & ((this.password!=null&foci.password!=null)?this.password.equals(foci.password):this.password==foci.password); + } + + // XXX this 'main' really belongs in a test class... + public static void main (String argv[]) { + FOStoreConnectionId id0 = new FOStoreConnectionId("url", null, null); // NOI18N + FOStoreConnectionId id1 = new FOStoreConnectionId("url", "1", null); // NOI18N + FOStoreConnectionId id2 = new FOStoreConnectionId("url", null, "2"); // NOI18N + FOStoreConnectionId id3 = new FOStoreConnectionId("url", "1", "2"); // NOI18N + FOStoreConnectionId id4 = new FOStoreConnectionId("url2", "1", "2"); // NOI18N + FOStoreConnectionId id5 = new FOStoreConnectionId("url2", "1", "2", true); // NOI18N + try { + FOStoreConnectionId id = new FOStoreConnectionId (null, null, null); + System.out.println ("Failure."); // NOI18N + return; + } catch (NullPointerException npe) { + // good catch + } catch (Throwable t) { + System.out.println ("Failure."); // NOI18N + return; + } + if (!id0.equals(id1) + & (!id0.equals(id2)) + & (!id0.equals(id3)) + & (!id0.equals(id4)) + & (!id1.equals(id0)) + & (!id2.equals(id0)) + & (!id3.equals(id0)) + & (!id4.equals(id0)) + & (id0.equals(id0)) + & (id1.equals(id1)) + & (id2.equals(id2)) + & (id3.equals(id3)) + & (id4.equals(id4)) + & (id4.equals(id5)) + ) { + System.out.println ("Success."); // NOI18N + } else { + System.out.println ("Failure."); // NOI18N + } + } + +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnector.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnector.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnector.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreConnector.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,277 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import java.io.IOException; + +import javax.jdo.JDOException; +import javax.jdo.JDODataStoreException; +import javax.jdo.JDOFatalInternalException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.store.Connector; +import org.apache.jdo.util.I18NHelper; + +/** +* FOStoreConnector represents a connection to the FOStoreDatabase. +* +* @author Dave Bristor +*/ +class FOStoreConnector implements Connector { + /** @see org.apache.jdo.store.Connector#setRollbackOnly */ + private boolean rollbackOnly = false; + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + /** + * Message in which this Connector buffers requests for the store. + */ + private final Message message; + + /** + * Datasource to which this Connector writes its Message. + */ + private final FOStorePMF pmf; + + /** + * Connection for interacting with store. + */ + FOStoreClientConnection connection = null; + + /** + * True if we can release this connection after flushing. By default we + * can; affected by transactions beginning and ending and their types. + */ + private boolean okToReleaseConnection = true; + + /** True if flush is in progress. */ + private boolean busy = false; + + FOStoreConnector(FOStorePMF pmf) { + this.pmf = pmf; + this.message = new Message(this); + } + + // + // Implement Connector + // + + /** + * @see org.apache.jdo.store.Connector#begin + */ + public void begin(boolean optimistic) { + assertNotRollbackOnly(); + + // If transaction is optimistic, then we can release the connection as + // soon as data is flushed. If it's datastore, then we can't release + // the connection until after commit/rollback. + this.okToReleaseConnection = optimistic; + + if (logger.isDebugEnabled()) { + logger.debug( + "FOConnector.begin: okToReleaseConnection=" + // NOI18N + okToReleaseConnection); + } + + try { + RequestFactory rf = pmf.getRequestFactory(); + BeginTxRequest request = + rf.getBeginTxRequest(message, pmf, optimistic); + request.doRequest(); + } catch (IOException ex) { + throw new FOStoreFatalIOException( + getClass(), "update", ex); // NOI18N + } catch (JDOException ex) { + throw ex; + } catch (Exception ex) { + throw new FOStoreFatalInternalException( + getClass(), "update", ex); // NOI18N + } + } + + /** + * @see org.apache.jdo.store.Connector#beforeCompletion + */ + public void beforeCompletion() { + assertNotRollbackOnly(); + + // Nothing to do. + } + + /** + * Get a connection, process the message by using that connection to + * interact with the database, read back the reply, release the + connection. + * @see org.apache.jdo.store.Connector#flush + */ + public void flush() { + assertNotRollbackOnly(); + assertNotBusy("flush"); // NOI18N + busy = true; + + try { + + if (logger.isDebugEnabled()) { + logger.debug( + "FOConnector.flush: " + // NOI18N + "okToReleaseConnection=" + okToReleaseConnection + // NOI18N + ", connection=" + connection); // NOI18N + } + + if (message.hasRequests()) { + if (logger.isTraceEnabled()) message.dump(); + + if (connection == null) { + FOStoreConnectionFactory cf = + (FOStoreConnectionFactory)pmf.getConnectionFactory(); + connection = (FOStoreClientConnection)cf.getConnection(); + } + + // Now send the message and process it in the store + try { + message.processInStore(connection, okToReleaseConnection); + } finally { + if (okToReleaseConnection) { + connection = null; + } + } + } + } finally { + busy = false; + } + } + + /** + * Add a CommitRequest to the connector's message, and send it to the + * store. Then close the connection. + * @see org.apache.jdo.store.Connector#commit + */ + public synchronized void commit() { + assertNotRollbackOnly(); + assertNotBusy("commit"); // NOI18N + + try { + if (logger.isDebugEnabled()) { + logger.debug("FOConnector.commit"); // NOI18N + } + + RequestFactory rf = pmf.getRequestFactory(); + CommitRequest request = rf.getCommitRequest(message, pmf); + + request.doRequest(); + } catch (IOException ex) { + throw new FOStoreFatalIOException( + this.getClass(), "commit", ex); // NOI18N + } catch (JDOException ex) { + throw ex; + } catch (Exception ex) { + throw new FOStoreFatalInternalException( + getClass(), "commit", ex); // NOI18N + } finally { + // Now that we've commited, we can release the connection. + okToReleaseConnection = true; + flush(); + } + } + + /** + * If rollbackOnly is set, then the store has already done a + * rollback, so we don't do one now (but neither do we throw an + * exception, as do other methds). + * @see org.apache.jdo.store.Connector#rollback + * @see org.apache.jdo.impl.fostore.ReplyHandler#processReplies + */ + public synchronized void rollback() { + assertNotBusy("rollback"); // NOI18N + if (logger.isDebugEnabled()) { + logger.debug( + "FOConnector.rollback, RBO=" + rollbackOnly); // NOI18N + } + + if (! rollbackOnly) { + try { + RequestFactory rf = pmf.getRequestFactory(); + RollbackRequest request = rf.getRollbackRequest(message, pmf); + + request.doRequest(); + } catch (IOException ex) { + throw new FOStoreFatalIOException( + this.getClass(), "rollback", ex); // NOI18N + } catch (JDOException ex) { + throw ex; + } catch (Exception ex) { + throw new FOStoreFatalInternalException( + getClass(), "rollback", ex); // NOI18N + } finally { + // Now that we've rolled back, we can release the connection. + okToReleaseConnection = true; + flush(); + } + } + } + + /** + * @see org.apache.jdo.store.Connector#setRollbackOnly + */ + public void setRollbackOnly() { + rollbackOnly = true; + } + + /** + * @see org.apache.jdo.store.Connector#getRollbackOnly + */ + public boolean getRollbackOnly() { + return rollbackOnly; + } + + // + // Implementation + // + + /** + * Provides the Message which this this connector uses to send data to the + * store. + */ + Message getMessage() { + assertNotRollbackOnly(); + assertNotBusy("getMessage"); // NOI18N + + return message; + } + + private void assertNotRollbackOnly() { + if (rollbackOnly) { + throw new JDODataStoreException( + msg.msg("EXC_RollbackOnly")); // NOI18N + } + } + + private void assertNotBusy(String methodName) { + if (busy) { + throw new FOStoreFatalInternalException( + getClass(), methodName, + msg.msg("EXC_Busy")); // NOI18N + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabase.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabase.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabase.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabase.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,574 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; +import java.util.Set; +import java.util.HashMap; + +import javax.jdo.JDOFatalUserException; +import javax.jdo.JDOFatalException; +import javax.jdo.JDOFatalDataStoreException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.util.I18NHelper; +import org.apache.jdo.util.Pool; +import org.netbeans.mdr.persistence.Storage; +import org.netbeans.mdr.persistence.SinglevaluedIndex; +import org.netbeans.mdr.persistence.StorageBadRequestException; +import org.netbeans.mdr.persistence.StorageException; +import org.netbeans.mdr.persistence.Streamable; + +/** +* File/Object Store Database, using an underlying Btree implementation. +* +* @author Dave Bristor +*/ +class FOStoreDatabase { + + /** We manage a map of pools of databases by database name. + */ + private static final HashMap databaseMap = new HashMap(); + + private static final int poolSize = 1; + + // We delegate all calls to this storage. Not final, because for + // rollback's sake we have to close and then make a new storage. + private FOStoreBtreeStorage storage; + + // Map from provisional IDs to real, datastore-created, durable IDs. + // + // XXX TBD Remote: Provisional CLID's are per client (PMF). + // That is because each client will independently generate its + // own set of provisional ID's. One way to manage this might be for + // each client to send a "hello" message when it first connects, with + // something like it's IP address and process id. Future communications + // from that same client will contain that same info. A "goodbye" message + // would be nice, too, so that the store can remove any tables that will + // not ever be used again. + private final HashMap provisionalCLIDs = new HashMap(); + + // Same as above, but for OIDs. + private final HashMap provisionalOIDs = new HashMap(); + + /** Count the number of connections open on this database. When the last + * connection closes, then really close the database. + */ + private int openConnections = 0; + + private DBInfo dbInfo = null; + /** Pool for this database. + */ + private Pool pool = null; + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + /** Find an open database of the given name. If it is not already open, + * open it and put it into the pool. + */ + public synchronized static FOStoreDatabase getDatabase (String dbname, + boolean create) + throws InterruptedException, FOStoreDatabaseException { + Pool pool = (Pool) databaseMap.get(dbname); + FOStoreDatabase db = null; + if (pool == null) { + try { + db = new FOStoreDatabase (dbname, create); + } catch (FOStoreDatabaseException fode) { + if (logger.isDebugEnabled()) { + logger.debug( + "FODB:getDatabase exception for new database " + // NOI18N + dbname + " create: " + create); // NOI18N + } + throw (fode); + } + pool = new Pool (poolSize); + databaseMap.put (dbname, pool); + pool.put (db); + db.setPool(pool); + } + if (logger.isDebugEnabled()) { + logger.debug("FODB:getDatabase found pool for database " + dbname); // NOI18N + } + db = (FOStoreDatabase) pool.get(); + return db; + } + + /** Release the database for other connections to use. + */ + public synchronized static void releaseDatabase (FOStoreDatabase db) + throws InterruptedException { + + db.pool.put (db); + } + + /** + * Create an FODatabase and create/open the files. + * @param name name of database, which is used to name the files + * @param isNew true if the database is being created + */ + FOStoreDatabase(String name, boolean isNew) + throws FOStoreDatabaseException { + // first verify that the directory exists + // if not, there is no way to continue + // Note, the filename might include a mixture of \ and / as + // path separator, make them all / for the lookup + int slashIndex = name.replace('\\', '/').lastIndexOf('/'); + if (slashIndex != -1) { // at least one / in pathname + String dirName = name.substring(0, slashIndex); + File dir = new File(dirName); // NOI18N + if (!existsFile(dir)) { + throw new JDOFatalDataStoreException ( + msg.msg("ERR_DirectoryDoesNotExist", name)); // NOI18N + } + } + // XXX the DBInfo is not a singleton; change this code + // and DBInfo to support multiple Databases in one VM. + if (isNew) { + // We must remove a database if one does exist. This means delete + // both the .btd and .btx files. + String extensions[] = {"btd", "btx"}; // NOI18N + boolean found = false; + for (int i = 0; i < extensions.length; i++) { + File f = new File(name + "." + extensions[i]); // NOI18N + if (existsFile(f)) { + if (! found) { + if (logger.isDebugEnabled()) { + logger.debug("FODB: Deleting existing database " + + name); // NOI18N + } + found = true; + } + deleteFile(f); + } + } + storage = createBtreeStorage(name, true); + dbInfo = new DBInfo(this); + dbInfo.store(); + commitChanges(); + if (logger.isDebugEnabled()) { + logger.debug("FODB: Created database " + name); // NOI18N + } + } else { + storage = createBtreeStorage(name, false); + dbInfo = DBInfo.get(this); + if (logger.isDebugEnabled()) { + logger.debug( + "FODB: Opened existing database " + name); // NOI18N + } + } + } + + /** Verify that this user is authorized to use this database. + */ + public void verifyUserPassword (String user, long timestamp, byte[] secret) { + // XXX look up Authentication objects and verify this user is authorized. + addConnection(); + } + + /** Get the DBInfo for this database. + */ + public DBInfo getDBInfo() { + return dbInfo; + } + + /** Associates the specified value with the specified key in this index. + * @return true if there was an item in this index that was associated + * with the key prior to this call + * @param key + * @param value + * @throws FOStoreDatabaseException + */ + public boolean put(OID key, Object value) throws FOStoreDatabaseException { + try { + Object keyValue = key.keyValue(storage); +// Uncomment the next lines to simulate a fatal error during login. +// if (null != keyValue) +// throw new NullPointerException("fodb: NPE on purpose"); // YYY + return storage.getPrimaryIndex().put(keyValue, value); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Replaces the original value associated with the specified key in this + * index with new value. If no value was associated with this key prior + * to this call StorageBadRequestException is thrown. + * @param key + * @param value + * @throws FOStoreDatabaseException + */ + public void replace(OID key, Object value) + throws FOStoreDatabaseException { + + try { + Object keyValue = key.keyValue(storage); + storage.getPrimaryIndex().replace(keyValue, value); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Returns the value to which this index maps the specified key. + * StorageBadRequestException is thrown if there is no value for the key. + * @return value associated with specified key + * @param key + * @throws FOStoreDatabaseException + */ + public Object get(OID key) + throws FOStoreDatabaseException { + + try { + Object keyValue = key.keyValue(storage); + return storage.getPrimaryIndex().get(keyValue); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Returns the value to which this index maps the specified key + * or null if there is no value for this key. + * @return value associated with specified key or null + * @param key + * @throws FOStoreDatabaseException + */ + public Object getIfExists (OID key) throws FOStoreDatabaseException { + try { + Object keyValue = key.keyValue(storage); + return storage.getPrimaryIndex().getIfExists(keyValue); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Returns the unique name of the index in the Storage. + * @return The name of this index. + */ + public String getName() { + return storage.getName(); + } + + /** Returns a set view of the keys contained in this index. + * Returned set is read only and may not be modified. + * @return keys contained in this index + * @throws FOStoreDatabaseException + */ + public Set keySet() throws FOStoreDatabaseException { + try { + return storage.getPrimaryIndex().keySet(); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Adds the specified value to values associated in this index with the + * specified key. If the index puts limit on number of values associated + * with one key and adding value would break this limit, it thorows + * StorageBadRequestException. + * @param key + * @param value + * @throws FOStoreDatabaseException + */ + public void add(OID key, Object value) throws FOStoreDatabaseException { + try { + Object keyValue = key.keyValue(storage); + storage.getPrimaryIndex().add(keyValue, value); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + /** Removes all values assosiated in the index with specified key. + * @return true if this index changed as a result of this call + * @param key + * @throws FOStoreDatabaseException + */ + public boolean remove (OID key) throws FOStoreDatabaseException { + try { + Object keyValue = key.keyValue(storage); + return storage.getPrimaryIndex().remove(keyValue); + } catch (StorageException ex) { + throw new FOStoreDatabaseException(ex); + } + } + + public void commitChanges() throws FOStoreDatabaseException { + if (logger.isDebugEnabled()) logger.debug("FODB.commitChanges"); // NOI18N + + // Need to have privileges to perform this operation. + final FOStoreBtreeStorage storage1 = storage; + try { + AccessController.doPrivileged ( + new PrivilegedExceptionAction () { + public Object run () throws StorageException { + storage1.commitChanges(); + return null; + } + } + ); + } catch (PrivilegedActionException ex) { + throw new FOStoreDatabaseException( + (StorageException)ex.getException()); + } + + } + + /** Increment the openConnections to allow proper close when the last + * connection closes the database. + */ + public synchronized void addConnection() { + openConnections++; + } + + /** Decrement the open connections counter and close the database when + * it reaches zero. + */ + public synchronized void close() throws FOStoreDatabaseException { + if (--openConnections > 0) { + if (logger.isDebugEnabled()) { + logger.debug("FODB.close: " + getName() + // NOI18N + " count still " + openConnections); // NOI18N + } + try { + releaseDatabase(this); + } catch (InterruptedException ex) { + // ignore + } + return; + } + // count has reached zero. Now really close the database. + if (logger.isDebugEnabled()) { + logger.debug( + "FODB.close: count reached zero." + getName()); // NOI18N + } + + closeBtreeStorage(storage); + databaseMap.remove(getName()); + pool = null; + } + + public static synchronized void closeDatabase (String dbname) + throws FOStoreDatabaseException, InterruptedException { + + Pool pool = (Pool) databaseMap.get(dbname); + if (pool == null) { + throw new JDOFatalDataStoreException ( + msg.msg("ERR_CloseDatabase", dbname)); // NOI18N + } + FOStoreDatabase fodb = (FOStoreDatabase) pool.get(); + fodb.close(); + } + + public void rollbackChanges() throws FOStoreDatabaseException { + if (logger.isDebugEnabled()) logger.debug("FODB.rollbackChanges"); // NOI18N + + // The btree doesn't *really* rollback. But closing it and + // getting a new one works fine. + final String name = storage.getName(); + closeBtreeStorage(storage); + + storage = createBtreeStorage(name, false); + dbInfo = DBInfo.get(this); + } + + static Block createBlock(FOStoreInput in, int length) { + return new Block(in, length); + } + + /** + * Establishes a mapping in this database from provisional to real + * CLIDs. The mapping lasts as long as the server is running, that is, it + * is not durable. + * @param pCLID A provisional CLID. + * @param rCLID The non-provisional, datastore CLID corresponding to + * provCLID. + * @exception JDOFatalException Thrown if the given CLID is not provisional. + */ + // If you change this code, please see also method of same name in FOStorePMF. + public synchronized void mapProvisionalCLIDToReal( + CLID pCLID, CLID rCLID) { + + if (! pCLID.isProvisional()) { + throw new JDOFatalException(msg.msg("ERR_CLIDNotProv", pCLID)); // NOI18N + } + if (null == provisionalCLIDs.get(pCLID)) { + provisionalCLIDs.put(pCLID, rCLID); + } + if (logger.isDebugEnabled()) { + logger.debug( + "FODB.mapProvCLIDToReal: " + pCLID + " -> " + rCLID); // NOI18N + } + } + + /** + * Provides a datastore CLID corresponding to the given provisional CLID. + * @param provCLID A provisional CLID for which there + * @return A non-provisional, datastore CLID corresponding to provCLID, or + * null if the given provCLID is not mapped to a datastore CLID. + * @exception JDOFatalException Thrown if the given CLID is not provisional. + */ + // If you change this code, see method of same name in FOStorePMF. + public CLID getRealCLIDFromProvisional(CLID provCLID) { + if (! provCLID.isProvisional()) { + throw new JDOFatalException(msg.msg("ERR_CLIDNotProv", provCLID)); // NOI18N + } + return (CLID)provisionalCLIDs.get(provCLID); + } + + /** + * Establishes a mapping in this database from provisional to real + * OIDs. The mapping lasts as long as the server is running, that is, it + * is not durable. + * @param pOID A provisional OID. + * @param rOID The non-provisional, datastore OID corresponding to + * pOID. + * @exception JDOFatalException Thrown if the given OID is not provisional. + */ + // If you change this code, please see also method of same name in FOStorePMF. + synchronized void mapProvisionalOIDToReal( + OID pOID, OID rOID) { + + if (! pOID.isProvisional()) { + throw new JDOFatalException(msg.msg("ERR_OIDNotProv", pOID)); // NOI18N + } + if (null == provisionalOIDs.get(pOID)) { + provisionalOIDs.put(pOID, rOID); + } + } + + /** + * Provides a datastore OID corresponding to the given provisional OID. + * @param pOID A provisional OID for which there + * @return A non-provisional, datastore OID corresponding to pOID or null + * if the given pOID is not mapped to a datastore OID. + * @exception JDOFatalException Thrown if the given OID is not provisional. + */ + // If you change this code, see method of same name in FOStorePMF. + OID getRealOIDFromProvisional(OID pOID) { + if (! pOID.isProvisional()) { + throw new JDOFatalException(msg.msg("ERR_OIDNotProv", pOID)); // NOI18N + } + return (OID)provisionalOIDs.get(pOID); + } + + private void setPool (Pool pool) { + this.pool = pool; + } + + /** + * Helper method to create a BtreeStorage. This method excutes the + * storage operation in a doPrivileged block and handles possible + * exceptions. + * @param name name of database, which is used to name the files + * @param isNew true if the database is being created + * @return new database + * @exception FOStoreDatabaseException wraps a StorageException thrown + * by the create call. + * @exception JDOFatalUserException wraps a possible SecurityException. + */ + private FOStoreBtreeStorage createBtreeStorage(final String name, + final boolean isNew) + throws FOStoreDatabaseException, JDOFatalUserException + { + try { + return (FOStoreBtreeStorage) AccessController.doPrivileged ( + new PrivilegedExceptionAction () { + public Object run () throws StorageException { + return new FOStoreBtreeStorage(name, isNew); + } + }); + } catch (SecurityException ex) { + throw new JDOFatalUserException(msg.msg( + "EXC_SecurityExceptionOnCreateBtreeStorage"), ex); //NOI18N + } catch (PrivilegedActionException ex) { + throw new FOStoreDatabaseException( + (StorageException)ex.getException()); + } + } + + /** + * Helper method to close a BtreeStorage. + * @param storage the database to be closed + * @exception FOStoreDatabaseException wraps a StorageException thrown + * by the close call. + */ + private void closeBtreeStorage(final FOStoreBtreeStorage storage) + throws FOStoreDatabaseException + { + try { + AccessController.doPrivileged ( + new PrivilegedExceptionAction () { + public Object run () throws StorageException { + storage.shutDown(); + storage.close(); + return null; + } + }); + } catch (SecurityException ex) { + throw new JDOFatalUserException(msg.msg( + "EXC_SecurityExceptionOnCloseBtreeStorage"), ex); //NOI18N + } catch (PrivilegedActionException ex) { + throw new FOStoreDatabaseException( + (StorageException)ex.getException()); + } + } + + /** + * Helper method to check whether a file exists. This method delegates + * to File.exists and handles possible SecurityExceptions. + * @param file the file to be checked + * @return <code>true</code> if the specified file exists. + */ + private boolean existsFile(File file) { + try { + return file.exists(); + } catch (SecurityException ex) { + throw new JDOFatalUserException(msg.msg( + "EXC_CannotReadFile", file.getName()), ex); //NOI18N + } + } + + /** + * Helper method to delete a file. This method delegates to File.delete + * and handles possible SecurityExceptions. + * @param file the file to be deleted + */ + private void deleteFile(File file) { + try { + file.delete(); + } catch (SecurityException ex) { + throw new JDOFatalUserException(msg.msg( + "EXC_CannotDeleteFile", file.getName()), ex); //NOI18N + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabaseException.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabaseException.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabaseException.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreDatabaseException.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,56 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import org.netbeans.mdr.persistence.StorageException; + +/** +* This provides an insulation layer between BtreeDatabase and FOStore. +* +* @author Dave Bristor +*/ +class FOStoreDatabaseException extends StorageException { + private final StorageException ex; + + FOStoreDatabaseException(StorageException ex) { + this.ex = ex; + } + + public String getLocalizedMessage() { + return ex.getLocalizedMessage(); + } + + public String getMessage() { + return ex.getMessage(); + } + + public void printStackTrace() { + ex.printStackTrace(); + } + + public void printStackTrace(java.io.PrintStream s) { + ex.printStackTrace(s); + } + + public void printStackTrace(java.io.PrintWriter s) { + ex.printStackTrace(s); + } + + public String toString() { + return ex.toString(); + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreExtent.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreExtent.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreExtent.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreExtent.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,304 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import java.io.DataInput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.jdo.Extent; +import javax.jdo.JDOHelper; +import javax.jdo.JDOException; +import javax.jdo.JDOUserException; +import javax.jdo.PersistenceManager; +import javax.jdo.Transaction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.jdo.pm.PersistenceManagerInternal; +import org.apache.jdo.state.StateManagerInternal; +import org.apache.jdo.util.I18NHelper; + +/** + * Represents a request to get the extent of a class and possibly its + * subclasses. + * + * @author Dave Bristor + */ +// +// This is client-side code. It does not need to live in the server. +// +/** + * This is an in-memory extent. + */ +class FOStoreExtent implements Extent { +private final FOStoreConnector connector; +private final RequestFactory rf; + + /** Iterators requested by user. */ + private final HashSet iterators = new HashSet(); + + /** Class specified by user. */ + private final Class cls; + + /** If true, extent includes subclasses of user's class. */ + private final boolean subclasses; + + /** Persistence manager on which getExtent was invoked by user. */ + private final PersistenceManagerInternal pm; + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + /** Logger */ + static final Log logger = LogFactory.getFactory().getInstance( + "org.apache.jdo.impl.fostore"); // NOI18N + + FOStoreExtent(Class cls, boolean subclasses, + PersistenceManagerInternal pm, + RequestFactory rf, + FOStoreConnector connector) { + + this.cls = cls; + this.subclasses = subclasses; + this.pm = pm; + this.rf = rf; + this.connector = connector; + } + + // + // Implement Extent + // + + /** + * @see javax.jdo.Extent#iterator + */ + public Iterator iterator() { + pm.assertReadAllowed(); + Iterator rc = new FOStoreExtentIterator(this); + iterators.add(rc); + return rc; + } + + /** + * @see javax.jdo.Extent#hasSubclasses + */ + public boolean hasSubclasses() { + return subclasses; + } + + /** + * @see javax.jdo.Extent#getCandidateClass + */ + public Class getCandidateClass() { + return cls; + } + + /** + * @see javax.jdo.Extent#getPersistenceManager + */ + public PersistenceManager getPersistenceManager() { + return pm.getCurrentWrapper(); + } + + /** + * @see javax.jdo.Extent#closeAll + */ + public void closeAll() { + for (Iterator i = iterators.iterator(); i.hasNext();) { + Iterator extentIterator = (Iterator)i.next(); + if (logger.isDebugEnabled()) { + logger.debug("Extent.closeAll: removing " + extentIterator); // NOI18N + } + i.remove(); // remove first because close also removes it + close(extentIterator); + } + } + + /** + * @see javax.jdo.Extent#close + */ + public void close(Iterator it) { + if (it instanceof FOStoreExtentIterator) { + FOStoreExtentIterator fit = (FOStoreExtentIterator)it; + fit.close(); + if (iterators.contains(fit)) { + if (logger.isDebugEnabled()) { + logger.debug("Extent.close: removing " + fit); // NOI18N + } + // this will be true only if not executing closeAll. + iterators.remove(fit); + } + } + } + + /** + * Iterates over the elements in a FOStoreExtent. + */ + class FOStoreExtentIterator implements Iterator { + /** Instances that have been retrieved from store. */ + // We have our own copy and do not use those from the enclosing + // FOStoreExtent, because we use a GetInstancesRequest to re-fill + // the ArrayList if needed. + private ArrayList instances; + private ArrayList oids; + private int maxInstances; + + /** Index into instances. */ + private int instanceIndex = 0; + + /** Index into oids. */ + private int oidsIndex = 0; + + /** Index into extent as a whole.*/ + private int index = 0; + + /** Size of extent (number of instances + number of oids). */ + private final int size; + + /** If false, then can get next object, otherwise + * next() always throws NoSuchElementException and + * hasNext() always returns false. */ + private boolean closed = false; + + private Object nextObject = null; + + FOStoreExtentIterator(FOStoreExtent extent) { + try { + Message message = connector.getMessage(); + GetExtentRequest request = + rf.getGetExtentRequest(extent, cls, subclasses, message, pm); + request.doRequest(); + connector.flush(); + instances = request.getInstances(); + oids = request.getOIDs(); + maxInstances = request.getMaxInstances(); + } catch (IOException ex) { + throw new FOStoreFatalIOException( + getClass(), "init", ex); // NOI18N + } catch (JDOException ex) { + throw ex; + } catch (Exception ex) { + throw new FOStoreFatalInternalException( + getClass(), "init", ex); // NOI18N + } + + Collection insertedInstances = pm.getInsertedInstances(); + for (Iterator it = insertedInstances.iterator(); it.hasNext();) { + Object o = it.next(); + Class clz = o.getClass(); + if (cls.equals(clz) || (subclasses && cls.isAssignableFrom(clz))) { + instances.add(o); + } + } + + if (logger.isDebugEnabled()) { + logger.debug( + "FOStoreExtentIterator.<init>: cls=" + cls.getName() + // NOI18N + ", numInstances=" + instances.size()); // NOI18N + } + + this.size = instances.size() + oids.size(); + if (size == 0) { + close(); + } + } + + /** + * @see java.util.Iterator#hasNext + */ + public boolean hasNext() { + // This code advances to the next not deleted + // instance, or until the iterator is closed. + // Iterator is closed in getNext() if there + // are no more instances available. nextObject + // is null at the beginning and after close(). + while (! closed && (nextObject == null || + JDOHelper.isDeleted(nextObject))) { + getNext(); + } + return (nextObject != null); + } + + /** + * @see java.util.Iterator#next + */ + public Object next() { + if (nextObject == null && !hasNext()) { + throw new NoSuchElementException(); + } + + Object rc = nextObject; + nextObject = null; + return rc; + } + + /** Get the next instance. Close the iterator if there are + * no more instances available. + */ + private void getNext() { + if (index < size) { + if (instanceIndex >= instances.size()) { + if (logger.isDebugEnabled()) { + logger.debug( + "FOStoreExtentIterator.next get new instances"); // NOI18N + } + FOStoreStoreManager srm = + (FOStoreStoreManager)pm.getStoreManager(); + instances = srm.getInstances( + oids, oidsIndex, maxInstances, pm, cls); + oidsIndex += instances.size(); + if (logger.isDebugEnabled()) { + logger.debug( + "FOStoreExtent.next got " + // NOI18N + instances.size() + " instances"); // NOI18N + } + instanceIndex = 0; + } + nextObject = instances.get(instanceIndex); + instanceIndex++; + index++; + + } else { + close(); + } + } + + /** + * Always throws UnsupportedOperationException. + */ + public void remove() { + throw new UnsupportedOperationException( + msg.msg("EXC_RemoveNotSupported")); // NOI18N + } + + /** + * Disallow getting further objects from this iterator. + */ + void close() { + closed = true; + instances = null; + index = size; + nextObject = null; + } + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalIOException.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalIOException.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalIOException.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalIOException.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,35 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +/** +* This is an exception which _should_ never be thrown, as it indicates an +* error in I/O traffic between client and server. It most likely indicates a +* bug in the implementation. +* +* @author Dave Bristor +*/ +public class FOStoreFatalIOException extends FOStoreFatalInternalException { + /** + * @param clz Class in which the exception is thrown. + * @param methodName Name of the method from which the exception is + * thrown. + */ + FOStoreFatalIOException(Class clz, String methodName, Exception nested) { + super(clz, methodName, nested); + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalInternalException.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalInternalException.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalInternalException.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreFatalInternalException.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,61 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import javax.jdo.JDOFatalInternalException; + +/** +* This is an exception which _should_ never be thrown, as it indicates an +* error in the implementation, such as a bug that has been found. +* +* @author Dave Bristor +*/ +public class FOStoreFatalInternalException extends JDOFatalInternalException { + /** + * @param clz Class in which the exception is thrown. + * @param methodName Name of the method from which the exception is + * thrown. + * @param msg The exception message. + */ + FOStoreFatalInternalException(Class clz, String methodName, String msg) { + super(clz.getName() + "." + methodName + ": " + msg); // NOI18N + } + + /** + * @param clz Class in which the exception is thrown. + * @param methodName Name of the method from which the exception is + * thrown. + */ + FOStoreFatalInternalException(Class clz, String methodName, + Exception nested) { + super(clz.getName() + "." + methodName, // NOI18N + new Exception[] {nested}); // NOI18N + } + + /** + * @param clz Class in which the exception is thrown. + * @param methodName Name of the method from which the exception is + * thrown. + * @param msg The exception message. + */ + FOStoreFatalInternalException(Class clz, String methodName, String msg, + Exception nested) { + + super(clz.getName() + "." + methodName + ": " + msg, // NOI18N + new Exception[] {nested}); // NOI18N + } +} Added: incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreInput.java URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreInput.java?view=auto&rev=158176 ============================================================================== --- incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreInput.java (added) +++ incubator/jdo/trunk/ri11/src/java/org/apache/jdo/impl/fostore/FOStoreInput.java Fri Mar 18 17:02:29 2005 @@ -0,0 +1,135 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.jdo.impl.fostore; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; + +import org.apache.jdo.util.I18NHelper; + + +/** +* Extend ByteArrayInputStream so that we can get ahold of the byte array +* and current position, and can change the current position. +* +* @author Dave Bristor +*/ +class FOStoreInput extends ByteArrayInputStream implements DataInput { + private final DataInputStream dis; + + /** I18N support. */ + private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME); + + FOStoreInput(byte data[], int off, int len) { + super(data, off, len); + dis = new DataInputStream(this); + } + + byte[] getBuf() { + return buf; + } + + int getPos() { + return pos; + } + + void setPos(int pos) { + if (pos < 0 || pos > buf.length) { + throw new FOStoreFatalInternalException( + this.getClass(), "setPos", // NOI18N + msg.msg("ERR_InvalidSeekPos", // NOI18N + new Integer(pos), new Integer(buf.length))); + } + this.pos = pos; + } + + // Advance pos by len, but don't break invariant required by + // ByteArrayInputStream (see ByteArrayInputStrea.pos's javadoc). + void advance(int len) { + if (pos + len <= count) { + pos += len; + } else { + pos = count; + } + } + + // + // Implement DataInput + // + + public boolean readBoolean() throws IOException { + return dis.readBoolean(); + } + + public byte readByte() throws IOException { + return dis.readByte(); + } + + public char readChar() throws IOException { + return dis.readChar(); + } + + public double readDouble() throws IOException { + return dis.readDouble(); + } + + public float readFloat() throws IOException { + return dis.readFloat(); + } + + public void readFully(byte[] b) throws IOException { + dis.readFully(b); + } + + public void readFully(byte[] b, int off, int len) throws IOException { + dis.readFully(b, off, len); + } + + public int readInt() throws IOException { + return dis.readInt(); + } + + public String readLine() throws IOException { + return dis.readLine(); + } + + public long readLong() throws IOException { + return dis.readLong(); + } + + public short readShort() throws IOException { + return dis.readShort(); + } + + public int readUnsignedByte() throws IOException { + return dis.readUnsignedByte(); + } + + public int readUnsignedShort() throws IOException { + return dis.readUnsignedShort(); + } + + public String readUTF() throws IOException { + return dis.readUTF(); + } + + public int skipBytes(int n) throws IOException { + return dis.skipBytes(n); + } +}