/* * $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.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Multimap; import com.google.common.collect.TreeMultimap; import com.google.inject.Injector; 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.util.Comparators; import io.janusproject.util.TwoStepConstruction; import io.sarl.lang.core.Space; import io.sarl.lang.core.SpaceID; import io.sarl.lang.core.SpaceSpecification; import io.sarl.lang.util.SynchronizedCollection; import io.sarl.util.Collections3; /** * A repository of spaces specific to a given context. * * <p>This repository is thread-safe. * * @author $Author: ngaud$ * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @TwoStepConstruction public class SpaceRepository { private static final Object[] NO_PARAMETERS = new Object[0]; private final String distributedSpaceSetName; private final Injector injector; /** * Listener used as internal implementation for this repository. */ private SpaceDMapListener internalListener; /** * Listener on the events in this repository (basically somewhere in the Context). */ private final SpaceRepositoryListener externalListener; /** * The set of the id of all spaces stored in this repository This set must be distributed and synchronized all over the * network. */ private final DMap<SpaceID, Object[]> spaceIDs; /** * Map linking a space id to its related Space object This is local non-distributed map. */ private final Map<SpaceID, Space> spaces; /** * Map linking a a class of Space specification to its related implementations' ids Use the map <code>spaces</code> to get the * Space object associated to a given id This is local non-distributed map. */ private final Multimap<Class<? extends SpaceSpecification<?>>, SpaceID> spacesBySpec; /** * @param distributedSpaceSetName - the name used to identify distributed map over network * @param distributedDataStructureService - distributed data structure service. * @param injector - injector to used for creating new spaces. * @param listener - listener on the events in the space repository. */ SpaceRepository(String distributedSpaceSetName, DistributedDataStructureService distributedDataStructureService, Injector injector, SpaceRepositoryListener listener) { this.distributedSpaceSetName = distributedSpaceSetName; this.injector = injector; this.externalListener = listener; this.spaces = new TreeMap<>(); this.spacesBySpec = TreeMultimap.create(Comparators.CLASS_COMPARATOR, Comparators.OBJECT_COMPARATOR); this.spaceIDs = distributedDataStructureService.getMap(this.distributedSpaceSetName, null); } /** * Finalize the initialization: ensure that the events are fired outside the scope of the SpaceRepository constructor. */ void postConstruction() { if (this.spaceIDs != null) { for (final Entry<SpaceID, Object[]> e : this.spaceIDs.entrySet()) { assert this.spaceIDs.containsKey(e.getKey()); ensureLocalSpaceDefinition(e.getKey(), e.getValue()); } this.internalListener = new SpaceDMapListener(); this.spaceIDs.addDMapListener(this.internalListener); } } /** Replies the mutex to be synchronized on the internal space repository. * * @return the mutex. */ private Object getSpaceRepositoryMutex() { return this.spaces; } /** Replies the mutex to be synchronized on the internal space ID repository. * * @return the mutex. */ private Object getSpaceIDsMutex() { return this.spaceIDs; } /** * Destroy this repository and releaqse all the resources. */ public void destroy() { // Unregister from Hazelcast layer. synchronized (getSpaceIDsMutex()) { if (this.internalListener != null) { this.spaceIDs.removeDMapListener(this.internalListener); } // Delete the spaces. If this function is called, it // means that the spaces seems to have no more participant. // The process cannot be done through hazelcast. final List<SpaceID> ids = new ArrayList<>(this.spaceIDs.keySet()); this.spaceIDs.clear(); for (final SpaceID spaceId : ids) { removeLocalSpaceDefinition(spaceId, true); } } } private <S extends Space> S createSpaceInstance(Class<? extends SpaceSpecification<S>> spec, SpaceID spaceID, boolean isLocalCreation, Object[] creationParams) { final S space; assert spaceID.getSpaceSpecification() == null || spaceID.getSpaceSpecification().equals(spec) : "The specification type is invalid"; //$NON-NLS-1$ // Split the call to create() to let the JVM to create the "empty" array for creation parameters. if (creationParams != null && creationParams.length > 0) { space = this.injector.getInstance(spec).create(spaceID, creationParams); } else { space = this.injector.getInstance(spec).create(spaceID); } assert space != null; final SpaceID id = space.getSpaceID(); assert id != null; this.spaces.put(id, space); this.spacesBySpec.put(id.getSpaceSpecification(), id); if (isLocalCreation) { Object[] sharedParams = NO_PARAMETERS; if (creationParams != null && creationParams.length > 0) { final List<Object> serializableParameters = new ArrayList<>(creationParams.length); for (final Object creationParameter : creationParams) { if (creationParameter instanceof Serializable) { serializableParameters.add(creationParameter); } } if (!serializableParameters.isEmpty()) { sharedParams = serializableParameters.toArray(); } } synchronized (getSpaceIDsMutex()) { this.spaceIDs.putIfAbsent(id, sharedParams); } } fireSpaceAdded(space, isLocalCreation); return space; } /** * Add the existing, but not yet known, spaces into this repository. * * @param id - identifier of the space * @param initializationParameters - parameters for initialization. */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected void ensureLocalSpaceDefinition(SpaceID id, Object[] initializationParameters) { synchronized (getSpaceRepositoryMutex()) { if (!this.spaces.containsKey(id)) { createSpaceInstance((Class) id.getSpaceSpecification(), id, false, initializationParameters); } } } /** * Remove a remote space. * * @param id - identifier of the space * @param isLocalDestruction - indicates if the destruction is initiated by the local kernel. */ protected void removeLocalSpaceDefinition(SpaceID id, boolean isLocalDestruction) { final Space space; synchronized (getSpaceRepositoryMutex()) { space = this.spaces.remove(id); if (space != null) { this.spacesBySpec.remove(id.getSpaceSpecification(), id); } } if (space != null) { fireSpaceRemoved(space, isLocalDestruction); } } /** * Remove all the remote spaces. * * @param isLocalDestruction - indicates if the destruction is initiated by the local kernel. */ protected void removeLocalSpaceDefinitions(boolean isLocalDestruction) { List<Space> removedSpaces = null; synchronized (getSpaceRepositoryMutex()) { if (!this.spaces.isEmpty()) { removedSpaces = new ArrayList<>(this.spaces.size()); final Iterator<Entry<SpaceID, Space>> iterator = this.spaces.entrySet().iterator(); Space space; SpaceID id; while (iterator.hasNext()) { final Entry<SpaceID, Space> entry = iterator.next(); id = entry.getKey(); space = entry.getValue(); iterator.remove(); this.spacesBySpec.remove(id.getSpaceSpecification(), id); removedSpaces.add(space); } } } if (removedSpaces != null) { for (final Space s : removedSpaces) { fireSpaceRemoved(s, isLocalDestruction); } } } /** * Create a space. * * @param <S> - the type of the space to reply. * @param spaceID - ID of the space. * @param spec - specification of the space. * @param creationParams - creation parameters. * @return the new space, or <code>null</code> if the space already exists. */ public <S extends io.sarl.lang.core.Space> S createSpace(SpaceID spaceID, Class<? extends SpaceSpecification<S>> spec, Object... creationParams) { synchronized (getSpaceRepositoryMutex()) { if (!this.spaces.containsKey(spaceID)) { return createSpaceInstance(spec, spaceID, true, creationParams); } } return null; } /** * Retrieve the first space of the given specification, or create a space if none. * * @param <S> - the type of the space to reply. * @param spaceID - ID of the space (used only when creating a space). * @param spec - specification of the space. * @param creationParams - creation parameters (used only when creating a space). * @return the new space. */ @SuppressWarnings("unchecked") public <S extends io.sarl.lang.core.Space> S getOrCreateSpaceWithSpec(SpaceID spaceID, Class<? extends SpaceSpecification<S>> spec, Object... creationParams) { synchronized (getSpaceRepositoryMutex()) { final Collection<SpaceID> ispaces = this.spacesBySpec.get(spec); final S firstSpace; if (ispaces == null || ispaces.isEmpty()) { firstSpace = createSpaceInstance(spec, spaceID, true, creationParams); } else { firstSpace = (S) this.spaces.get(ispaces.iterator().next()); } assert firstSpace != null; return firstSpace; } } /** * Retrieve the first space of the given identifier, or create a space if none. * * @param <S> - the type of the space to reply. * @param spaceID - ID of the space. * @param spec - specification of the space. * @param creationParams - creation parameters (used only when creating a space). * @return the new space. */ @SuppressWarnings("unchecked") public <S extends io.sarl.lang.core.Space> S getOrCreateSpaceWithID(SpaceID spaceID, Class<? extends SpaceSpecification<S>> spec, Object... creationParams) { synchronized (getSpaceRepositoryMutex()) { Space space = this.spaces.get(spaceID); if (space == null) { space = createSpaceInstance(spec, spaceID, true, creationParams); } assert space != null; return (S) space; } } /** * Returns the collection of all spaces stored in this repository. * * @return the collection of all spaces stored in this repository. */ public SynchronizedCollection<? extends Space> getSpaces() { synchronized (getSpaceRepositoryMutex()) { return Collections3.synchronizedCollection(Collections.unmodifiableCollection(this.spaces.values()), getSpaceRepositoryMutex()); } } /** * Returns the collection of all spaces with the specified {@link SpaceSpecification} stored in this repository. * * @param <S> - type of the spaces to reply. * @param spec - the specification used to filter the set of stored spaces. * @return the collection of all spaces with the specified {@link SpaceSpecification} stored in this repository */ @SuppressWarnings("unchecked") public <S extends Space> SynchronizedCollection<S> getSpaces(final Class<? extends SpaceSpecification<S>> spec) { synchronized (getSpaceRepositoryMutex()) { return Collections3 .synchronizedCollection((Collection<S>) Collections2.filter(this.spaces.values(), new Predicate<Space>() { @Override public boolean apply(Space input) { return input.getSpaceID().getSpaceSpecification().equals(spec); } }), getSpaceRepositoryMutex()); } } /** * Returns the first instance of a space with the specified SpaceID. * * @param spaceID - the identifier to retreive. * @return the space instance of <code>null</code> if none. */ public Space getSpace(SpaceID spaceID) { synchronized (getSpaceRepositoryMutex()) { return this.spaces.get(spaceID); } } /** * Notifies the listeners on the space creation. * * @param space - the created space. * @param isLocalCreation - indicates if the creation of the space was initiated on the current kernel. */ protected void fireSpaceAdded(Space space, boolean isLocalCreation) { if (this.externalListener != null) { this.externalListener.spaceCreated(space, isLocalCreation); } } /** * Notifies the listeners on the space destruction. * * @param space - the removed space. * @param isLocalDestruction - indicates if the destruction of the space was initiated on the current kernel. */ protected void fireSpaceRemoved(Space space, boolean isLocalDestruction) { if (this.externalListener != null) { this.externalListener.spaceDestroyed(space, isLocalDestruction); } } /** * Listener on events related to the space service. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class SpaceDMapListener implements DMapListener<SpaceID, Object[]> { /** * Construct. */ SpaceDMapListener() { // } @SuppressWarnings("synthetic-access") @Override public void entryAdded(SpaceID key, Object[] value) { assert SpaceRepository.this.spaceIDs.containsKey(key); ensureLocalSpaceDefinition(key, value); } @SuppressWarnings("synthetic-access") @Override public void entryRemoved(SpaceID key, Object[] value) { assert !SpaceRepository.this.spaceIDs.containsKey(key); removeLocalSpaceDefinition(key, false); } @Override public void entryUpdated(SpaceID key, Object[] value) { // } @Override public void mapCleared(boolean localClearing) { removeLocalSpaceDefinitions(false); } } }