package won.node.service.impl;
import org.apache.jena.graph.TripleBoundary;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.DatasetFactory;
import org.apache.jena.rdf.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import won.protocol.exception.*;
import won.protocol.model.*;
import won.protocol.repository.*;
import won.protocol.service.WonNodeInformationService;
import won.protocol.util.DataAccessUtils;
import won.protocol.vocabulary.WON;
import java.net.URI;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* User: gabriel
* Date: 06/11/13
*/
public class DataAccessServiceImpl implements won.node.service.DataAccessService
{
private final Logger logger = LoggerFactory.getLogger(getClass());
private URIService URIService;
@Autowired
private NeedRepository needRepository;
@Autowired
private ConnectionRepository connectionRepository;
@Autowired
private FacetRepository facetRepository;
@Autowired
private WonNodeInformationService wonNodeInformationService;
@Autowired
protected ConnectionContainerRepository connectionContainerRepository;
@Autowired
protected NeedEventContainerRepository needEventContainerRepository;
@Autowired
protected ConnectionEventContainerRepository connectionEventContainerRepository;
@Autowired
protected DatasetHolderRepository datasetHolderRepository;
/**
* Creates a new Connection object.
*
* @param connectionURI
* @param needURI
* @param otherNeedURI
* @param otherConnectionURI
* @param facetURI
* @param connectionState
* @param connectionEventType
* @return
* @throws NoSuchNeedException
* @throws IllegalMessageForNeedStateException
* @throws ConnectionAlreadyExistsException
*/
public Connection createConnection(final URI connectionURI, final URI needURI, final URI otherNeedURI, final URI otherConnectionURI,
final URI facetURI, final ConnectionState connectionState,
final ConnectionEventType connectionEventType)
throws NoSuchNeedException, IllegalMessageForNeedStateException, ConnectionAlreadyExistsException {
if (needURI == null) throw new IllegalArgumentException("needURI is not set");
if (otherNeedURI == null) throw new IllegalArgumentException("otherNeedURI is not set");
if (needURI.equals(otherNeedURI)) throw new IllegalArgumentException("needURI and otherNeedURI are the same");
if (facetURI == null) throw new IllegalArgumentException("facetURI is not set");
//Load need (throws exception if not found)
Need need = DataAccessUtils.loadNeed(needRepository, needURI);
if (!isNeedActive(need))
throw new IllegalMessageForNeedStateException(needURI, connectionEventType.name(), need.getState());
//TODO: create a proper exception if a facet is not supported by a need
if(facetRepository.findByNeedURIAndTypeURI(needURI, facetURI).isEmpty()) throw new RuntimeException("Facet is not supported by Need: " + facetURI);
/* Create connection */
Connection con = new Connection();
//create and set new uri
con.setConnectionURI(connectionURI);
con.setNeedURI(needURI);
con.setState(connectionState);
con.setRemoteNeedURI(otherNeedURI);
con.setRemoteConnectionURI(otherConnectionURI);
con.setTypeURI(facetURI);
ConnectionEventContainer connectionEventContainer = new ConnectionEventContainer(con, connectionURI);
try {
con = connectionRepository.save(con);
connectionEventContainerRepository.save(connectionEventContainer);
} catch (Exception e){
//we assume the unique key constraint on needURI, remoteNeedURI, typeURI was violated: we have to perform an
// update, not an insert
logger.warn("caught exception, assuming unique key constraint on needURI, remoteNeedURI, typeURI was violated" +
". Throwing a ConnectionAlreadyExistsException. TODO: think about handling this exception " +
"separately", e);
throw new ConnectionAlreadyExistsException(con.getConnectionURI(),needURI, otherNeedURI);
}
return con;
}
@Override
public Collection<URI> getSupportedFacets(URI needUri) throws NoSuchNeedException
{
List<URI> ret = new LinkedList<URI>();
Need need = DataAccessUtils.loadNeed(needRepository, needUri);
DatasetHolder datasetHolder = need.getDatatsetHolder();
Model content = null;
content = datasetHolder.getDataset().getDefaultModel();
if (content == null) {
throw new IllegalStateException("tried to access content dataset of need '"+need.getNeedURI()+"' but found " +
"none!");
}
Resource baseRes = content.getResource(content.getNsPrefixURI(""));
StmtIterator stmtIterator = baseRes.listProperties(WON.HAS_FACET);
while (stmtIterator.hasNext()) {
RDFNode object = stmtIterator.nextStatement().getObject();
if (object.isURIResource()){
ret.add(URI.create(object.toString()));
}
}
return ret;
}
@Override
public Connection getConnection(List<Connection> connections, URI facetURI, ConnectionEventType eventType)
throws ConnectionAlreadyExistsException {
Connection con = null;
for(Connection c : connections) {
//TODO: check remote need type as well
if (facetURI.equals(c.getTypeURI()))
con = c;
}
return con;
}
@Override
public Connection nextConnectionState(URI connectionURI, ConnectionEventType connectionEventType)
throws NoSuchConnectionException, IllegalMessageForConnectionStateException {
if (connectionURI == null) throw new IllegalArgumentException("connectionURI is not set");
//load connection, checking if it exists
Connection con = DataAccessUtils.loadConnection(connectionRepository, connectionURI);
//perform state transit
ConnectionState nextState = performStateTransit(con, connectionEventType);
//set new state and save in the db
con.setState(nextState);
//save in the db
return connectionRepository.save(con);
}
@Override
public Connection nextConnectionState(Connection con, ConnectionEventType connectionEventType)
throws IllegalMessageForConnectionStateException {
//perform state transit
ConnectionState nextState = performStateTransit(con, connectionEventType);
//set new state and save in the db
con.setState(nextState);
//save in the db
return connectionRepository.save(con);
}
/**
* Adds feedback, represented by the subgraph reachable from feedback, to the RDF description of the
* item identified by forResource
* @param connection
* @param feedback
* @return true if feedback could be added false otherwise
*/
@Override
public boolean addFeedback(final Connection connection, final Resource feedback){
//TODO: concurrent modifications to the model for this resource result in side-effects.
//think about locking.
logger.debug("adding feedback to resource {}", connection);
DatasetHolder datasetHolder = connection.getDatasetHolder();
Dataset dataset = null;
if (datasetHolder == null) {
//if no dataset is found, we create one.
dataset = DatasetFactory.create();
datasetHolder = new DatasetHolder(connection.getConnectionURI(), dataset);
connection.setDatasetHolder(datasetHolder);
} else {
dataset = datasetHolder.getDataset();
}
Model model = dataset.getDefaultModel();
Resource mainRes = model.getResource(connection.getConnectionURI().toString());
if (mainRes == null){
logger.debug("could not add feedback to resource {}: resource not found/created in model", connection.getConnectionURI());
return false;
}
mainRes.addProperty(WON.HAS_FEEDBACK_EVENT, feedback);
ModelExtract extract = new ModelExtract(new StatementTripleBoundary(TripleBoundary.stopNowhere));
model.add(extract.extract(feedback, feedback.getModel()));
dataset.setDefaultModel(model);
datasetHolder.setDataset(dataset);
datasetHolderRepository.save(datasetHolder);
connectionRepository.save(connection);
logger.debug("done adding feedback for resource {}", connection);
return true;
}
@Override
public void updateRemoteConnectionURI(Connection con, URI remoteConnectionURI) {
if (logger.isDebugEnabled()) {
logger.debug("updating remote connection URI of con {} to {}", con, remoteConnectionURI);
}
con.setRemoteConnectionURI(remoteConnectionURI);
connectionRepository.save(con);
}
private boolean isNeedActive(final Need need) {
return NeedState.ACTIVE == need.getState();
}
/**
* Calculates the ATConnectionState resulting from the message in the current connection state.
* Checks if the specified message is allowed in the connection's state and throws an exception if not.
*
* @param con
* @param msg
* @return
* @throws won.protocol.exception.IllegalMessageForConnectionStateException
* if the message is not allowed in the connection's current state
*/
private ConnectionState performStateTransit(Connection con, ConnectionEventType msg) throws IllegalMessageForConnectionStateException
{
if (!msg.isMessageAllowed(con.getState())) {
throw new IllegalMessageForConnectionStateException(con.getConnectionURI(), msg.name(), con.getState());
}
return con.getState().transit(msg);
}
@Override
public void setURIService(URIService URIService) {
this.URIService = URIService;
}
}