blob: 56a747584cea97443b2309e0b81df76ce78b1212 [file] [log] [blame]
/*
* 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.vysper.xmpp.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import org.apache.vysper.event.EventBus;
import org.apache.vysper.event.SimpleEventBus;
import org.apache.vysper.storage.OpenStorageProviderRegistry;
import org.apache.vysper.storage.StorageProvider;
import org.apache.vysper.storage.StorageProviderRegistry;
import org.apache.vysper.xmpp.addressing.Entity;
import org.apache.vysper.xmpp.addressing.EntityUtils;
import org.apache.vysper.xmpp.authentication.UserAuthentication;
import org.apache.vysper.xmpp.cryptography.TLSContextFactory;
import org.apache.vysper.xmpp.delivery.StanzaRelay;
import org.apache.vysper.xmpp.modules.Module;
import org.apache.vysper.xmpp.modules.ModuleRegistry;
import org.apache.vysper.xmpp.modules.ServerRuntimeContextService;
import org.apache.vysper.xmpp.protocol.HandlerDictionary;
import org.apache.vysper.xmpp.protocol.ProtocolWorker;
import org.apache.vysper.xmpp.protocol.StanzaHandler;
import org.apache.vysper.xmpp.protocol.StanzaHandlerLookup;
import org.apache.vysper.xmpp.protocol.StanzaProcessor;
import org.apache.vysper.xmpp.server.components.Component;
import org.apache.vysper.xmpp.server.s2s.DefaultXMPPServerConnectorRegistry;
import org.apache.vysper.xmpp.server.s2s.XMPPServerConnectorRegistry;
import org.apache.vysper.xmpp.stanza.Stanza;
import org.apache.vysper.xmpp.state.presence.LatestPresenceCache;
import org.apache.vysper.xmpp.state.presence.SimplePresenceCache;
import org.apache.vysper.xmpp.state.resourcebinding.DefaultResourceRegistry;
import org.apache.vysper.xmpp.state.resourcebinding.ResourceRegistry;
import org.apache.vysper.xmpp.uuid.JVMBuiltinUUIDGenerator;
import org.apache.vysper.xmpp.uuid.UUIDGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class DefaultServerRuntimeContext implements ServerRuntimeContext, ModuleRegistry {
private final Logger logger = LoggerFactory.getLogger(DefaultServerRuntimeContext.class);
// basic internal data structures and configuration...
/**
* directory where all available processors for incoming stanzas are located
*/
private StanzaHandlerLookup stanzaHandlerLookup;
/**
* the 'domain' the server is directly serving for
*/
private Entity serverEntity;
/**
* feature configuration
*/
private ServerFeatures serverFeatures = new ServerFeatures();
/**
* the Secure Socket Engine in use
*/
private SSLContext sslContext = null;
/**
* generates unique session ids
*/
private UUIDGenerator sessionIdGenerator = new JVMBuiltinUUIDGenerator();
// basic services the server is using...
/**
* 'input stream': receives stanzas issued by client sessions to be handled by the server
*/
private StanzaProcessor stanzaProcessor = new ProtocolWorker();
/**
* 'output stream': receives stanzas issued by a session, which are going to other sessions/servers
*/
private StanzaRelay stanzaRelay;
/**
* administrate and query resources and sessions
*/
private ResourceRegistry resourceRegistry;
/**
* holds the latest presence stanza for a resource
*/
private LatestPresenceCache presenceCache = new SimplePresenceCache();
private XMPPServerConnectorRegistry serverConnectorRegistry = new DefaultXMPPServerConnectorRegistry(this);
/**
* holds the storage services
*/
private StorageProviderRegistry storageProviderRegistry = new OpenStorageProviderRegistry();
/**
* collection of all other services, which are mostly add-ons to the minimal setup
*/
private final Map<String, ServerRuntimeContextService> serverRuntimeContextServiceMap = new HashMap<String, ServerRuntimeContextService>();
private List<Module> modules = new ArrayList<Module>();
/**
* map of all registered components, index by the subdomain they are registered for
*/
protected final Map<String, Component> componentMap = new HashMap<String, Component>();
private final SimpleEventBus eventBus;
public DefaultServerRuntimeContext(Entity serverEntity, StanzaRelay stanzaRelay) {
this.serverEntity = serverEntity;
this.stanzaRelay = stanzaRelay;
this.resourceRegistry = new DefaultResourceRegistry();
this.stanzaHandlerLookup = new StanzaHandlerLookup(this);
this.eventBus = new SimpleEventBus();
}
public DefaultServerRuntimeContext(Entity serverEntity, StanzaRelay stanzaRelay,
StorageProviderRegistry storageProviderRegistry) {
this(serverEntity, stanzaRelay);
this.storageProviderRegistry = storageProviderRegistry;
}
public DefaultServerRuntimeContext(Entity serverEntity, StanzaRelay stanzaRelay, ServerFeatures serverFeatures,
List<HandlerDictionary> dictionaries, ResourceRegistry resourceRegistry) {
this(serverEntity, stanzaRelay);
this.serverFeatures = serverFeatures;
this.resourceRegistry = resourceRegistry;
addDictionaries(dictionaries);
}
/**
* change the presence cache implementation. this is a setter intended to be used at
* initialisation time. (thus, this method is not part of ServerRuntimeContext.
*
* @param presenceCache
*/
public void setPresenceCache(LatestPresenceCache presenceCache) {
this.presenceCache = presenceCache;
}
public StanzaHandler getHandler(Stanza stanza) {
return stanzaHandlerLookup.getHandler(stanza);
}
public String getNextSessionId() {
return sessionIdGenerator.create();
}
public Entity getServerEnitity() {
return serverEntity;
}
public String getDefaultXMLLang() {
return "en_US"; // TODO must be configurable as of RFC3920
}
public StanzaProcessor getStanzaProcessor() {
return stanzaProcessor;
}
public StanzaRelay getStanzaRelay() {
return stanzaRelay;
}
public ServerFeatures getServerFeatures() {
return serverFeatures;
}
public XMPPServerConnectorRegistry getServerConnectorRegistry() {
return serverConnectorRegistry;
}
public void addDictionary(HandlerDictionary namespaceHandlerDictionary) {
stanzaHandlerLookup.addDictionary(namespaceHandlerDictionary);
}
protected void addDictionaries(List<HandlerDictionary> dictionaries) {
for (HandlerDictionary dictionary : dictionaries) {
addDictionary(dictionary);
}
}
public void setTlsContextFactory(TLSContextFactory tlsContextFactory) {
try {
sslContext = tlsContextFactory.getSSLContext();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public SSLContext getSslContext() {
return sslContext;
}
/**
* @deprecated use {@link #getStorageProvider(Class)} with {@link UserAuthentication}.class instead
*/
public UserAuthentication getUserAuthentication() {
return (UserAuthentication) storageProviderRegistry.retrieve(UserAuthentication.class);
}
public ResourceRegistry getResourceRegistry() {
return resourceRegistry;
}
public LatestPresenceCache getPresenceCache() {
return presenceCache;
}
/**
* add a runtime service. makes the service dynamically discoverable at runtime.
* @param service
*/
public void registerServerRuntimeContextService(ServerRuntimeContextService service) {
if (service == null)
throw new IllegalStateException("service must not be null");
if (serverRuntimeContextServiceMap.get(service.getServiceName()) != null) {
throw new IllegalStateException("service already registered: " + service.getServiceName());
}
serverRuntimeContextServiceMap.put(service.getServiceName(), service);
}
/**
* retrieves a previously registered runtime context service. The RosterManager is a good example of such a service.
* This allows for modules, extensions and other services to discover their dependencies at runtime.
*
* @see org.apache.vysper.xmpp.server.DefaultServerRuntimeContext#getStorageProvider(Class)
* @param name
* @return
*/
@Override
public ServerRuntimeContextService getServerRuntimeContextService(String name) {
return serverRuntimeContextServiceMap.get(name);
}
/**
* adds a whole set of storage providers at once to the system.
* @param storageProviderRegistry
*/
public void setStorageProviderRegistry(StorageProviderRegistry storageProviderRegistry) {
logger.info("replacing the storage provider registry with "
+ storageProviderRegistry.getClass().getCanonicalName());
this.storageProviderRegistry = storageProviderRegistry;
}
/**
* retrieves a particular storage provider.
* @param clazz
* @return
*/
public <T extends StorageProvider> T getStorageProvider(Class<T> clazz) {
return storageProviderRegistry.retrieve(clazz);
}
/**
* adds and initializes a list of Modules. A module extends the server's functionality by adding an
* XMPP extension ('XEP') to it. (More) Modules can be added at runtime.
* This approach has an advantage over adding modules one by one, in that it allows for a better
* dependency management: all modules from the list to first discover each other before initialize()
* get's called for every one of them.
*
* @see org.apache.vysper.xmpp.server.DefaultServerRuntimeContext#registerServerRuntimeContextService(org.apache.vysper.xmpp.modules.ServerRuntimeContextService)
* @see org.apache.vysper.xmpp.server.DefaultServerRuntimeContext#getServerRuntimeContextService(String)
* @see org.apache.vysper.xmpp.modules.Module
* @param modules List of modules
*/
public void addModules(List<Module> modules) {
for (Module module : modules) {
addModuleInternal(module);
}
for (Module module : modules) {
module.initialize(this);
}
}
/**
* adds and initializes a single Module. a module extends the server's functionality by adding an
* XMPP extension ('XEP') to it.
* @see org.apache.vysper.xmpp.modules.Module
* @see DefaultServerRuntimeContext#addModules(java.util.List) for adding a number of modules at once
* @param module
*/
public void addModule(Module module) {
addModuleInternal(module);
module.initialize(this);
}
protected void addModuleInternal(Module module) {
logger.info("adding module... {} ({})", module.getName(), module.getVersion());
List<ServerRuntimeContextService> serviceList = module.getServerServices();
if (serviceList != null) {
for (ServerRuntimeContextService serverRuntimeContextService : serviceList) {
registerServerRuntimeContextService(serverRuntimeContextService);
// if a storage service, also register there
if (serverRuntimeContextService instanceof StorageProvider) {
StorageProvider storageProvider = (StorageProvider) serverRuntimeContextService;
storageProviderRegistry.add(storageProvider);
}
}
}
List<HandlerDictionary> handlerDictionaryList = module.getHandlerDictionaries();
if (handlerDictionaryList != null) {
for (HandlerDictionary handlerDictionary : handlerDictionaryList) {
addDictionary(handlerDictionary);
}
}
module.getEventListenerDictionary().ifPresent(eventBus::addDictionary);
if (module instanceof Component) {
registerComponent((Component) module);
}
modules.add(module);
}
public List<Module> getModules() {
return Collections.unmodifiableList(modules);
}
public <T> T getModule(Class<T> clazz) {
for(Module module : modules) {
if(module.getClass().equals(clazz)) return (T) module;
}
return null;
}
@Override
public EventBus getEventBus() {
return eventBus;
}
public void registerComponent(Component component) {
componentMap.put(component.getSubdomain(), component);
}
public StanzaProcessor getComponentStanzaProcessor(Entity entity) {
String serverDomain = getServerEnitity().getDomain();
if (!EntityUtils.isAddressingServerComponent(entity, getServerEnitity())) {
return null;
}
String domain = entity.getDomain();
String subdomain = domain.replace("." + serverDomain, "");
Component component = componentMap.get(subdomain);
if (component == null)
return null;
return component.getStanzaProcessor();
}
}