/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.stanbol.ontologymanager.multiplexer.clerezza.session;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyOption;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.stanbol.ontologymanager.core.session.TimestampedSessionIDGenerator;
import org.apache.stanbol.ontologymanager.multiplexer.clerezza.impl.SessionImpl;
import org.apache.stanbol.ontologymanager.ontonet.api.OntologyNetworkConfiguration;
import org.apache.stanbol.ontologymanager.servicesapi.OfflineConfiguration;
import org.apache.stanbol.ontologymanager.servicesapi.collector.MissingOntologyException;
import org.apache.stanbol.ontologymanager.servicesapi.collector.OntologyCollectorListener;
import org.apache.stanbol.ontologymanager.servicesapi.io.StoredOntologySource;
import org.apache.stanbol.ontologymanager.servicesapi.ontology.Multiplexer;
import org.apache.stanbol.ontologymanager.servicesapi.ontology.OWLExportable.ConnectivityPolicy;
import org.apache.stanbol.ontologymanager.servicesapi.ontology.OntologyProvider;
import org.apache.stanbol.ontologymanager.servicesapi.scope.Scope;
import org.apache.stanbol.ontologymanager.servicesapi.scope.ScopeEventListener;
import org.apache.stanbol.ontologymanager.servicesapi.scope.ScopeRegistry;
import org.apache.stanbol.ontologymanager.servicesapi.session.DuplicateSessionIDException;
import org.apache.stanbol.ontologymanager.servicesapi.session.NonReferenceableSessionException;
import org.apache.stanbol.ontologymanager.servicesapi.session.Session;
import org.apache.stanbol.ontologymanager.servicesapi.session.Session.State;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionEvent;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionEvent.OperationType;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionIDGenerator;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionLimitException;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionListener;
import org.apache.stanbol.ontologymanager.servicesapi.session.SessionManager;
import org.osgi.service.component.ComponentContext;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLOntologyID;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Calls to <code>getSessionListeners()</code> return a {@link Set} of listeners.
*
* TODO: implement storage (using persistence layer).
*
* @author alexdma
*
*/
@Component(immediate = true, metatype = true)
@Service({SessionManager.class, org.apache.stanbol.ontologymanager.ontonet.api.session.SessionManager.class})
public class SessionManagerImpl implements
org.apache.stanbol.ontologymanager.ontonet.api.session.SessionManager, ScopeEventListener {
public static final String _CONNECTIVITY_POLICY_DEFAULT = "TIGHT";
public static final String _ID_DEFAULT = "session";
public static final int _MAX_ACTIVE_SESSIONS_DEFAULT = -1;
public static final String _ONTOLOGY_NETWORK_NS_DEFAULT = "http://localhost:8080/ontonet/";
private static SessionManagerImpl me = null;
public static SessionManagerImpl get() {
return me;
}
/**
* Concatenated with the sessionManager ID, it identifies the Web endpoint and default base URI for all
* sessions.
*/
private IRI baseNS;
@Property(name = SessionManager.CONNECTIVITY_POLICY, options = {
@PropertyOption(value = '%'
+ SessionManager.CONNECTIVITY_POLICY
+ ".option.tight", name = "TIGHT"),
@PropertyOption(value = '%'
+ SessionManager.CONNECTIVITY_POLICY
+ ".option.loose", name = "LOOSE")}, value = _CONNECTIVITY_POLICY_DEFAULT)
private String connectivityPolicyString;
@Property(name = SessionManager.ID, value = _ID_DEFAULT)
protected String id;
protected SessionIDGenerator idgen;
protected Set<SessionListener> listeners;
protected Logger log = LoggerFactory.getLogger(getClass());
@Property(name = SessionManager.MAX_ACTIVE_SESSIONS, intValue = _MAX_ACTIVE_SESSIONS_DEFAULT)
private int maxSessions;
@Reference
private OfflineConfiguration offline;
@Reference
// (cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.DYNAMIC, bind =
// "bindScopeManager", unbind = "unbindScopeManager", strategy = ReferenceStrategy.EVENT)
private ScopeRegistry scopeRegistry;
@Reference
private OntologyProvider<?> ontologyProvider;
private Map<String,Session> sessionsByID;
/**
* This default constructor is <b>only</b> intended to be used by the OSGI environment with Service
* Component Runtime support.
* <p>
* DO NOT USE to manually create instances - the ReengineerManagerImpl instances do need to be configured!
* YOU NEED TO USE {@link #SessionManagerImpl(OntologyProvider, Dictionary)} to parse the configuration
* and then initialise the rule store if running outside an OSGI environment.
*/
public SessionManagerImpl() {
super();
listeners = new HashSet<SessionListener>();
sessionsByID = new HashMap<String,Session>();
}
/**
* To be invoked by non-OSGi environments.
*
* @param the
* ontology provider that will store and provide ontologies for this session manager.
* @param configuration
*/
public SessionManagerImpl(OntologyProvider<?> ontologyProvider,
OfflineConfiguration offline,
Dictionary<String,Object> configuration) {
this(ontologyProvider, null, offline, configuration);
}
/**
* To be invoked by non-OSGi environments.
*
* @param the
* ontology provider that will store and provide ontologies for this session manager.
* @param configuration
*/
public SessionManagerImpl(OntologyProvider<?> ontologyProvider,
ScopeRegistry scopeRegistry,
OfflineConfiguration offline,
Dictionary<String,Object> configuration) {
this();
this.ontologyProvider = ontologyProvider;
this.scopeRegistry = scopeRegistry;
this.offline = offline;
try {
activate(configuration);
} catch (IOException e) {
log.error("Unable to access servlet context.", e);
}
}
/**
* Used to configure an instance within an OSGi container.
*
* @throws IOException
*/
@SuppressWarnings("unchecked")
@Activate
protected void activate(ComponentContext context) throws IOException {
log.info("in " + SessionManagerImpl.class + " activate with context " + context);
if (context == null) {
throw new IllegalStateException("No valid" + ComponentContext.class + " parsed in activate!");
}
activate((Dictionary<String,Object>) context.getProperties());
}
/**
* Called within both OSGi and non-OSGi environments.
*
* @param configuration
* @throws IOException
*/
protected void activate(Dictionary<String,Object> configuration) throws IOException {
long before = System.currentTimeMillis();
me = this;
// Parse configuration
id = (String) configuration.get(SessionManager.ID);
if (id == null) id = _ID_DEFAULT;
String s = null;
try {
setDefaultNamespace(offline.getDefaultOntologyNetworkNamespace());
} catch (Exception e) {
log.warn("Invalid namespace {}. Setting to default value {}",
offline.getDefaultOntologyNetworkNamespace(), _ONTOLOGY_NETWORK_NS_DEFAULT);
setDefaultNamespace(IRI.create(_ONTOLOGY_NETWORK_NS_DEFAULT));
}
try {
s = (String) configuration.get(SessionManager.MAX_ACTIVE_SESSIONS);
maxSessions = Integer.parseInt(s);
} catch (Exception e) {
log.warn("Invalid session limit {}. Setting to default value {}",
configuration.get(SessionManager.MAX_ACTIVE_SESSIONS), _MAX_ACTIVE_SESSIONS_DEFAULT);
maxSessions = _MAX_ACTIVE_SESSIONS_DEFAULT;
}
if (id == null || id.isEmpty()) {
log.warn("The Ontology Network Manager configuration does not define a ID for the Ontology Network Manager");
}
idgen = new TimestampedSessionIDGenerator();
Object connectivityPolicy = configuration.get(SessionManager.CONNECTIVITY_POLICY);
if (connectivityPolicy == null) {
this.connectivityPolicyString = _CONNECTIVITY_POLICY_DEFAULT;
} else {
this.connectivityPolicyString = connectivityPolicy.toString();
}
// Add listeners
if (ontologyProvider instanceof SessionListener) this
.addSessionListener((SessionListener) ontologyProvider);
this.addSessionListener(ontologyProvider.getOntologyNetworkDescriptor());
if (scopeRegistry != null) scopeRegistry.addScopeRegistrationListener(this);
// Rebuild sessions
rebuildSessions();
log.debug(SessionManager.class + " activated. Time : {} ms.", System.currentTimeMillis() - before);
}
protected synchronized void addSession(Session session) {
sessionsByID.put(session.getID(), session);
}
@Override
public void addSessionListener(SessionListener listener) {
listeners.add(listener);
}
private void checkSessionLimit() throws SessionLimitException {
if (maxSessions >= 0 && sessionsByID.size() >= maxSessions) throw new SessionLimitException(
maxSessions, "Cannot create new session. Limit of " + maxSessions + " already raeached.");
}
@Override
public void clearSessionListeners() {
listeners.clear();
}
@Override
public Session createSession() throws SessionLimitException {
checkSessionLimit();
Set<String> exclude = getRegisteredSessionIDs();
Session session = null;
while (session == null)
try {
session = createSession(idgen.createSessionID(exclude));
} catch (DuplicateSessionIDException e) {
exclude.add(e.getDuplicateID());
continue;
}
return session;
}
@Override
public synchronized Session createSession(String sessionID) throws DuplicateSessionIDException,
SessionLimitException {
/*
* Throw the duplicate ID exception first, in case developers decide to reuse the existing session
* before creating a new one.
*/
if (sessionsByID.containsKey(sessionID)) throw new DuplicateSessionIDException(sessionID);
checkSessionLimit();
IRI ns = IRI.create(getDefaultNamespace() + getID() + "/");
Session session = new SessionImpl(sessionID, ns, ontologyProvider);
// Have the ontology provider listen to ontology events
if (ontologyProvider instanceof OntologyCollectorListener) session
.addOntologyCollectorListener((OntologyCollectorListener) ontologyProvider);
if (ontologyProvider instanceof SessionListener) session
.addSessionListener((SessionListener) ontologyProvider);
Multiplexer multiplexer = ontologyProvider.getOntologyNetworkDescriptor();
session.addOntologyCollectorListener(multiplexer);
session.addSessionListener(multiplexer);
ConnectivityPolicy policy;
try {
policy = ConnectivityPolicy.valueOf(connectivityPolicyString);
} catch (IllegalArgumentException e) {
log.warn("The value {}", connectivityPolicyString);
log.warn(" -- configured as default ConnectivityPolicy does not match any value of the Enumeration!");
log.warn(" -- Setting the default policy as defined by the {}.", ConnectivityPolicy.class);
policy = ConnectivityPolicy.valueOf(_CONNECTIVITY_POLICY_DEFAULT);
}
session.setConnectivityPolicy(policy);
addSession(session);
fireSessionCreated(session);
return session;
}
/**
* Deactivation of the ONManagerImpl resets all its resources.
*/
@Deactivate
protected void deactivate(ComponentContext context) {
id = null;
baseNS = null;
maxSessions = 0; // No sessions allowed for an inactive component.
log.info("in " + SessionManagerImpl.class + " deactivate with context " + context);
}
@Override
public synchronized void destroySession(String sessionID) {
try {
Session ses = sessionsByID.get(sessionID);
if (ses == null) log.warn(
"Tried to destroy nonexisting session {} . Could it have been previously destroyed?",
sessionID);
else {
ses.close();
if (ses instanceof SessionImpl) ((SessionImpl) ses).state = State.ZOMBIE;
// Make session no longer referenceable
removeSession(ses);
fireSessionDestroyed(ses);
}
} catch (NonReferenceableSessionException e) {
log.warn("Tried to kick a dead horse on session \"{}\" which was already in a zombie state.",
sessionID);
}
}
protected void fireSessionCreated(Session session) {
SessionEvent e = new SessionEvent(session, OperationType.CREATE);
for (SessionListener l : listeners)
l.sessionChanged(e);
}
protected void fireSessionDestroyed(Session session) {
SessionEvent e = new SessionEvent(session, OperationType.KILL);
for (SessionListener l : listeners)
l.sessionChanged(e);
}
@Override
public int getActiveSessionLimit() {
return maxSessions;
}
@Override
public IRI getDefaultNamespace() {
return baseNS;
}
@Override
public String getID() {
return id;
}
@Override
public IRI getNamespace() {
return getDefaultNamespace();
}
@Override
public Set<String> getRegisteredSessionIDs() {
return sessionsByID.keySet();
}
@Override
public Session getSession(String sessionID) {
return sessionsByID.get(sessionID);
}
@Override
public Collection<SessionListener> getSessionListeners() {
return listeners;
}
private void rebuildSessions() {
if (ontologyProvider == null) {
log.warn("No ontology provider supplied. Cannot rebuild sessions");
return;
}
OntologyNetworkConfiguration struct = ontologyProvider.getOntologyNetworkConfiguration();
for (String sessionId : struct.getSessionIDs()) {
long before = System.currentTimeMillis();
log.debug("Rebuilding session with ID \"{}\"", sessionId);
Session session;
try {
session = createSession(sessionId);
} catch (DuplicateSessionIDException e) {
log.warn("Session \"{}\" already exists and will be reused.", sessionId);
session = getSession(sessionId);
} catch (SessionLimitException e) {
log.error("Cannot create session {}. Session limit of {} reached.", sessionId,
getActiveSessionLimit());
break;
}
// Register even if some ontologies were to fail to be restored afterwards.
sessionsByID.put(sessionId, session);
session.setActive(false); // Restored sessions are inactive at first.
for (OWLOntologyID key : struct.getOntologyKeysForSession(sessionId))
try {
session.addOntology(new StoredOntologySource(key));
} catch (MissingOntologyException ex) {
log.error(
"Could not find an ontology with public key {} to be managed by session \"{}\". Proceeding to next ontology.",
key, sessionId);
continue;
} catch (Exception ex) {
log.error("Exception caught while trying to add ontology with public key " + key
+ " to rebuilt session \"" + sessionId + "\". Proceeding to next ontology.", ex);
continue;
}
for (String scopeId : struct.getAttachedScopes(sessionId)) {
/*
* The scope is attached by reference, so we won't have to bother checking if the scope has
* been rebuilt by then (which could not happen if the SessionManager is being activated
* first).
*/
session.attachScope(scopeId);
}
log.info("Session \"{}\" rebuilt in {} ms.", sessionId, System.currentTimeMillis() - before);
}
}
protected synchronized void removeSession(Session session) {
String id = session.getID();
Session s2 = sessionsByID.get(id);
if (session == s2) sessionsByID.remove(id);
}
@Override
public void removeSessionListener(SessionListener listener) {
listeners.remove(listener);
}
@Override
public void scopeActivated(Scope scope) {}
@Override
public void scopeCreated(Scope scope) {}
@Override
public void scopeDeactivated(Scope scope) {
for (String sid : getRegisteredSessionIDs())
getSession(sid).detachScope(scope.getID());
}
@Override
public void scopeRegistered(Scope scope) {}
@Override
public void scopeUnregistered(Scope scope) {
for (String sid : getRegisteredSessionIDs())
getSession(sid).detachScope(scope.getID());
}
@Override
public void setActiveSessionLimit(int limit) {
this.maxSessions = limit;
}
@Override
public void setDefaultNamespace(IRI namespace) {
if (namespace == null) throw new IllegalArgumentException("Namespace cannot be null.");
if (namespace.toURI().getQuery() != null) throw new IllegalArgumentException(
"URI Query is not allowed in OntoNet namespaces.");
if (namespace.toURI().getFragment() != null) throw new IllegalArgumentException(
"URI Fragment is not allowed in OntoNet namespaces.");
if (namespace.toString().endsWith("#")) throw new IllegalArgumentException(
"OntoNet namespaces must not end with a hash ('#') character.");
if (!namespace.toString().endsWith("/")) {
log.warn("Namespace {} does not end with slash character ('/'). It will be added automatically.",
namespace);
this.baseNS = IRI.create(namespace + "/");
return;
}
this.baseNS = namespace;
}
@Override
public void setNamespace(IRI namespace) {
setDefaultNamespace(namespace);
}
@Override
public void storeSession(String sessionID, OutputStream out) throws NonReferenceableSessionException,
OWLOntologyStorageException {
throw new UnsupportedOperationException(
"Not necessary. Session content is always stored by default in the current implementation.");
}
}