/* * Copyright 2015 the original author 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.atomix.manager.internal; import io.atomix.copycat.server.Commit; import io.atomix.copycat.server.Snapshottable; import io.atomix.copycat.server.StateMachine; import io.atomix.copycat.server.StateMachineExecutor; import io.atomix.copycat.server.session.ServerSession; import io.atomix.copycat.server.session.SessionListener; import io.atomix.copycat.server.storage.snapshot.SnapshotReader; import io.atomix.copycat.server.storage.snapshot.SnapshotWriter; import io.atomix.manager.ResourceManagerException; import io.atomix.manager.resource.internal.InstanceOperation; import io.atomix.resource.Resource; import io.atomix.resource.ResourceStateMachine; import io.atomix.resource.ResourceType; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** * Resource manager. * * @author <a href="http://github.com/kuujo">Jordan Halterman</a> */ public class ResourceManagerState extends StateMachine implements SessionListener, Snapshottable { private StateMachineExecutor executor; private final Map<String, Long> keys = new HashMap<>(); private final Map<Long, ResourceHolder> resources = new HashMap<>(); private final ResourceManagerCommitPool commits = new ResourceManagerCommitPool(); @Override public void configure(StateMachineExecutor executor) { this.executor = executor; executor.register(InstanceOperation.class, (Function<Commit<InstanceOperation>, Object>) this::operateResource); executor.register(GetResource.class, this::getResource); executor.register(GetResourceIfExists.class, this::getResourceIfExists); executor.register(CloseResource.class, this::closeResource); executor.register(DeleteResource.class, this::deleteResource); executor.register(ResourceExists.class, this::resourceExists); executor.register(GetResourceKeys.class, this::getResourceKeys); } @Override public void snapshot(SnapshotWriter writer) { List<ResourceHolder> resources = new ArrayList<>(this.resources.values()); Collections.sort(resources, (r1, r2) -> (int) (r1.id - r2.id)); for (ResourceHolder resource : resources) { if (resource.stateMachine instanceof Snapshottable) { ((Snapshottable) resource.stateMachine).snapshot(writer); } } } @Override public void install(SnapshotReader reader) { List<ResourceHolder> resources = new ArrayList<>(this.resources.values()); Collections.sort(resources, (r1, r2) -> (int)(r1.id - r2.id)); for (ResourceHolder resource : resources) { if (resource.stateMachine instanceof Snapshottable) { ((Snapshottable) resource.stateMachine).install(reader); } } } /** * Performs an operation on a resource. */ @SuppressWarnings("unchecked") private Object operateResource(Commit<InstanceOperation> commit) { long resourceId = commit.operation().resource(); ResourceHolder resource = resources.get(resourceId); if (resource == null) { commit.close(); throw new ResourceManagerException("unknown resource: " + resourceId); } // If the session exists for the resource, use the existing session. ManagedResourceSession resourceSession = resource.executor.context.sessions.session(commit.session().id()); // If the session is not open for this resource, add the commit session to the resource. if (resourceSession == null) { resourceSession = new ManagedResourceSession(resourceId, commit, commit.session()); } // Execute the operation. return resource.executor.execute(commits.acquire(commit, resourceSession)); } /** * Gets a resource. */ protected long getResource(Commit<? extends GetResource> commit) { String key = commit.operation().key(); ResourceType type = commit.operation().type(); // Lookup the resource ID for the resource key. Long resourceId = keys.get(key); // If no resource ID was found, create the resource. if (resourceId == null) { // The first time a resource is created, the resource ID is the index of the commit that created it. resourceId = commit.index(); keys.put(key, resourceId); try { // For the new resource, construct a state machine and store the resource info. ResourceStateMachine stateMachine = type.factory().newInstance().createStateMachine(new Resource.Config(commit.operation().config())); ResourceManagerStateMachineExecutor executor = new ResourceManagerStateMachineExecutor(resourceId, this.executor); // Store the resource to be referenced by its resource ID. ResourceHolder resource = new ResourceHolder(resourceId, key, type, commit, stateMachine, executor); resources.put(resourceId, resource); // Initialize the resource state machine. stateMachine.init(executor); // Create a resource session for the client resource instance. ManagedResourceSession resourceSession = new ManagedResourceSession(resourceId, commit, commit.session()); resource.executor.context.sessions.register(resourceSession); // Returns the session ID for the resource client session. return resourceId; } catch (InstantiationException | IllegalAccessException e) { throw new ResourceManagerException("failed to instantiate state machine", e); } } else { // If a resource was found, validate that the resource type matches. ResourceHolder resource = resources.get(resourceId); if (resource == null || !resource.type.equals(type)) { throw new ResourceManagerException("inconsistent resource type: " + commit.operation().type()); } // Create a resource session for the client resource instance. ManagedResourceSession resourceSession = new ManagedResourceSession(resourceId, commit, commit.session()); resource.executor.context.sessions.register(resourceSession); return resourceId; } } /** * Applies a get resource if exists commit. */ @SuppressWarnings("unchecked") private long getResourceIfExists(Commit<GetResourceIfExists> commit) { String key = commit.operation().key(); // Lookup the resource ID for the resource key. Long resourceId = keys.get(key); if (resourceId != null) { return getResource(commit); } return 0; } /** * Checks if a resource exists. */ protected boolean resourceExists(Commit<ResourceExists> commit) { try { return keys.containsKey(commit.operation().key()); } finally { commit.close(); } } /** * Closes a resource. */ protected void closeResource(Commit<CloseResource> commit) { try { long resourceId = commit.operation().resource(); ResourceHolder resource = resources.get(resourceId); if (resource == null) { throw new ResourceManagerException("unknown resource: " + resourceId); } resource.executor.context.sessions.unregister(commit.session().id()); resource.executor.context.sessions.close(commit.session().id()); } finally { commit.close(); } } /** * Applies a delete resource commit. */ protected boolean deleteResource(Commit<DeleteResource> commit) { try { ResourceHolder resource = resources.remove(commit.operation().resource()); if (resource == null) { throw new ResourceManagerException("unknown resource: " + commit.operation().resource()); } // Delete the resource state machine and close the resource state machine executor. resource.stateMachine.delete(); resource.executor.close(); resource.commit.close(); keys.remove(resource.key); return true; } finally { commit.close(); } } /** * Handles get resource keys commit. */ protected Set<String> getResourceKeys(Commit<GetResourceKeys> commit) { try { if (commit.operation().type() == 0) { return new HashSet<>(keys.keySet()); } return new HashSet<>(resources.entrySet() .stream() .filter(e -> e.getValue().type.id() == commit.operation().type()) .map(e -> e.getValue().key) .collect(Collectors.toSet())); } finally { commit.close(); } } @Override public void register(ServerSession session) { } @Override public void expire(ServerSession session) { for (ResourceHolder resource : resources.values()) { resource.executor.context.sessions.expire(session.id()); } } @Override public void unregister(ServerSession session) { for (ResourceHolder resource : resources.values()) { resource.executor.context.sessions.unregister(session.id()); } } @Override public void close(ServerSession session) { for (ResourceHolder resource : resources.values()) { resource.executor.context.sessions.close(session.id()); } } /** * Resource holder. */ private static class ResourceHolder { private final long id; private final String key; private final ResourceType type; private final Commit<? extends GetResource> commit; private final ResourceStateMachine stateMachine; private final ResourceManagerStateMachineExecutor executor; private ResourceHolder(long id, String key, ResourceType type, Commit<? extends GetResource> commit, ResourceStateMachine stateMachine, ResourceManagerStateMachineExecutor executor) { this.id = id; this.key = key; this.type = type; this.commit = commit; this.stateMachine = stateMachine; this.executor = executor; } } }