package won.node.facet.impl; import org.apache.jena.query.Dataset; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFDataMgr; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import won.node.service.DataAccessService; import won.protocol.exception.*; import won.protocol.message.WonMessage; import won.protocol.model.Connection; import won.protocol.model.Need; import won.protocol.model.NeedState; import won.protocol.repository.DatasetHolderRepository; import won.protocol.repository.NeedRepository; import won.protocol.service.WonNodeInformationService; import won.protocol.util.linkeddata.LinkedDataSource; import won.protocol.vocabulary.WON; import java.io.StringWriter; import java.net.URI; import java.util.concurrent.ExecutorService; /** * Created with IntelliJ IDEA. * User: gabriel * Date: 16.09.13 * Time: 17:09 * To change this template use File | Settings | File Templates. */ public abstract class AbstractFacet implements Facet { //string to be appended to the need uri, and prepended to a facet-specific identifier // so as to form a unique graph name used for storing data that is managed by the facet private static final String FACET_GRAPH_PATH = "facetgraph"; private final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired protected WonNodeInformationService wonNodeInformationService; @Autowired protected LinkedDataSource linkedDataSource; @Autowired protected DatasetHolderRepository datasetHolderRepository; @Autowired NeedRepository needRepository; protected won.node.service.impl.URIService URIService; protected ExecutorService executorService; protected DataAccessService dataService; /** * A string that is used to create a graph URI used for need data managed by this * facet. * @return */ protected String getIdentifierForFacetManagedGraph(){ return getClass().getSimpleName(); } /** * * This function is invoked when an owner sends an open message to a won node and usually executes registered facet specific code. * It is used to open a connection which is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void openFromOwner(final Connection con, final Model content, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { //inform the other side //in an 'open' call the local and the remote connection URI are always known and must be present //in the con object. if (wonMessage.getReceiverURI() != null) { executorService.execute(new Runnable() { @Override public void run() { // try { // needFacingConnectionClient.open(con, content, wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in openFromOwner", e); // } } }); } } /** * * This function is invoked when an owner sends a close message to a won node and usually executes registered facet specific code. *It is used to close a connection which is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void closeFromOwner(final Connection con, final Model content, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { // //inform the other side // //TODO: don't inform the other side if there is none (suggested, request_sent states) // if (con.getRemoteConnectionURI() != null) { // executorService.execute(new Runnable() // { // @Override // public void run() // { // try { // needFacingConnectionClient.close(con, content, wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in closeFromOwner: ",e); // } // } // }); // } } /** * This function is invoked when an owner sends a text message to a won node and usually executes registered facet specific code. * It is used to indicate the sending of a chat message with by the specified connection object con * to the remote partner. * * * @param con the connection object * @param message the chat message * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void sendMessageFromOwner(final Connection con, final Model message, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { //inform the other side executorService.execute(new Runnable() { @Override public void run() { // try { // needFacingConnectionClient.sendMessage(con, message, wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in textMessageFromOwner: ",e); // } } }); } /** * * This function is invoked when an won node sends an open message to another won node and usually executes registered facet specific code. * It is used to open a connection which is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void openFromNeed(final Connection con, final Model content, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { //inform the need side executorService.execute(new Runnable() { @Override public void run() { // try { // ownerFacingConnectionClient.open(wonMessage.getReceiverURI(), // content, // wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in openFromNeed:", e); // } } }); } /** * * This function is invoked when an won node sends a close message to another won node and usually executes registered facet specific code. * It is used to close a connection which is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void closeFromNeed(final Connection con, final Model content, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { //inform the need side executorService.execute(new Runnable() { @Override public void run() { // try { // ownerFacingConnectionClient.close(con.getConnectionURI(), content, wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in closeFromNeed:", e); // } } }); } /** * This function is invoked when a won node sends a text message to another won node and usually executes registered facet specific code. * It is used to indicate the sending of a chat message with by the specified connection object con * to the remote partner. * * * @param con the connection object * @param message the chat message * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void sendMessageFromNeed(final Connection con, final Model message, final WonMessage wonMessage) throws NoSuchConnectionException, IllegalMessageForConnectionStateException { //send to the need side executorService.execute(new Runnable() { @Override public void run() { // try { // ownerFacingConnectionClient.sendMessage(con.getConnectionURI(), message, wonMessage); // } catch (Exception e) { // logger.warn("caught Exception in textMessageFromNeed:", e); // } } }); } /** * This function is invoked when a matcher sends a hint message to a won node and * usually executes registered facet specific code. * It notifies the need of a matching otherNeed with the specified match score. Originator * identifies the entity making the call. Normally, originator is a matching service. * A rdf graph can be sent along with the request. * * @param con the connection object * @param score match score between 0.0 (bad) and 1.0 (good). Implementations treat lower values as 0.0 and higher values as 1.0. * @param originator an URI identifying the calling entity * @param content (optional) an optional RDF graph containing more detailed information about the hint. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the match event. * @throws won.protocol.exception.NoSuchNeedException * if needURI is not a known need URI * @throws won.protocol.exception.IllegalMessageForNeedStateException * if the need is not active */ @Override public void hint(final Connection con, final double score, final URI originator, final Model content, final WonMessage wonMessage) throws NoSuchNeedException, IllegalMessageForNeedStateException { Model remoteFacetModelCandidate = content; if (wonMessage == null) remoteFacetModelCandidate = changeHasRemoteFacetToHasFacet(content); final Model remoteFacetModel = remoteFacetModelCandidate; try { executorService.execute(new Runnable() { @Override public void run() { //here, we don't really need to handle exceptions, as we don't want to flood matching services with error messages // try { // ownerProtocolOwnerService.hint( // con.getNeedURI(), con.getRemoteNeedURI(), // score, originator, remoteFacetModel, wonMessage); // } catch (NoSuchNeedException e) { // logger.warn("error sending hint message to owner - no such need:", e); // } catch (IllegalMessageForNeedStateException e) { // logger.warn("error sending hint content to owner - illegal need state:", e); // } catch (Exception e) { // logger.warn("error sending hint content to owner:", e); // } } }); } catch (WonMessageBuilderException e) { logger.warn("error creating HintMessage", e); } } /** * * This function is invoked when an won node sends an connect message to another won node and usually executes registered facet specific code. * The connection is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void connectFromNeed(final Connection con, final Model content, final WonMessage wonMessage) throws NoSuchNeedException, IllegalMessageForNeedStateException, ConnectionAlreadyExistsException { final Connection connectionForRunnable = con; executorService.execute(new Runnable() { @Override public void run() { // try { // ownerProtocolOwnerService.connect(con.getNeedURI(), con.getRemoteNeedURI(), // connectionForRunnable.getConnectionURI(), content, wonMessage); // } catch (WonProtocolException e) { // // we can't connect the connection. we send a deny back to the owner // // TODO should we introduce a new protocol method connectionFailed (because it's not an owner deny but some protocol-level error)? // // For now, we call the close method as if it had been called from the owner side // // TODO: even with this workaround, it would be good to send a content along with the close (so we can explain what happened). // logger.warn("could not connectFromNeed, sending close back. Exception was: ",e); //// try { //// // ToDo //// ownerFacingConnectionCommunicationService.close( //// connectionForRunnable.getConnectionURI(), content, wonMessage); //// } catch (Exception e1) { //// logger.warn("caught Exception sending close back from connectFromNeed:", e1); //// } // } } }); } /** * * This function is invoked when an owner sends an open message to the won node and usually executes registered facet specific code. * The connection is identified by the connection object con. A rdf graph can be sent along with the request. * * @param con the connection object * @param content a rdf graph describing properties of the event. The null releative URI ('<>') inside that graph, * as well as the base URI of the graph will be attached to the resource identifying the event. * @throws NoSuchConnectionException if connectionURI does not refer to an existing connection * @throws IllegalMessageForConnectionStateException if the message is not allowed in the current state of the connection */ @Override public void connectFromOwner(final Connection con, final Model content, WonMessage wonMessage) throws NoSuchNeedException, IllegalMessageForNeedStateException, ConnectionAlreadyExistsException { Model remoteFacetModel = null; remoteFacetModel = changeHasRemoteFacetToHasFacet(content); final Connection connectionForRunnable = con; //send to need // try { // final ListenableFuture<URI> remoteConnectionURI = needProtocolNeedService.connect(con.getRemoteNeedURI(), // con.getNeedURI(), connectionForRunnable.getConnectionURI(), remoteFacetModel, wonMessage); // this.executorService.execute(new Runnable(){ // @Override // public void run() { // try{ // if (logger.isDebugEnabled()) { // logger.debug("saving remote connection URI"); // } // dataService.updateRemoteConnectionURI(con, remoteConnectionURI.get()); // } catch (Exception e) { // logger.warn("Error saving connection {}. Stacktrace follows", con); // logger.warn("Error saving connection ", e); // } // } // }); // // } catch (WonProtocolException e) { // // we can't connect the connection. we send a close back to the owner // // TODO should we introduce a new protocol method connectionFailed (because it's not an owner deny but some protocol-level error)? // // For now, we call the close method as if it had been called from the remote side // // TODO: even with this workaround, it would be good to send a content along with the close (so we can explain what happened). // logger.warn("could not connectFromOwner, sending close back. Exception was: ",e); // try { // // // this WonMessage is not valid (the sender part) since it should be send from the remote WON node // // but it should be replaced with a response message anyway // WonMessageBuilder builder = new WonMessageBuilder(); // WonMessage closeWonMessage = builder // .setMessageURI(wonNodeInformationService.generateEventURI( // wonMessage.getSenderNodeURI())) //not sure if this is correct // .setWonMessageType(WonMessageType.CLOSE) // .setSenderURI(wonMessage.getSenderURI()) // .setSenderNeedURI(wonMessage.getSenderNeedURI()) // .setSenderNodeURI(wonMessage.getSenderNodeURI()) // .setReceiverURI(wonMessage.getSenderURI()) // .setReceiverNeedURI(wonMessage.getSenderNeedURI()) // .setReceiverNodeURI(wonMessage.getSenderNodeURI()) // .setWonMessageDirection(WonMessageDirection.FROM_EXTERNAL) // .build(); // //// needFacingConnectionCommunicationService.close( //// connectionForRunnable.getConnectionURI(), content, closeWonMessage); // } catch (Exception e1) { // logger.warn("caught Exception sending close back from connectFromOwner::", e1); // } // } catch (Exception e) { // logger.warn("caught Exception in connectFromOwner: ",e); // } } /** * The facet may store data into the need dataset; this method finds the graph * that the facet may write to and creates a new Graph in the dataset if needed. * @param needURI * @return the facet-managed graph */ protected Model getFacetManagedGraph(URI needURI, Dataset needDataset){ String graphURI = getFacetManagedGraphName(needURI); Model facetManagedGraph = needDataset.getNamedModel(graphURI); if (facetManagedGraph == null){ facetManagedGraph = ModelFactory.createDefaultModel(); needDataset.addNamedModel(graphURI, facetManagedGraph); } return facetManagedGraph; } /** * The facet may store data into the need dataset; this method removes * that data from the need dataset. * @param needURI */ protected void removeFacetManagedGraph(URI needURI, Dataset needDataset){ String graphURI = getFacetManagedGraphName(needURI); Model facetManagedGraph = needDataset.getNamedModel(graphURI); if (facetManagedGraph != null){ needDataset.removeNamedModel(graphURI); } } private String getFacetManagedGraphName(final URI needURI) { //TODO: these checks can be made mor throrough once graph signing is in place return needURI.toString() + "/" + FACET_GRAPH_PATH + getIdentifierForFacetManagedGraph(); } /** * Creates a copy of the specified model, replacing won:hasRemoteFacet by won:hasFacet and vice versa. * @param model * @return */ private Model changeHasRemoteFacetToHasFacet(Model model) { Resource baseRes = model.getResource(model.getNsPrefixURI("")); StmtIterator stmtIterator = baseRes.listProperties(WON.HAS_REMOTE_FACET); if (!stmtIterator.hasNext()) throw new IllegalArgumentException("at least one facet must be specified with won:hasRemoteFacet"); final Model newModel = ModelFactory.createDefaultModel(); newModel.setNsPrefix("", model.getNsPrefixURI("")); newModel.add(model); newModel.removeAll(null, WON.HAS_REMOTE_FACET, null); newModel.removeAll(null, WON.HAS_FACET, null); Resource newBaseRes = newModel.createResource(newModel.getNsPrefixURI("")); //replace won:hasFacet while (stmtIterator.hasNext()) { Resource facet = stmtIterator.nextStatement().getObject().asResource(); newBaseRes.addProperty(WON.HAS_FACET, facet); } //replace won:hasRemoteFacet stmtIterator = baseRes.listProperties(WON.HAS_FACET); if (stmtIterator != null) { while (stmtIterator.hasNext()) { Resource facet = stmtIterator.nextStatement().getObject().asResource(); newBaseRes.addProperty(WON.HAS_REMOTE_FACET, facet); } } if (logger.isDebugEnabled()){ StringWriter modelAsString = new StringWriter(); RDFDataMgr.write(modelAsString, model, Lang.TTL); StringWriter newModelAsString = new StringWriter(); RDFDataMgr.write(newModelAsString, model, Lang.TTL); logger.debug("changed hasRemoteFacet to hasFacet. Old: \n{},\n new: \n{}",modelAsString.toString(), newModelAsString.toString()); } return newModel; } private boolean isNeedActive(final Need need) { return NeedState.ACTIVE == need.getState(); } public void setDataService(DataAccessService dataService) { this.dataService = dataService; } public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } public void setURIService(won.node.service.impl.URIService URIService) { this.URIService = URIService; } }