/******************************************************************************* * Copyright 2014 Miami-Dade County * * 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.sharegov.cirm.owl; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import mjson.Json; import org.hypergraphdb.app.owl.HGDBOWLManager; import org.hypergraphdb.app.owl.versioning.distributed.VDHGDBOntologyRepository; import org.hypergraphdb.app.owl.versioning.distributed.activity.BrowseRepositoryActivity; import org.hypergraphdb.app.owl.versioning.distributed.activity.PullActivity; import org.hypergraphdb.app.owl.versioning.distributed.activity.BrowseRepositoryActivity.BrowseEntry; import org.hypergraphdb.peer.HGPeerIdentity; import org.hypergraphdb.peer.workflow.ActivityResult; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLOntologyID; import org.sharegov.cirm.StartUp; import org.sharegov.cirm.utils.ThreadLocalStopwatch; /** * Encapsulate the OWL repository and networking services with some * convenience operations needed by the application; * * @author boris, Thomas Hilpold * */ public class OwlRepo { private static final OwlRepo instance = new OwlRepo(); /** * The timeout to use for activities that interact with the ontology server. */ public static final int TIMEOUT_BROWSE_SECS = 180; //3 min timeout (prev was 1 min) public static final int TIMEOUT_PULL_SECS = 600; //10 min timeout (prev was 3 min) public static final int FIND_ONTO_SERVER_MAX_ATTEMPTS = 10; //10 attampts with 1 sec pause /** * Prefer Refs.owlRepo.resolve() as the correct method to get a reference to the repo. */ public static OwlRepo getInstance() { return instance; } private volatile VDHGDBOntologyRepository repo; private volatile HGPeerIdentity ontoServer = null; /** * Attempts to find the ontoServer as connected XMPP peer. * @return HGPeerIdentity if ontoServer found, or null if not found yet. */ private HGPeerIdentity findOntoServer() { String ontoServerName = StartUp.getConfig().at("network").at("ontoServer").asString(); for (HGPeerIdentity id : repo.getPeer().getConnectedPeers()) { String name = (String)repo.getPeer().getNetworkTarget(id); if (name.startsWith(ontoServerName)) { return id; } } return null; //ontoServer not found } private void ensureRepository() { if (repo == null) { repo = VDHGDBOntologyRepository.getInstance(); if (repo.getOntologyManager() == null) { repo.setOntologyManager(HGDBOWLManager.createOWLOntologyManager()); } } } public HGPeerIdentity getDefaultPeer() { return ontoServer; } public VDHGDBOntologyRepository repo() { ensureRepository(); return repo; } /** * Starts XMPP networking if needed by connecting this instance to the network and also attempting to find the ontology server. * The XMPP chat server must be available, and the configured ontology server must be on the roster of this instance's XMPP user for this method to return. * * @return always true, fails with various RuntimeExceptions if any problem is detected. */ public boolean ensurePeerStarted() { ensureRepository(); if (repo.getPeer() != null && repo.getPeer().getPeerInterface().isConnected() && ontoServer != null) return true; synchronized (repo) { if (repo.getPeer() == null || !repo.getPeer().getPeerInterface().isConnected()) { Json config = StartUp.getConfig().at("network"); if (config == null) throw new RuntimeException("No network configured."); repo.startNetworking(config.at("user").asString(), config.at("password").asString(), config.at("serverUrl").asString()); } if (ontoServer == null) { int attempts = 0; do { attempts ++; ontoServer = findOntoServer(); try { Thread.sleep(1000); } catch (Throwable t) { } } while (ontoServer == null && attempts < FIND_ONTO_SERVER_MAX_ATTEMPTS); } if (ontoServer == null) { repo.getPeer().stop(); throw new RuntimeException("Ontology Server " + StartUp.getConfig().at("network").at("ontoServer").asString() + " is offline, please ensure server is started and try again."); } else { ThreadLocalStopwatch.now("Found ontoServer for OwlRepo: " + ontoServer.getHostname() + " (" + ontoServer.getIpAddress() + ")"); } return true; } } /** * Creates a local repository at the given location and downloads (pulls) ontologies from the default peer. * @param dbLocation * @param ontologyIRIs */ public void createRepositoryFromDefaultPeer(String dbLocation, Set<IRI> ontologyIRIs) { if (repo != null) throw new IllegalStateException("A repository was already created. This method must be called before."); File f = new File(dbLocation); if (!f.exists()) f.mkdir(); VDHGDBOntologyRepository.setHypergraphDBLocation(dbLocation); ensureRepository(); pullNewFromDefaultPeer(ontologyIRIs); } /** * Pulls (downloads) all versioned ontologies specified by the ontologyIRIs and imports them into the local repository. * None of the ontologies may exist locally and all ontologies must exist remotely before calling this method. * Transactional. * * @param ontologyIRIs * @throws IllegalStateException if any ontology already exists locally or does not exist remotely. * @throws RuntimeException on Timeouts, or if any processing error occurred. */ public void pullNewFromDefaultPeer(final Set<IRI> ontologyIRIs) { repo.getHyperGraph().getTransactionManager().ensureTransaction(new Callable<Object>() { public Object call() { for(IRI ontoIri : ontologyIRIs) { OWLOntologyID ontoId = new OWLOntologyID(ontoIri); if (repo.existsOntology(ontoId)) { throw new IllegalStateException("Cannot create new ontology in local repository with IRI " + ontoIri +" because it exists locally. \r\n "); } } ensurePeerStarted(); ThreadLocalStopwatch.now("Connected to Ontology Server " + getDefaultPeer()); BrowseRepositoryActivity browseAct = repo.browseRemote(ontoServer); ActivityResult actResult; try { Future<ActivityResult> browseActFuture = browseAct.getFuture(); actResult = browseActFuture.get(TIMEOUT_BROWSE_SECS, TimeUnit.SECONDS); if (!browseActFuture.isDone()) { throw new TimeoutException("browsing for ontologies at the ontology server timed out after " + TIMEOUT_BROWSE_SECS + " secs"); } else if (actResult.getException() != null) { throw new RuntimeException("browsing for ontologies at the ontology server failed", actResult.getException()); } List<BrowseEntry> remoteEntries = findRemoteEntriesFor(ontologyIRIs, browseAct.getRepositoryBrowseEntries()); for(BrowseEntry remoteEntry : remoteEntries) { if (repo.getHyperGraph().get(remoteEntry.getUuid()) != null) { throw new IllegalStateException("Cannot create ontology " + remoteEntry.getOwlOntologyIRI() + "in local repository with UUID " + remoteEntry.getUuid()+ " because it (the uuid) exists locally. \r\n "); } } for(BrowseEntry remoteEntry : remoteEntries) { ThreadLocalStopwatch.now("Pulling new ontology from remote: " + remoteEntry.getOwlOntologyIRI() + " (" + remoteEntry.getUuid() + ")" + " Mode: " + remoteEntry.getDistributionMode()); PullActivity pullNewAct = repo.pullNew(remoteEntry.getUuid(), ontoServer); Future<ActivityResult> pullNewActFuture = pullNewAct.getFuture(); actResult = pullNewActFuture.get(TIMEOUT_PULL_SECS, TimeUnit.SECONDS); if (!pullNewActFuture.isDone()) { throw new TimeoutException("PullActivity for new ontology " + remoteEntry.getOwlOntologyIRI() + " timed out after " + TIMEOUT_PULL_SECS + " secs"); } else if (actResult.getException() != null) { throw new RuntimeException("PullActivity for new ontology " + remoteEntry.getOwlOntologyIRI() + " failed", actResult.getException()); } ThreadLocalStopwatch.now("Pulling new completed: " + pullNewAct.getCompletedMessage()); } } catch (Exception e) { throw new RuntimeException("Exception in pullNewFromDefaultPeer. Please kill the process manually.", e); } return null; } }); } /** * Gets a list of BrowseEntries for all iris by searching in remoteEntries. * @param iris * @param remoteEntries * @return * @throws IllegalStateException if at least one IRI was not found. */ private List<BrowseEntry> findRemoteEntriesFor(Set<IRI> ontologyIRIs, List<BrowseEntry> remoteEntries) { List<BrowseEntry> l = new ArrayList<BrowseEntry>(); BrowseEntry found = null; for (IRI iri : ontologyIRIs) { for (BrowseEntry remote : remoteEntries) { if (remote.getOwlOntologyIRI().equals(iri.toString())) { found = remote; break; } } if (found != null) { l.add(found); found = null; } else { throw new IllegalStateException("OntologyIRI not found in remote entries: " + iri); } } return l; } }