/* * Copyright 2008 Fedora Commons, Inc. * * 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.mulgara.resolver.distributed; import org.mulgara.query.Answer; import org.mulgara.query.Constraint; import org.mulgara.query.ConstraintElement; import org.mulgara.query.ConstraintImpl; import org.mulgara.query.LocalNode; import org.mulgara.query.GraphResource; import org.mulgara.query.Query; import org.mulgara.query.QueryException; import org.mulgara.query.TuplesException; import org.mulgara.query.UnconstrainedAnswer; import org.mulgara.query.Variable; import org.mulgara.query.rdf.URIReferenceImpl; import org.mulgara.server.Session; import org.mulgara.server.ServerInfo; import org.mulgara.server.NonRemoteSessionException; import org.mulgara.server.driver.SessionFactoryFinderException; import org.mulgara.util.URIUtil; import org.mulgara.resolver.distributed.remote.StatementSetFactory; import org.mulgara.resolver.spi.GlobalizeException; import org.mulgara.resolver.spi.Resolution; import org.mulgara.resolver.spi.ResolverException; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.resolver.spi.Statements; import org.apache.log4j.Logger; import org.jrdf.graph.Node; import org.jrdf.graph.Triple; import org.jrdf.graph.URIReference; import java.net.URI; import java.net.URISyntaxException; import java.util.*; import javax.transaction.xa.XAException; /** * Resolve a constraint across a socket. * * @created 2007-03-20 * @author <a href="mailto:gearon@users.sourceforge.net">Paula Gearon</a> * @copyright © 2007 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class NetworkDelegator implements Delegator { /** Logger. */ private static final Logger logger = Logger.getLogger(NetworkDelegator.class.getName()); /** The session to delegate resolutions through. */ private final ResolverSession session; /** Whether the current transaction is r/w or r/o. */ private final boolean forWrite; /** The transaction coordinator with which to register new XAResource's */ private final TransactionCoordinator txCord; /** The session cache to use. */ private final SessionCache sessionCache; /** The map of distributed sessions. */ private Map<URI,Session> sessionMap = new HashMap<URI,Session>(); /** * Constructs a delegator, using a given session. * @param session The session to delegate resolution through. * @param forWrite Whether to open this for writes or for read-only * @param txCord the transaction-coordinator being used * @param sessionCache the session cache to use */ public NetworkDelegator(ResolverSession session, boolean forWrite, TransactionCoordinator txCord, SessionCache sessionCache) { this.session = session; this.forWrite = forWrite; this.txCord = txCord; this.sessionCache = sessionCache; } /** * Resolve a given constraint down to the appropriate resolution. * @param localConstraint The constraint to resolve in local form. * @param localModel The LocalNode containing the model. * @throws QueryException A error occurred resolving the constraint. * @throws ResolverException A error occurred setting up the resolution. */ public Resolution resolve(Constraint localConstraint, LocalNode localModel) throws QueryException, ResolverException { // globalize the model URIReferenceImpl modelRef = getModelRef(localModel); URI serverUri = getServerUri(modelRef); try { modelRef = new URIReferenceImpl(URIUtil.localizeGraphUri(modelRef.getURI())); logger.debug("Querying for: " + localConstraint + " in model: " + modelRef + " on server: " + serverUri); Answer ans = getServerSession(serverUri).query(globalizedQuery(localConstraint, modelRef)); return new AnswerResolution(serverUri, session, ans, localConstraint); } catch (TuplesException te) { throw new ResolverException("Localization failed", te); } catch (URISyntaxException qe) { throw new QueryException("Bad graph URI provided for resolution", qe); } } /** * Add a set of statements to a model. * @param model The <code>long</code> containing the model gNode. * @param statements The statements to add to the model. * @throws ResolverException A delegator specific problem occurred adding the data. * @throws QueryException There was an error adding data at the remote end. */ public void add(long model, Statements statements) throws ResolverException, QueryException { // globalize the model URIReferenceImpl modelRef = getModelRef(model); // find and verify the server URI serverUri = getServerUri(modelRef); logger.debug("Adding data to model: " + modelRef + " on server: " + serverUri); // convert the data to something shippable try { Set<Triple> statementSet = StatementSetFactory.newStatementSet(statements, session); getServerSession(serverUri).insert(modelRef.getURI(), statementSet); } catch (GlobalizeException ge) { throw new ResolverException("Insertion data can't be sent over a network", ge); } catch (TuplesException te) { throw new ResolverException("Insertion data inaccessible", te); } } /** * Remove a set of statements from a model. * @param model The <code>long</code> containing the model gNode. * @param statements The statements to remove from the model. * @throws ResolverException A delegator specific problem occurred removing the data. * @throws QueryException There was an error removing data at the remote end. */ public void remove(long model, Statements statements) throws ResolverException, QueryException { // globalize the model URIReferenceImpl modelRef = getModelRef(model); // find and verify the server URI serverUri = getServerUri(modelRef); logger.debug("Removing data from model: " + modelRef + " on server: " + serverUri); // convert the data to something shippable try { Set<Triple> statementSet = StatementSetFactory.newStatementSet(statements, session); getServerSession(serverUri).delete(modelRef.getURI(), statementSet); } catch (GlobalizeException ge) { throw new ResolverException("Deletion data can't be sent over a network", ge); } catch (TuplesException te) { throw new ResolverException("Deletion data inaccessible", te); } } /** * Convert a local node representing a model into a URIReferenceImpl. * @param localModel The local node to convert. * @return The URIReference for the model * @throws ResolverException The Node was not recognized as a model. */ protected URIReferenceImpl getModelRef(LocalNode localModel) throws ResolverException { return getModelRef(localModel.getValue()); } /** * Convert a model gNode into a URIReferenceImpl. * @param modelGNode The gNode to convert. * @return The URIReference for the model * @throws ResolverException The gNode was not recognized as a model. */ protected URIReferenceImpl getModelRef(long modelGNode) throws ResolverException { // globalize the model Node modelNode = globalizeNode(modelGNode); if (!(modelNode instanceof URIReference)) throw new ResolverException("Unexpected model type in constraint: (" + modelNode.getClass() + ")" + modelNode.toString()); // convert the node to a URIReferenceImpl, which includes the Value interface return makeRefImpl((URIReference)modelNode); } /** * Create a query for a single constraint. * @param constraint The local constraint to query for. * @return The globalized query, looking for the single constraint. * @throws ResolverException There was an error globalizing the constraint elements. */ @SuppressWarnings("unchecked") protected Query globalizedQuery(Constraint localConstraint, URIReferenceImpl model) throws ResolverException { // convert the constraint to network compatible form Constraint globalConstraint = new ConstraintImpl( globalizeConstraintElement(localConstraint.getElement(0)), globalizeConstraintElement(localConstraint.getElement(1)), globalizeConstraintElement(localConstraint.getElement(2)), model ); // convert the variable set to a variable list - add types via unchecked casts List<Variable> variables = new ArrayList<Variable>((Set<Variable>)globalConstraint.getVariables()); // build the new query return new Query(variables, new GraphResource(model.getURI()), globalConstraint, null, Collections.EMPTY_LIST, null, 0, true, new UnconstrainedAnswer()); } /** * Convert a local node to a global value. * @param localNode The node to globalize. * @return The globalized node, either a BlankNode, a URIReference, or a Literal. * @throws ResolverException An error occurred while globalizing */ protected Node globalizeNode(LocalNode localNode) throws ResolverException { return globalizeNode(localNode.getValue()); } /** * Convert a gNode to a global node value. * @param gNode The node id to globalize. * @return The globalized node, either a BlankNode, a URIReference, or a Literal. * @throws QueryException An error occurred while globalizing */ protected Node globalizeNode(long gNode) throws ResolverException { try { return session.globalize(gNode); } catch (GlobalizeException ge) { throw new ResolverException("Error globalizing gNode: " + gNode, ge); } } /** * Converts a constraint element from local form into global form. * @param localElement The constraint element in local form. * @throws ResolverException The constraint element could not be globalized. */ protected ConstraintElement globalizeConstraintElement(ConstraintElement localElement) throws ResolverException { // return the element if it does not need to be converted if (!(localElement instanceof LocalNode) || (localElement instanceof URIReferenceImpl)) return localElement; // convert the reference to a Value return makeRefImpl((URIReference)globalizeNode((LocalNode)localElement)); } /** * Guarantee that a URIReference is a URIReferenceImpl, wrapping in a new URIReferenceImpl if needed. * This method is required since URIReferenceImpl meets the Value interface when URIReference does not. * @param ref The reference to convert if needed. * @return A URIReferenceImpl matching ref. */ protected URIReferenceImpl makeRefImpl(URIReference ref) { return (ref instanceof URIReferenceImpl) ? (URIReferenceImpl)ref : new URIReferenceImpl(ref.getURI()); } /** * Tests if a model is really on a different server. If the model is local then throw an exception. * @param modelUri The URI of the model to test. * @throws ResolverException Thrown when the model is on the current system. */ protected static void testForLocality(URI modelUri) throws ResolverException { String protocol = modelUri.getScheme(); if (!DistributedResolverFactory.getProtocols().contains(protocol)) { throw new IllegalStateException("Bad Protocol sent to distributed resolver."); } String host = modelUri.getHost(); if (ServerInfo.getHostnameAliases().contains(host)) { // on the same machine. Check if the server is different. URI serverUri = ServerInfo.getServerURI(); if (serverUri != null && serverUri.getPath().equals(modelUri.getPath())) { throw new ResolverException("Attempt to resolve a local model through the distributed resolver."); } } } /** * Gets the URI for a server. * @param modelUri The URI of the model we are getting the server for. * @return A new URI containing just the server information. * @throws ResolverException The model is not on a remote server. */ protected static URI getServerUri(URIReference model) throws ResolverException { try { // check if this model is really on a remote server URI modelUri = model.getURI(); testForLocality(modelUri); // use the URI without the model fragment return new URI(modelUri.getScheme(), modelUri.getSchemeSpecificPart(), null); } catch (URISyntaxException use) { throw new AssertionError(use); } } /** * Retrieves a session for a given server URI, using a cached value if possible. * @param serverUri The URI of the server to get a session for. * @return a remote session on the host specified in serverUri. * @throws QueryException Thrown when the session cannot be created. */ protected Session getServerSession(URI serverUri) throws QueryException { Session session = sessionMap.get(serverUri); return (session != null) ? session : newSession(serverUri); } /** * Get a new session and save in the cache. * @param serverUri The URI of the server to create a session for. * @return A new remote session. * @throws QueryException There was a problem creating the session. */ protected Session newSession(URI serverUri) throws QueryException { try { // get a new session Session session = sessionCache.getSession(serverUri); sessionMap.put(serverUri, session); // get the XAResource and enlist it txCord.enlistResource(forWrite ? session.getXAResource() : session.getReadOnlyXAResource()); // done return session; } catch (NonRemoteSessionException nrse) { throw new QueryException("State Error: non-local URI was mapped to a local session", nrse); } catch (SessionFactoryFinderException sffe) { throw new QueryException("Unable to get a session to the server", sffe); } catch (XAException xae) { throw new QueryException("Error enlisting xaresource", xae); } } /** * Return all sessions used by this delegator. */ public void close() { for (Map.Entry<URI,Session> e : sessionMap.entrySet()) { sessionCache.returnSession(e.getKey(), e.getValue()); } } }