/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* 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 io.janusproject.kernel.services.jdk.contextspace;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.util.concurrent.Service;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import io.janusproject.JanusConfig;
import io.janusproject.services.AbstractDependentService;
import io.janusproject.services.contextspace.ContextRepositoryListener;
import io.janusproject.services.contextspace.ContextSpaceService;
import io.janusproject.services.contextspace.SpaceRepositoryListener;
import io.janusproject.services.distributeddata.DMap;
import io.janusproject.services.distributeddata.DMapListener;
import io.janusproject.services.distributeddata.DistributedDataStructureService;
import io.janusproject.services.kerneldiscovery.KernelDiscoveryService;
import io.janusproject.services.logging.LogService;
import io.janusproject.services.network.NetworkService;
import io.janusproject.util.ListenerCollection;
import io.janusproject.util.TwoStepConstruction;
import io.sarl.lang.core.AgentContext;
import io.sarl.lang.core.Space;
import io.sarl.lang.core.SpaceID;
import io.sarl.util.Collections3;
/**
* A repository of Agent's context and spaces that is based on the other Janus platform services.
*
* @author $Author: ngaud$
* @author $Author: srodriguez$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@Singleton
@TwoStepConstruction
public class StandardContextSpaceService extends AbstractDependentService implements ContextSpaceService {
private final ListenerCollection<?> listeners = new ListenerCollection<>();
/**
* Factory of contexts.
*/
private ContextFactory contextFactory;
/**
* Factory of space repositories.
*/
private SpaceRepositoryFactory spaceRepositoryFactory;
/**
* Map linking a context id to its associated default space id. This map must be distributed and synchronized all over the
* network
*/
private DMap<UUID, SpaceID> defaultSpaces;
/**
* Map linking a context id to its related Context object This is local non-distributed map.
*/
private Map<UUID, AgentContext> contexts = new TreeMap<>();
/**
* Internal listener on the space repository changes.
*/
private ContextDMapListener dmapListener;
/**
* Log service.
*/
private LogService logger;
/**
* Constructs <code>ContextRepository</code>.
*/
public StandardContextSpaceService() {
//
}
@Override
public final Class<? extends Service> getServiceType() {
return ContextSpaceService.class;
}
@Override
public Collection<java.lang.Class<? extends Service>> getServiceDependencies() {
return Arrays.<Class<? extends Service>>asList(DistributedDataStructureService.class, NetworkService.class,
KernelDiscoveryService.class);
}
/**
* Replies the factory used to create contexts.
*
* @return the context factory.
*/
ContextFactory getContextFactory() {
return this.contextFactory;
}
/**
* Change the factory used to create contexts.
*
* @param factory - the context factory.
*/
void setContextFactory(ContextFactory factory) {
if (factory != null) {
this.contextFactory = factory;
}
}
/**
* Replies the factory used to create space repositories.
*
* @return the context factory.
*/
SpaceRepositoryFactory getSpaceRepositoryFactory() {
return this.spaceRepositoryFactory;
}
/**
* Change the factory used to create space repositories.
*
* @param factory - the space repository factory.
*/
void setSpaceRepositoryFactory(SpaceRepositoryFactory factory) {
if (factory != null) {
this.spaceRepositoryFactory = factory;
}
}
@Override
public final Object mutex() {
return this;
}
/**
* Initialize this service with injected objects.
*
* @param janusID - injected identifier.
* @param dataStructureService - service that permits to obtain distributed data structure.
* @param logService - service of logging.
* @param injector - the injector to use.
*/
@Inject
void postConstruction(@Named(JanusConfig.DEFAULT_CONTEXT_ID_NAME) UUID janusID,
DistributedDataStructureService dataStructureService, LogService logService, Injector injector) {
this.logger = logService;
this.contextFactory = new DefaultContextFactory();
this.spaceRepositoryFactory = new Context.DefaultSpaceRepositoryFactory(injector, dataStructureService, logService);
this.defaultSpaces = dataStructureService.getMap(janusID.toString(), null);
}
@Override
public boolean isEmptyContextRepository() {
synchronized (mutex()) {
return this.contexts.isEmpty();
}
}
@Override
public int getNumberOfContexts() {
synchronized (mutex()) {
return this.contexts.size();
}
}
@Override
public boolean containsContext(UUID contextID) {
synchronized (mutex()) {
return this.contexts.containsKey(contextID);
}
}
@Override
public synchronized AgentContext createContext(UUID contextID, UUID defaultSpaceUUID) {
assert contextID != null : "The contextID cannot be null"; //$NON-NLS-1$
assert defaultSpaceUUID != null : "The defaultSpaceUUID cannot be null"; //$NON-NLS-1$
assert this.contexts != null : "Internal Error: the context container must not be null"; //$NON-NLS-1$
final AgentContext context = this.contexts.get(contextID);
if (context == null) {
final Context ctx = this.contextFactory.newInstance(contextID, defaultSpaceUUID, this.spaceRepositoryFactory,
new SpaceEventProxy());
assert ctx != null : "The internal Context cannot be null"; //$NON-NLS-1$
assert this.contexts != null : "Internal Error: the context container must not be null"; //$NON-NLS-1$
this.contexts.put(contextID, ctx);
fireContextCreated(ctx);
final Space defaultSpace = ctx.postConstruction();
assert defaultSpace != null : "The default space in the context " //$NON-NLS-1$
+ contextID + " cannot be null"; //$NON-NLS-1$
this.defaultSpaces.putIfAbsent(ctx.getID(), defaultSpace.getSpaceID());
return ctx;
}
return context;
}
@Override
public final void removeContext(AgentContext context) {
removeContext(context.getID());
}
@Override
public void removeContext(UUID contextID) {
AgentContext context = null;
synchronized (mutex()) {
this.defaultSpaces.remove(contextID);
context = this.contexts.remove(contextID);
if (context != null) {
((Context) context).destroy();
}
}
if (context != null) {
fireContextDestroyed(context);
}
}
@Override
public Collection<AgentContext> getContexts() {
synchronized (mutex()) {
return Collections.unmodifiableCollection(Collections3.synchronizedCollection(this.contexts.values(), mutex()));
}
}
@Override
public Collection<AgentContext> getContexts(final Collection<UUID> contextIDs) {
synchronized (mutex()) {
return Collections2.filter(this.contexts.values(), new Predicate<AgentContext>() {
@Override
public boolean apply(AgentContext input) {
return contextIDs.contains(input.getID());
}
});
}
}
@Override
public Set<UUID> getContextIDs() {
synchronized (mutex()) {
return Collections.unmodifiableSet(Collections3.synchronizedSet(this.contexts.keySet(), mutex()));
}
}
@Override
public AgentContext getContext(UUID contextID) {
synchronized (mutex()) {
return this.contexts.get(contextID);
}
}
@Override
public void addContextRepositoryListener(ContextRepositoryListener listener) {
this.listeners.add(ContextRepositoryListener.class, listener);
}
@Override
public void removeContextRepositoryListener(ContextRepositoryListener listener) {
this.listeners.remove(ContextRepositoryListener.class, listener);
}
/**
* Notifies the listeners about a context creation.
*
* @param context - reference to the created context.
*/
protected void fireContextCreated(AgentContext context) {
final ContextRepositoryListener[] ilisteners = this.listeners.getListeners(ContextRepositoryListener.class);
this.logger.info(Messages.StandardContextSpaceService_0, context.getID());
for (final ContextRepositoryListener listener : ilisteners) {
listener.contextCreated(context);
}
}
/**
* Notifies the listeners about a context destruction.
*
* @param context - reference to the destroyed context.
*/
protected void fireContextDestroyed(AgentContext context) {
final ContextRepositoryListener[] ilisteners = this.listeners.getListeners(ContextRepositoryListener.class);
this.logger.info(Messages.StandardContextSpaceService_1, context.getID());
for (final ContextRepositoryListener listener : ilisteners) {
listener.contextDestroyed(context);
}
}
@Override
public void addSpaceRepositoryListener(SpaceRepositoryListener listener) {
this.listeners.add(SpaceRepositoryListener.class, listener);
}
@Override
public void removeSpaceRepositoryListener(SpaceRepositoryListener listener) {
this.listeners.remove(SpaceRepositoryListener.class, listener);
}
/**
* Notifies the listeners on the space creation.
*
* @param space - reference to the created space.
* @param isLocalCreation - indicates if the space was initially created on the current kernel.
*/
protected void fireSpaceCreated(Space space, boolean isLocalCreation) {
for (final SpaceRepositoryListener listener : this.listeners.getListeners(SpaceRepositoryListener.class)) {
listener.spaceCreated(space, isLocalCreation);
}
}
/**
* Notifies the listeners on the space destruction.
*
* @param space - reference to the destroyed space.
* @param isLocalDestruction - indicates if the space was destroyed in the current kernel.
*/
protected void fireSpaceDestroyed(Space space, boolean isLocalDestruction) {
for (final SpaceRepositoryListener listener : this.listeners.getListeners(SpaceRepositoryListener.class)) {
listener.spaceDestroyed(space, isLocalDestruction);
}
}
/**
* Update the internal data structure when a default space was discovered.
*
* @param spaceID - identifier of the space to initialize.
*/
protected void ensureDefaultSpaceDefinition(SpaceID spaceID) {
createContext(spaceID.getContextID(), spaceID.getID());
}
/**
* Update the internal data structure when a default space was removed.
*
* @param spaceID - identifier of the space to remove.
*/
protected void removeDefaultSpaceDefinition(SpaceID spaceID) {
AgentContext context = null;
synchronized (mutex()) {
context = this.contexts.remove(spaceID.getContextID());
}
if (context != null) {
fireContextDestroyed(context);
}
}
@Override
protected void doStart() {
synchronized (mutex()) {
for (final SpaceID space : this.defaultSpaces.values()) {
ensureDefaultSpaceDefinition(space);
}
this.dmapListener = new ContextDMapListener();
this.defaultSpaces.addDMapListener(this.dmapListener);
}
notifyStarted();
}
@Override
protected void doStop() {
final Map<UUID, AgentContext> old;
synchronized (mutex()) {
if (this.dmapListener != null) {
this.defaultSpaces.removeDMapListener(this.dmapListener);
}
// Unconnect the default space collection from remote clusters
// Not needed becasue the Kernel will be stopped: this.defaultSpaces.destroy();
// Delete the contexts from this repository
old = this.contexts;
this.contexts = new TreeMap<>();
}
for (final AgentContext context : old.values()) {
((Context) context).destroy();
fireContextDestroyed(context);
}
notifyStopped();
}
/**
* Listener on map events from Hazelcast.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class ContextDMapListener implements DMapListener<UUID, SpaceID> {
/**
* Construct.
*/
ContextDMapListener() {
//
}
@Override
public void entryAdded(UUID key, SpaceID value) {
assert value != null;
ensureDefaultSpaceDefinition(value);
}
@Override
public void entryRemoved(UUID key, SpaceID value) {
assert value != null;
removeDefaultSpaceDefinition(value);
}
@Override
public void entryUpdated(UUID key, SpaceID value) {
//
}
@Override
public void mapCleared(boolean localClearing) {
throw new UnsupportedOperationException();
}
}
/**
* Proxy for space events.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class SpaceEventProxy implements SpaceRepositoryListener {
/**
* Construct.
*/
SpaceEventProxy() {
//
}
@Override
public void spaceCreated(Space space, boolean isLocalCreation) {
fireSpaceCreated(space, isLocalCreation);
}
@Override
public void spaceDestroyed(Space space, boolean isLocalDestruction) {
fireSpaceDestroyed(space, isLocalDestruction);
}
}
/**
* Factory of contexts.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class DefaultContextFactory implements ContextFactory {
/**
* Create an instance of context.
*/
DefaultContextFactory() {
//
}
@Override
public Context newInstance(UUID contextId, UUID defaultSpaceId, SpaceRepositoryFactory factory,
SpaceRepositoryListener listener) {
return new Context(contextId, defaultSpaceId, factory, listener);
}
}
}