📄 abstractfactory.java
字号:
// You can redistribute this software and/or modify it under the terms of
// the Ozone Library License version 1 published by ozone-db.org.
//
// The original code and portions created by SMB are
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
//
// $Id: AbstractFactory.java,v 1.9 2003/04/27 13:43:51 per_nyfelt Exp $
package org.ozoneDB;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
/** <p>Abstract base class for all ozone-compatible factories. Normally such
* factories are generated by OPP and you need not bother with them.
* A factory has a bit of a schizophrenic nature: on the client-side it 'links'
* to an ExternalDatabase, while on the server-side it does so to the Database
* that holds the instances that the factory creates. Note however that a
* factory running inside an Ozone server can also be linked to an External
* database outside that server (userclient -> server A -> server B). In that
* case such a factory would be 'linked' an ExternalDatabase.</p>
* <p>The idea behind factories is fourfold:<ul>
* <li>make typecasts for creating and retrieving (proxy)objects unneccessairy</li>
* <li>provide an abstraction from Ozone specific object creation</li>
* <li>facilitate creating objects with non-default constructors</li>
* <li>provide (almost) the same interface on the server side and the client
* side for object creation</li></ul>
* The differences in client side and server side operation are:<ul>
* <li>on the server side <code>getDefault()</code> returns a factory that is
* creates its objects inside that same server database and on the client side
* it returns a factory that creates its objects in the default database</li>
* <li>In order to use <code>getDefault()<code> on the client side, you need to
* call <code>setDefaultDatabaseUrl(String)</code> to specify the default
* database.</li>
* In a typical real-world scenario where you connect to only one ozone database
* you would call <code>setDefaultDatabaseUrl(String)</code> on the client only
* once, and for the rest of the programs lifespan call
* <code>xxxFactory.getDefault().create()</code> methods both on the server and
* client sides to create objects.</p>
* <p>Note: if you do not have a clue what factories are and how they work, you
* should probably brush up on your knowledge of <i>design patterns</i>.
* @author <a href="mailto:ozoneATmekenkampD0Tcom">Leo Mekenkamp (mind the anti-sp@m)</a>
* @since REPLACE_ME_SINCE
*/
public abstract class AbstractFactory {
private static class DatabaseInfo {
private OzoneInterface database;
private boolean autoClose = true;
private int useCnt = 0;
public DatabaseInfo(OzoneInterface database) {
this.database = database;
}
public final void pin() {
useCnt++;
}
public final void unpin() {
useCnt--;
try {
// note that a database may already have been closed by closeDatabase()
if ((!isPinned()) && getAutoClose() && ((ExternalDatabase) database).isOpen()) {
((ExternalDatabase) database).close();
}
} catch (Exception e) {
throw new UnexpectedException("could not close database " + e.getMessage());
}
}
public final boolean isPinned() {
return useCnt != 0;
}
public final boolean getAutoClose() {
return autoClose;
}
public final void setAutoClose(final boolean autoClose) {
if (database instanceof ExternalDatabase) {
this.autoClose = autoClose;
} else {
throw new UnexpectedException("cannot change autoClose on server side database");
}
}
public final OzoneInterface getDatabase() {
return database;
}
}
/** Keeps track of which factory classes there are; all in this collection
* can be notified through calling 'defaultDatabaseUrlChanged' by a default
* url change.
*/
private static Collection factoryClassInfos = new ArrayList();
/** holds which databases are opened as client (actually holds databaseInfo
* instances)
*/
private static HashMap databaseInfoMap = new HashMap();
/** client side only */
private static String defaultDatabaseUrl = null;
/** Called by the {@link Database} constructor only!
*/
static void setDefaultDatabase(Database defaultDb) {
// synchronization here probably not needed, but doing it just in case;
// as this is only called once, there is little performace loss
synchronized(databaseInfoMap) {
DatabaseInfo defaultDatabaseInfo = new DatabaseInfo(defaultDb);
databaseInfoMap.put(null, defaultDatabaseInfo);
}
}
/** <p>Sets the url to identify the default database. This method can
* only be called from a client; a call from within the server will surely
* result in an exception, because in the server the default database is
* the server itself. Note that after challing this method all
* <code>XxxFactory.getDefault()</code> methods return a different factory
* than before.</p>
* <p>This method calls both <code>System.gc()</code> and <code>System.runFinalization()</code>,
* which usually means that if you have no references to factories linked
* to the database identified by the former value of <code>getDefaultDatabaseUrl</code>,
* that the link to that database will be closed automatically (if
* <code>setAutoClose()</code> is <code>true</code>).</p>
* @param url identifies the default database that factories created without
* specifying a database URL (i.e. their default constructor) will create
* their objects in
*/
public static void setDefaultDatabaseUrl(String url) {
synchronized(databaseInfoMap) {
if (getDefaultDatabase() instanceof Database) {
throw new UnexpectedException("cannot call setDefaultDatabaseUrl() on server/local side");
}
defaultDatabaseUrl = url;
for (Iterator i = factoryClassInfos.iterator(); i.hasNext(); ) {
FactoryClassInfo info = (FactoryClassInfo) i.next();
info.defaultDatabaseUrlChanged();
}
}
System.gc();
System.runFinalization();
}
public static String getDefaultDatabaseUrl() {
synchronized(databaseInfoMap) {
return defaultDatabaseUrl;
}
}
protected static OzoneInterface getDefaultDatabase() {
synchronized(databaseInfoMap) {
DatabaseInfo databaseInfo = (DatabaseInfo) databaseInfoMap.get(getDefaultDatabaseUrl());
return (databaseInfo == null) ? null : databaseInfo.getDatabase();
}
}
protected static void addFactoryClassInfo(FactoryClassInfo info) {
synchronized(databaseInfoMap) {
factoryClassInfos.add(info);
}
}
private String databaseUrl;
private DatabaseInfo databaseInfo;
/** Creates a factory object that creates its objects in the same database
* as the default factory.
*/
protected AbstractFactory() throws Exception {
synchronized(databaseInfoMap) {
if ((getDefaultDatabase() == null) && (getDefaultDatabaseUrl() == null)) {
throw new OzoneRemoteException("default database url not set");
}
init(getDefaultDatabaseUrl());
}
}
/** <p>Creates a factory that creates its objects in the database specified by
* <code>url</code>. Note that this constructor can only be used if the database in
* question is not opened by this client. Use <code>AbstractFactory()</code> or
* <code>AbstractFactory(Factory)</code> to create a factory for an already opened
* database. This might seem strange, or even annoying at first, but it is very
* logical from an object oriented point of view: you probably want a factory that
* creates its objects in the default database, or in the same realm as another
* factory you already have created...</p>
* @param databaseUrl url defining the remote database (something like
* <code>ozone:remote://localhost:3333</code>)
*/
protected AbstractFactory(String databaseUrl) throws Exception {
init(databaseUrl);
}
private void init(String databaseUrl) throws Exception {
synchronized (databaseInfoMap) {
this.databaseUrl = databaseUrl;
databaseInfo = (DatabaseInfo) databaseInfoMap.get(databaseUrl);
if (databaseInfo == null) {
ExternalDatabase db = ExternalDatabase.openDatabase(databaseUrl);
databaseInfo = new DatabaseInfo(db);
databaseInfoMap.put(databaseUrl, databaseInfo);
}
databaseInfo.pin();
}
}
/** <p>Creates a factory that creates its objects in the same database as a specific
* other fatory does.</p>
* @param factory the factory that creates its objects in the same database as the new factory
* should
*/
protected AbstractFactory(AbstractFactory factory) {
synchronized (databaseInfoMap) {
databaseInfo = (DatabaseInfo) databaseInfoMap.get(factory.getDatabaseUrl());
databaseInfo.pin();
}
}
protected void finalize() throws Throwable {
synchronized (databaseInfoMap) {
databaseInfo.unpin();
if (!databaseInfo.isPinned()) {
// this action is unnecessary when the database has been closed;
// the dbInfoMap has then already been removed.
databaseInfoMap.remove(getDatabaseUrl());
}
}
super.finalize();
}
/** <p>Closes the connection to the database this instance creates its
* objects in. Closing happens at once, unlike the autoClose property.</p>
* Note: when you call this method, you close the connection to the database
* FOR ALL OTHER FACTORIES that link to the same database as this instance
* as well!</p>
* @see #getAutoClose
* @see #setAutoClose
*/
public final void closeDatabase() throws Exception {
synchronized (databaseInfoMap) {
OzoneInterface db = getDatabase();
if (db instanceof ExternalDatabase) {
((ExternalDatabase) db).close();
databaseInfoMap.remove(getDatabaseUrl());
} else {
throw new UnexpectedException("cannot close database on server side");
}
}
}
/** <p>Override this method to find out when the default database has been
* closed. Every class derived from <code>AbstractFactory</code> has its own
* default instance.</p>
*/
abstract protected void defaultClosed();
/** <p>Returns the database this instance creates its objects in.</p>
* @return database this factory is 'linked' to
*/
public final OzoneInterface getDatabase() {
return databaseInfo.getDatabase();
}
protected final String getDatabaseUrl() {
return databaseUrl;
}
/** <p>Gets current autoClose setting for this and all other factories
* linked to the same database. When <code>true</code> the connection to the
* database this instance creates its objects in is closed when all
* factories connected to that database are garbage collected.
*/
public final boolean getAutoClose() {
return databaseInfo.getAutoClose();
}
/** <p>Sets current autoClose setting for this and all other factories
* linked to the same database. When autoClose is <code>true</code> the
* connection to the database this instance creates its objects in is closed
* when all factories connected to that database are garbage collected.</p>
* <p>Note: when you call this method, change the auto-close settings for all
* other factories that link to the same database as this instance as well!
* </p>
*/
public final void setAutoClose(boolean autoClose) {
databaseInfo.setAutoClose(autoClose);
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -