/*
* #!
* Ontopia Engine
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 net.ontopia.topicmaps.impl.rdbms;
import java.util.Map;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.persistence.proxy.StorageIF;
import net.ontopia.topicmaps.core.StoreDeletedException;
import net.ontopia.topicmaps.core.TopicMapStoreFactoryIF;
import net.ontopia.topicmaps.core.TopicMapStoreIF;
import net.ontopia.topicmaps.entry.AbstractTopicMapReference;
import net.ontopia.topicmaps.impl.utils.AbstractTopicMapStore;
import net.ontopia.topicmaps.impl.utils.StorePoolableObjectFactory;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.PropertyUtils;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* INTERNAL: RDBMS database topic map reference.
*/
public class RDBMSTopicMapReference extends AbstractTopicMapReference {
public static final String EXHAUSED_BLOCK = "block";
public static final String EXHAUSED_GROW = "grow";
public static final String EXHAUSED_FAIL = "fail";
// Define a logging category.
static Logger log = LoggerFactory.getLogger(RDBMSTopicMapReference.class.getName());
protected StorageIF storage;
protected long topicmap_id;
protected LocatorIF base_address;
// store pool
protected StorePoolableObjectFactory ofactory;
protected GenericObjectPool pool;
protected RDBMSTopicMapStore rostore;
public RDBMSTopicMapReference(String _id, String _title, StorageIF _storage,
long _topicmap_id, LocatorIF _base_address) {
super(_id, _title);
this.storage = _storage;
this.topicmap_id = _topicmap_id;
this.base_address = _base_address;
}
protected RDBMSTopicMapStore _createStore(boolean readonly) {
RDBMSTopicMapStore store = new RDBMSTopicMapStore(storage, topicmap_id);
store.setReadOnly(readonly);
store.setReference(RDBMSTopicMapReference.this);
if (base_address != null)
store.setBaseAddressOverride(base_address);
return store;
}
protected void init() {
// store factory
TopicMapStoreFactoryIF sfactory = new TopicMapStoreFactoryIF() {
public TopicMapStoreIF createStore() {
return _createStore(false);
}
};
// initialize pool
this.ofactory = new StorePoolableObjectFactory(sfactory);
this.pool = new GenericObjectPool(ofactory);
this.pool.setTestOnBorrow(true);
Map properties = storage.getProperties();
if (properties != null) {
// Set minimum pool size (default: 0)
String _minsize = PropertyUtils.getProperty(properties,
"net.ontopia.topicmaps.impl.rdbms.StorePool.MinimumSize", false);
int minsize = (_minsize == null ? 0 : Integer.parseInt(_minsize));
log.debug("Setting StorePool.MinimumSize '" + minsize + "'");
pool.setMinIdle(minsize); // 0 = no limit
// Set maximum pool size (default: Integer.MAX_VALUE)
String _maxsize = PropertyUtils.getProperty(properties,
"net.ontopia.topicmaps.impl.rdbms.StorePool.MaximumSize", false);
int maxsize = (_maxsize == null ? 8 : Integer.parseInt(_maxsize));
log.debug("Setting StorePool.MaximumSize '" + maxsize + "'");
pool.setMaxActive(maxsize); // 0 = no limit
// Set soft maximum - emergency objects (default: false)
boolean softmax = PropertyUtils.isTrue(properties,
"net.ontopia.topicmaps.impl.rdbms.StorePool.SoftMaximum", false);
log.debug("Setting StorePool.SoftMaximum '" + softmax + "'");
if (softmax)
pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
else
pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
// EXPERIMENTAL!
String _etime = PropertyUtils.getProperty(properties, "net.ontopia.topicmaps.impl.rdbms.StorePool.IdleTimeout", false);
int etime = (_etime == null ? -1 : Integer.parseInt(_etime));
pool.setTimeBetweenEvictionRunsMillis(etime);
pool.setSoftMinEvictableIdleTimeMillis(etime);
}
// allow the user to fully overwrite exhausted options
String _whenExhaustedAction = PropertyUtils.getProperty(properties, "net.ontopia.topicmaps.impl.rdbms.StorePool.WhenExhaustedAction", false);
if (EXHAUSED_BLOCK.equals(_whenExhaustedAction))
pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
if (EXHAUSED_GROW.equals(_whenExhaustedAction))
pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_GROW);
if (EXHAUSED_FAIL.equals(_whenExhaustedAction))
pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_FAIL);
if (pool.getWhenExhaustedAction() == GenericObjectPool.WHEN_EXHAUSTED_BLOCK)
log.debug("Pool is set to block on exhaused");
if (pool.getWhenExhaustedAction() == GenericObjectPool.WHEN_EXHAUSTED_GROW)
log.debug("Pool is set to grow on exhaused");
if (pool.getWhenExhaustedAction() == GenericObjectPool.WHEN_EXHAUSTED_FAIL)
log.debug("Pool is set to fail on exhaused");
}
public synchronized void open() {
// ignore if already open
if (isOpen())
return;
if (isDeleted())
throw new StoreDeletedException(
"Topic map has been deleted through this reference.");
// initialize reference
init();
this.isopen = true;
}
public synchronized TopicMapStoreIF createStore(boolean readonly) {
if (!isOpen())
open();
log.debug("RTR: borrow " + getId() + " i: " + pool.getNumIdle() + " a: "
+ pool.getNumActive());
try {
if (readonly) {
if (rostore == null)
rostore = _createStore(true);
else {
boolean valid = rostore.validate();
if (!valid) {
try { rostore.close(); } catch (Exception e) { }
rostore = _createStore(true);
}
}
return rostore;
} else {
// borrow store from pool and set managed members
AbstractTopicMapStore store = (AbstractTopicMapStore) pool.borrowObject();
// register listeners
store.setTopicListeners(getTopicListeners());
return store;
}
} catch (Exception e) {
// NOTE: NoSuchElementException will be thrown if pool times out or is
// full
throw new OntopiaRuntimeException("Could not get topic map store '"
+ getId() + "' from pool.", e);
}
}
@Override
public void setTitle(String title) {
super.setTitle(title);
TopicMapStoreIF store = null;
try {
store = createStore(false);
TopicMap topicmap = (TopicMap) store.getTopicMap();
topicmap.setTitle(title);
store.commit();
} finally {
if (store != null) {
store.close();
}
}
}
/**
* INTERNAL: Returns the base address locator to be used when loading
* the topic map.
*/
public LocatorIF getBaseAddress() {
return base_address;
}
/**
* INTERNAL: Sets the base address locator to be used when loading
* the topic map and persists it in the database.
*/
public void setBaseAddress(LocatorIF base_address) {
this.base_address = base_address;
TopicMapStoreIF store = null;
try {
store = createStore(false);
TopicMap topicmap = (TopicMap) store.getTopicMap();
topicmap.setBaseAddress(base_address);
store.commit();
} finally {
if (store != null) {
store.close();
}
}
}
public synchronized void close() {
// ISSUE: should block until all stores are returned to pool?
this.isopen = false;
if (pool != null) {
try {
// WARNING: it is important to note that pool does not close
// active stores.
pool.close();
pool = null;
if (rostore != null) {
rostore.close(false);
rostore = null;
}
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
}
}
}
public synchronized void clear() {
if (isDeleted())
throw new StoreDeletedException(
"Topic map has been deleted through this reference.");
// close reference
close();
// create new topic map store which is to be used when clearing.
RDBMSTopicMapStore store = new RDBMSTopicMapStore(storage, topicmap_id);
// clear topic map. The store is closed automatically.
try {
store.clear();
store.commit();
} finally {
if (store.isOpen())
store.close();
}
}
public synchronized void delete() {
if (source == null)
throw new UnsupportedOperationException("This reference cannot be deleted as it does not belong to a source.");
if (!source.supportsDelete())
throw new UnsupportedOperationException("This reference cannot be deleted as the source does not allow deleting.");
// ignore if store already deleted
if (isDeleted())
return;
// close reference
close();
// create new topic map store which is to be used when deleting.
RDBMSTopicMapStore store = new RDBMSTopicMapStore(storage, topicmap_id);
// delete topic map from data repository by delegating to
// TopicMapStoreIF.close(). The store is closed automatically.
this.deleted = store.delete(this);
}
public String toString() {
return super.toString() + " [" + topicmap_id + "]";
}
// --- Extension properties
public long getTopicMapId() {
return topicmap_id;
}
// --- store pooling
public synchronized void storeClosed(TopicMapStoreIF store) {
if (!store.isReadOnly()) {
// dereference listeners
((AbstractTopicMapStore)store).setTopicListeners(null);
}
if (pool != null) {
log.debug("RTR: return " + getId() + " i: " + pool.getNumIdle() + " a: " + pool.getNumActive());
try {
// return rw store to pool
if (!store.isReadOnly()) {
pool.returnObject(store);
}
} catch (Exception e) {
throw new OntopiaRuntimeException("Could not return topic map store '" + getId() + "' to pool.", e);
}
} else {
// pool has been closed before store, so we should close store ourselves
synchronized (store) {
if (store.isOpen())
((RDBMSTopicMapStore)store).close(false);
}
}
}
public void writeReport(java.io.Writer out, boolean dumpCaches) throws java.io.IOException {
out.write("<table>\n");
out.write(" <tr><td>");
out.write("Topic Map:");
out.write("</td><td>");
out.write(getId());
out.write(" </td></tr>\n");
out.write(" <tr><td>");
out.write("Active:");
out.write("</td><td>");
out.write(Integer.toString(pool == null ? 0 : pool.getNumActive()));
out.write(" </td></tr>\n");
out.write(" <tr><td>");
out.write("Idle:");
out.write("</td><td>");
out.write(Integer.toString(pool == null ? 0 : pool.getNumIdle()));
out.write(" </td><tr>\n");
out.write("</table>\n");
Object[] stores = ofactory.stores.toArray();
for (int i = 0; i < stores.length; i++) {
RDBMSTopicMapStore store = (RDBMSTopicMapStore)stores[i];
out.write("<h3>Identity Map - ReadWriteStore #");
out.write(Integer.toString(i+1));
out.write("</h3>\n");
store.writeIdentityMap(out, dumpCaches);
}
if (rostore != null) {
out.write("<h3>Identity Map - ReadOnlyStore</h3>\n");
rostore.writeIdentityMap(out, dumpCaches);
}
}
}