/*
* #!
* Ontopia TMRAP
* #-
* 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.utils.tmrap;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import net.ontopia.infoset.core.LocatorIF;
import net.ontopia.infoset.impl.basic.URILocator;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.core.TopicMapStoreFactoryIF;
import net.ontopia.topicmaps.core.index.ClassInstanceIndexIF;
import net.ontopia.topicmaps.impl.basic.InMemoryTopicMapStore;
import net.ontopia.topicmaps.utils.CharacteristicUtils;
import net.ontopia.topicmaps.utils.MergeUtils;
import net.ontopia.topicmaps.utils.NullResolvingExternalReferenceHandler;
import net.ontopia.topicmaps.utils.TopicMapSynchronizer;
import net.ontopia.topicmaps.xml.XTMTopicMapReader;
import net.ontopia.utils.CollectionUtils;
import net.ontopia.utils.CompactHashSet;
import net.ontopia.utils.OntopiaRuntimeException;
import net.ontopia.utils.StringUtils;
import org.xml.sax.InputSource;
/**
* EXPERIMENTAL: An implementation that looks up topics on remote
* servers using the TM RAP protocol.
*/
public class RemoteTopicIndex implements TopicIndexIF {
protected String editBaseuri;
protected String viewBaseuri;
protected TopicMapStoreFactoryIF storefactory;
protected String tmid;
public RemoteTopicIndex(String editBaseuri, String viewBaseuri) {
this(editBaseuri, viewBaseuri,
new net.ontopia.topicmaps.impl.basic.InMemoryStoreFactory());
}
public RemoteTopicIndex(String editBaseuri, String viewBaseuri,
TopicMapStoreFactoryIF factory) {
this(editBaseuri, viewBaseuri, factory, null);
}
public RemoteTopicIndex(String editBaseuri, String viewBaseuri,
TopicMapStoreFactoryIF factory, String tmid) {
this.editBaseuri = editBaseuri;
this.viewBaseuri = viewBaseuri;
this.storefactory = factory;
this.tmid = tmid;
}
public Collection<TopicIF> getTopics(Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
if (indicators.isEmpty() && sources.isEmpty() && subjects.isEmpty())
return Collections.emptySet();
// lookup or create target topic
TopicMapIF targetTopicMap = storefactory.createStore().getTopicMap();
TopicIF targetTopic = createTopic(targetTopicMap, indicators, sources, subjects);
// make sure topic knows it's being loaded
setLoaded(targetTopic);
TopicMapIF sourceTopicMap = new InMemoryTopicMapStore().getTopicMap();
sourceTopicMap.getStore().setBaseAddress(targetTopicMap.getStore().getBaseAddress());
// send get-topic request
try {
String params = encodeIdentityParameters(indicators, sources, subjects);
if (tmid != null)
params = "topicmap=" + tmid + "&" + params;
loadXTM("get-topic", params, false, sourceTopicMap);
} catch (IOException e) {
throw new OntopiaRuntimeException(e);
}
TopicIF sourceTopic = findTopic(sourceTopicMap, indicators, sources, subjects);
TopicMapSynchronizer.update(targetTopicMap, sourceTopic);
targetTopic = findTopic(targetTopicMap, indicators, sources, subjects);
return (targetTopic == null ? null : Collections.singleton(targetTopic));
}
private TopicIF createTopic(TopicMapIF tm,
Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
// merge all topics that have any of the identities
TopicIF topic = null;
Collection<TopicIF> existing = findTopics(tm, indicators, sources, subjects);
if (!existing.isEmpty()) {
Iterator<TopicIF> iter = existing.iterator();
topic = iter.next();
if (iter.hasNext()) {
MergeUtils.mergeInto(topic, iter.next());
}
}
// if no topics was found then create a new one
if (topic == null)
topic = tm.getBuilder().makeTopic();
// make sure topic has all the identities
Iterator<LocatorIF> it = indicators.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
topic.addSubjectIdentifier(loc);
}
it = sources.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
topic.addItemIdentifier(loc);
}
it = subjects.iterator();
while (it.hasNext()) {
LocatorIF subject = it.next();
topic.addSubjectLocator(subject);
}
return topic;
}
private TopicIF findTopic(TopicMapIF tm,
Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
Iterator<LocatorIF> it = indicators.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
TopicIF topic = tm.getTopicBySubjectIdentifier(loc);
if (topic != null) return topic;
}
it = sources.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
TopicIF topic = (TopicIF) tm.getObjectByItemIdentifier(loc);
if (topic != null)
return topic;
else {
// Resolve object by object id
String address = loc.getAddress();
if (RemoteTopicIndex.isVirtualReference(address)) {
if (tmid.equals(RemoteTopicIndex
.sourceTopicMapFromVirtualReference(address))) {
topic = (TopicIF)tm.getObjectById(RemoteTopicIndex
.resolveVirtualReference(address, tmid));
if (topic != null) return topic;
}
}
}
}
it = subjects.iterator();
while (it.hasNext()) {
LocatorIF subject = it.next();
TopicIF topic = tm.getTopicBySubjectLocator(subject);
if (topic != null) return topic;
}
return null;
}
private Collection<TopicIF> findTopics(TopicMapIF tm,
Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
// find all topics that have any of the identities
Collection<TopicIF> result = new HashSet<TopicIF>();
Iterator<LocatorIF> it = indicators.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
TopicIF topic = tm.getTopicBySubjectIdentifier(loc);
if (topic != null) result.add(topic);
}
it = sources.iterator();
while (it.hasNext()) {
LocatorIF loc = it.next();
TopicIF topic = (TopicIF) tm.getObjectByItemIdentifier(loc);
if (topic != null)
result.add(topic);
else {
// Resolve object by object id
String address = loc.getAddress();
if (RemoteTopicIndex.isVirtualReference(address)) {
if (tmid.equals(RemoteTopicIndex
.sourceTopicMapFromVirtualReference(address))) {
topic = (TopicIF)tm.getObjectById(RemoteTopicIndex
.resolveVirtualReference(address, tmid));
if (topic != null) result.add(topic);
}
}
}
}
it = subjects.iterator();
while (it.hasNext()) {
LocatorIF subject = it.next();
TopicIF topic = tm.getTopicBySubjectLocator(subject);
if (topic != null) result.add(topic);
}
return result;
}
private boolean isLoaded(TopicIF topic) {
if (topic instanceof net.ontopia.topicmaps.impl.remote.RemoteTopic)
return ((net.ontopia.topicmaps.impl.remote.RemoteTopic) topic).isLoaded();
return true;
}
private void setLoaded(TopicIF topic) {
if (topic instanceof net.ontopia.topicmaps.impl.remote.RemoteTopic) {
boolean prev = ((net.ontopia.topicmaps.impl.remote.RemoteTopic) topic).isLoaded();
((net.ontopia.topicmaps.impl.remote.RemoteTopic) topic).setLoaded(true);
}
}
private List<String> getIdPredicates(TopicIF topic, String varname) {
List<String> idpredicates = new ArrayList<String>();
Iterator<LocatorIF> ids = topic.getItemIdentifiers().iterator();
while (ids.hasNext()) {
LocatorIF loc = ids.next();
String address = loc.getAddress();
if (RemoteTopicIndex.isVirtualReference(address)) {
String topicId = RemoteTopicIndex.resolveVirtualReference(address, tmid);
if (topicId != null)
idpredicates.add("$" + varname + " = @" + topicId);
else
System.err.println("Cannot resolve virtual reference: " + topicId + " (" + address + ")");
} else {
idpredicates.add("item-identifier($" + varname + ", \"" + loc.getAddress() + "\")");
}
}
ids = topic.getSubjectIdentifiers().iterator();
while (ids.hasNext()) {
LocatorIF loc = ids.next();
idpredicates.add("subject-identifier($" + varname + ", \"" + loc.getAddress() + "\")");
}
ids = topic.getSubjectLocators().iterator();
while (ids.hasNext()) {
LocatorIF loc = ids.next();
idpredicates.add("subject-locator($" + varname + ", \"" + loc.getAddress() + "\")");
}
return idpredicates;
}
public Collection<TopicIF> loadRelatedTopics(Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects,
boolean two_steps) {
long start = System.currentTimeMillis();
if (indicators.isEmpty() && sources.isEmpty() && subjects.isEmpty())
return Collections.emptySet();
try {
// lookup or create target topic
TopicMapIF targetTopicMap = storefactory.createStore().getTopicMap();
TopicIF targetTopic = createTopic(targetTopicMap, indicators, sources, subjects);
int count = 0;
// build identity predicates
List<String> idpredicates_T = getIdPredicates(targetTopic, "T");
if (idpredicates_T.isEmpty())
return Collections.emptySet();
StringBuilder query = new StringBuilder();
query.append("related-to($T1, $T2) :- " +
" role-player($R1, $T1), " +
" association-role($A, $R1), " +
" association-role($A, $R2), " +
" $R1 /= $R2, " +
" role-player($R2, $T2), " +
" $T1 /= $T2 . ");
query.append("select $O from ");
if (idpredicates_T.size() > 1)
query.append(" {");
query.append(StringUtils.join(idpredicates_T, " | "));
if (idpredicates_T.size() > 1)
query.append(" }");
query.append(", { ");
query.append(" $O = $T | ");
query.append(" related-to($T, $O) ");
if (two_steps)
query.append("| related-to($T, $TMP), related-to($TMP, $O) ");
query.append("}?");
TopicMapIF sourceTopicMap = new InMemoryTopicMapStore().getTopicMap();
sourceTopicMap.getStore().setBaseAddress(targetTopicMap.getStore().getBaseAddress());
String params = "syntax=application/x-xtm&tolog=" + URLEncoder.encode(query.toString());
if (tmid != null)
params = "topicmap=" + tmid + "&" + params;
params += "&compress=true";
loadXTM("get-tolog", params, true, sourceTopicMap);
// make sure topic knows it's being loaded
TopicIF sourceTopic = findTopic(sourceTopicMap, indicators, sources, subjects);
if (!isLoaded(targetTopic)) {
setLoaded(targetTopic);
TopicMapSynchronizer.update(targetTopicMap, sourceTopic);
count++;
}
// get loaded topics (possibly 2 steps out)
Collection<TopicIF> loaded = new CompactHashSet<TopicIF>();
Iterator<TopicIF> riter = CharacteristicUtils.getAssociatedTopics(sourceTopic).iterator();
while (riter.hasNext()) {
TopicIF topic = riter.next();
loaded.add(topic);
if (two_steps)
loaded.addAll(CharacteristicUtils.getAssociatedTopics(topic));
}
// move topics over, and mark as loaded
transferLoadedTopics(loaded, targetTopicMap);
return Collections.singleton(targetTopic);
} catch (IOException e) {
throw new OntopiaRuntimeException(e);
}
}
public Collection<TopicPage> getTopicPages(Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
Collection<TopicPage> pages = new ArrayList<TopicPage>();
try {
String params = encodeIdentityParameters(indicators, sources, subjects);
if (tmid != null)
params = "topicmap=" + tmid + "&" + params;
InputSource src = getInputSource("get-topic-page", params, false);
String baseuri = viewBaseuri == null ? editBaseuri : viewBaseuri;
LocatorIF base = new URILocator(baseuri + "get-topic-page");
XTMTopicMapReader reader = new XTMTopicMapReader(src, base);
reader.setValidation(false); // means we don't need Jing
TopicMapIF tm = reader.read();
ClassInstanceIndexIF ix = (ClassInstanceIndexIF)
tm.getIndex("net.ontopia.topicmaps.core.index.ClassInstanceIndexIF");
LocatorIF vpsi = new URILocator("http://psi.ontopia.net/tmrap/view-page");
TopicIF vptype = tm.getTopicBySubjectIdentifier(vpsi);
if (vptype == null)
return pages;
Iterator<TopicIF> it = ix.getTopics(vptype).iterator();
while (it.hasNext()) {
TopicIF vp = it.next();
LocatorIF subject = CollectionUtils.getFirst(vp.getSubjectLocators());
pages.add(new TopicPage(null, subject.getAddress(),
null, null, null));
}
} catch (IOException e) {
throw new OntopiaRuntimeException(e);
}
return pages;
}
public TopicPages getTopicPages2(Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
throw new UnsupportedOperationException("This method is not supported");
}
public void loadAssociationTypes(TopicMapIF topicmap) throws IOException {
loadQuery(topicmap,
"select $T from " +
"association($A), " +
"type($A, $T)?");
}
public void loadTopicTypes(TopicMapIF topicmap) throws IOException {
loadQuery(topicmap,
"select $T from " +
"direct-instance-of($I, $T)?");
}
public void loadQuery(TopicMapIF topicmap, String query) throws IOException {
// assemble get-tolog request
query = URLEncoder.encode(query);
TopicMapIF tmptm = new InMemoryTopicMapStore().getTopicMap();
tmptm.getStore().setBaseAddress(topicmap.getStore().getBaseAddress());
String params = "syntax=application/x-xtm&tolog=" + query;
if (tmid != null)
params = "topicmap=" + tmid + "&" + params;
params += "&compress=true";
// send request
loadXTM("get-tolog", params, true, tmptm);
// collect newly loaded topics
Collection<TopicIF> loaded = new ArrayList<TopicIF>();
Iterator<TopicIF> it = tmptm.getTopics().iterator();
while (it.hasNext()) {
TopicIF topic = it.next();
if (!(topic.getTypes().isEmpty() &&
topic.getTopicNames().isEmpty() &&
topic.getOccurrences().isEmpty() &&
topic.getRoles().isEmpty()))
loaded.add(topic);
}
// mark as loaded and transfer
transferLoadedTopics(loaded, topicmap);
}
public void close() {
// nothing we need to let go of
}
// Special configuration methods
public void setStoreFactory(TopicMapStoreFactoryIF storefactory) {
this.storefactory = storefactory;
}
// Internal methods
protected InputSource getInputSource(String method, String params,
boolean compress)
throws IOException {
String baseuri = viewBaseuri == null ? editBaseuri : viewBaseuri;
String url = baseuri + method + "?" + params;
URLConnection conn = this.getConnection(url, 0);
InputSource src = new InputSource(url);
if (!compress) {
src.setByteStream(conn.getInputStream());
String ctype = conn.getContentType();
if (ctype != null && ctype.startsWith("text/xml")) {
int pos = ctype.indexOf("charset=");
if (pos != -1) src.setEncoding(ctype.substring(pos + 8));
}
} else
src.setByteStream(new java.util.zip.GZIPInputStream(conn.getInputStream()));
return src;
}
private URLConnection getConnection(String url, int count) {
URLConnection conn = null;
try {
conn = new URL(url).openConnection();
} catch (IOException e) {
if (count < 5) {
System.out.println();
System.out.print("getConnection failed - ");
System.out.println(count);
System.out.println("Trying again ...");
conn = this.getConnection(url, count++);
} else {
System.out.println("Giving up");
throw new OntopiaRuntimeException(e);
}
}
return conn;
}
private String encodeIdentityParameters(Collection<LocatorIF> indicators,
Collection<LocatorIF> sources,
Collection<LocatorIF> subjects) {
boolean notfirst = false;
StringBuilder buf = new StringBuilder();
Iterator<LocatorIF> it = indicators.iterator();
while (it.hasNext()) {
LocatorIF locator = it.next();
if (notfirst) buf.append("&");
buf.append("identifier=" + URLEncoder.encode(locator.getExternalForm()));
notfirst = true;
}
it = sources.iterator();
while (it.hasNext()) {
LocatorIF locator = it.next();
if (notfirst) buf.append("&");
buf.append("item=" + URLEncoder.encode(locator.getExternalForm()));
notfirst = true;
}
it = subjects.iterator();
while (it.hasNext()) {
LocatorIF locator = it.next();
if (notfirst) buf.append("&");
buf.append("subject=" + URLEncoder.encode(locator.getExternalForm()));
}
return buf.toString();
}
private void loadXTM(String request, String params, boolean compress,
TopicMapIF topicmap)
throws IOException {
InputSource src = getInputSource(request, params, compress);
String baseuri = viewBaseuri == null ? editBaseuri : viewBaseuri;
LocatorIF base = new URILocator(baseuri + request);
XTMTopicMapReader reader = new XTMTopicMapReader(src, base);
reader.setExternalReferenceHandler(new NullResolvingExternalReferenceHandler());
reader.setValidation(false); // means we don't need Jing
reader.importInto(topicmap);
}
private void transferLoadedTopics(Collection<TopicIF> loaded,
TopicMapIF targetTopicMap) {
Iterator<TopicIF> it = loaded.iterator();
while (it.hasNext()) {
TopicIF sourceRelated = it.next();
TopicIF targetRelated = createTopic(targetTopicMap,
sourceRelated.getSubjectIdentifiers(),
sourceRelated.getItemIdentifiers(),
sourceRelated.getSubjectLocators());
if (!isLoaded(targetRelated)) {
setLoaded(targetRelated);
TopicMapSynchronizer.update(targetTopicMap, sourceRelated);
}
}
}
//-------------------------------------------------------------------
// Virtual locators
//--------------------------------------------------------------------
public static final String VIRTUAL_URN = "urn:x-oks-virtual:";
public static boolean isVirtualReference(String address) {
return address.startsWith(VIRTUAL_URN);
}
public static String resolveVirtualReference(String address, String tmid) {
String topicMapIndex = RemoteTopicIndex.sourceTopicMapFromVirtualReference(address);
if (!topicMapIndex.equals(tmid))
throw new OntopiaRuntimeException("Topic map IDs do not match, requested=" + topicMapIndex +
", current=" + tmid);
return address.substring(address.indexOf('#') + 1);
}
public static String sourceTopicMapFromVirtualReference(String address) {
int index = address.indexOf('#');
return address.substring(RemoteTopicIndex.VIRTUAL_URN.length(), index);
}
}