/*
* #!
* 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.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.infoset.core.Locators;
import net.ontopia.infoset.impl.basic.URILocator;
import net.ontopia.persistence.proxy.RDBMSStorage;
import net.ontopia.topicmaps.entry.TopicMapReferenceIF;
import net.ontopia.topicmaps.entry.TopicMapSourceIF;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.StreamUtils;
/**
* PUBLIC: A topic map source that holds the list of <i>all</i> topic
* map references accessible by a rdbms session.
*/
public class RDBMSTopicMapSource implements TopicMapSourceIF {
protected boolean supportsCreate;
protected boolean supportsDelete;
protected LocatorIF base_address;
protected boolean hidden;
protected String id;
protected String title;
protected Map<String, String> properties;
protected String propfile;
protected String queryfile;
protected String topicListeners;
protected Map<String, TopicMapReferenceIF> refmap;
protected RDBMSStorage storage;
/**
* PUBLIC: Creates an rdbms topic map source. Use the setter methods
* to provide the instance with the properties it needs.<p>
*
* If the property file has not been given the
* 'net.ontopia.topicmaps.impl.rdbms.PropertyFile' system properties
* will be used.<p>
*/
public RDBMSTopicMapSource() {
}
/**
* INTERNAL: Creates an rdbms topic map source with the database
* property file set.
*/
public RDBMSTopicMapSource(String propfile) {
this.propfile = propfile;
}
/**
* INTERNAL: Creates an rdbms topic map source with the specified
* database properties.
* @since 1.2.4
*/
public RDBMSTopicMapSource(Map<String, String> properties) {
this.properties = properties;
}
private boolean isInitialized() {
return (refmap != null);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
/**
* PUBLIC: Gets the base address of the topic maps retrieved from
* the source. The notation is assumed to be 'URI'.
*/
public String getBaseAddress() {
return (base_address == null ? null : base_address.getAddress());
}
/**
* PUBLIC: Sets the base address of the topic maps retrieved from
* the source. The notation is assumed to be 'URI'.
*/
public void setBaseAddress(String base_address) {
try {
this.base_address = new URILocator(base_address);
} catch (MalformedURLException e) {
throw new OntopiaRuntimeException(e);
}
}
/**
* PUBLIC: Gets the database property file containing configuration
* parameters for accessing the rdbms database.
*/
public String getPropertyFile() {
return propfile;
}
/**
* PUBLIC: Sets the database property file containing configuration
* parameters for accessing the rdbms database. The propfile given
* with first be attempted loaded from the file system. If it does
* not exist on the file system it will be loaded from the
* classpath. If the access must be explicit then the property file
* name can be prefixed by 'file:' or 'classpath:'.
*/
public void setPropertyFile(String propfile) {
this.propfile = propfile;
}
public void setQueryfile(String queryfile) {
this.queryfile = queryfile;
}
public String getQueryfile() {
return queryfile;
}
public synchronized Collection<TopicMapReferenceIF> getReferences() {
if (!isInitialized()) refresh();
return refmap.values();
}
protected RDBMSStorage createStorage() throws IOException {
if (storage == null) {
if (propfile != null)
this.storage = new RDBMSStorage(propfile);
else if (properties != null)
this.storage = new RDBMSStorage(properties);
else
throw new OntopiaRuntimeException("propertyFile property must be specified on source with id '" + getId() + "'.");
if (queryfile != null) {
InputStream stream = StreamUtils.getInputStream(queryfile);
if (stream == null) throw new IOException("Could not find query file " + queryfile);
storage.getQueryDeclarations().loadQueries(stream);
}
}
return storage;
}
public synchronized void refresh() {
Connection conn = null;
try {
createStorage();
// Create connection for transaction
conn = storage.getConnectionFactory(true).requestConnection();
Statement stm = conn.createStatement();
ResultSet rs = stm.executeQuery("select id, title, base_address from TM_TOPIC_MAP");
// Loop over result rows
Map<String, TopicMapReferenceIF> newmap = new HashMap<String, TopicMapReferenceIF>();
while (rs.next()) {
// Add row object to result collection
long topicmap_id = rs.getLong(1);
String _title = rs.getString(2);
String loc = rs.getString(3);
String referenceId = getReferenceId(loc, _title, topicmap_id);
// Do not create new reference if active reference exist.
if (refmap != null) {
TopicMapReferenceIF ref = refmap.get(referenceId);
if (ref != null && ref.isOpen()) {
// Use existing reference
newmap.put(referenceId, ref);
continue;
}
}
if (_title == null)
_title = referenceId;
LocatorIF _base_address = this.base_address;
if (_base_address == null) {
if (loc != null)
_base_address = Locators.getURILocator(loc);
}
// Create a new reference
RDBMSTopicMapReference ref = createTopicMapReference(referenceId, _title, storage, topicmap_id, _base_address);
ref.setSource(this);
// register topic listeners
if (topicListeners != null)
ref.registerTopicListeners(topicListeners);
newmap.put(referenceId, ref);
}
rs.close();
stm.close();
if (refmap != null) {
// Close open reference no longer in refmap
Collection<TopicMapReferenceIF> danglingReferences = new HashSet(refmap.values());
danglingReferences.removeAll(newmap.values());
for (TopicMapReferenceIF danglingReference : danglingReferences) {
if (danglingReference.isOpen()) {
danglingReference.close();
}
}
}
// Update reference map
refmap = newmap;
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
} finally {
if (conn != null) try { conn.close(); } catch (Exception e) { };
}
}
@Override
public void close() {
if (storage != null) {
storage.close();
}
}
protected String getReferenceId(String baseAdress, String title, long topicmap_id) {
if (id == null)
return "RDBMS-" + topicmap_id;
else
return id + "-" + topicmap_id;
}
protected RDBMSTopicMapReference createTopicMapReference(String referenceId, String title, RDBMSStorage storage, long topicmapId, LocatorIF baseAddress) {
return new RDBMSTopicMapReference(referenceId, title, storage, topicmapId, baseAddress);
}
public boolean supportsCreate() {
return getSupportsCreate();
}
public boolean getSupportsCreate() {
return supportsCreate;
}
public void setSupportsCreate(boolean supportsCreate) {
this.supportsCreate = supportsCreate;
}
public boolean supportsDelete() {
return getSupportsDelete();
}
public boolean getSupportsDelete() {
return supportsDelete;
}
public void setSupportsDelete(boolean supportsDelete) {
this.supportsDelete = supportsDelete;
}
public synchronized TopicMapReferenceIF createTopicMap(String name, String baseAddress) {
if (!supportsCreate())
throw new UnsupportedOperationException("This source does not support creating new topic maps.");
// create topic map instance
RDBMSTopicMapStore store = null;
long topicmap_id = -1;
try {
createStorage();
store = new RDBMSTopicMapStore(storage);
TopicMap tm = (TopicMap)store.getTopicMap();
tm.setTitle(name);
topicmap_id = tm.getLongId();
LocatorIF _base_address = (baseAddress == null ? null : new URILocator(baseAddress));
store.setBaseAddress(_base_address);
store.commit();
// create topic map reference
String id = getReferenceId(baseAddress, name, topicmap_id);
String title = (name == null ? id : name);
RDBMSTopicMapReference ref = createTopicMapReference(id, title, storage, topicmap_id, _base_address);
ref.setSource(this);
// register topic listeners
if (topicListeners != null)
ref.registerTopicListeners(topicListeners);
if (refmap == null) refmap = new HashMap<String, TopicMapReferenceIF>();
refmap.put(id, ref);
return ref;
} catch (Exception e) {
throw new OntopiaRuntimeException(e);
} finally {
if (store != null) store.close();
}
}
public boolean getHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public String getTopicListeners() {
return topicListeners;
}
public void setTopicListeners(String topicListeners) {
this.topicListeners = topicListeners;
}
}