Java tutorial
/* * Copyright 2014 Cask Data, Inc. * * 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 co.cask.cdap.internal.app.runtime.service; import co.cask.cdap.api.service.ServiceSpecification; import co.cask.cdap.app.ApplicationSpecification; import co.cask.cdap.app.program.Program; import co.cask.cdap.app.runtime.Arguments; import co.cask.cdap.app.runtime.ProgramController; import co.cask.cdap.app.runtime.ProgramOptions; import co.cask.cdap.app.runtime.ProgramRunner; import co.cask.cdap.internal.app.runtime.AbstractProgramController; import co.cask.cdap.internal.app.runtime.BasicArguments; import co.cask.cdap.internal.app.runtime.ProgramOptionConstants; import co.cask.cdap.internal.app.runtime.ProgramRunnerFactory; import co.cask.cdap.internal.app.runtime.SimpleProgramOptions; import co.cask.cdap.proto.ProgramType; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Table; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Inject; import org.apache.twill.api.RunId; import org.apache.twill.api.RuntimeSpecification; import org.apache.twill.internal.RunIds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * For Running Services in standalone mode. */ public class InMemoryServiceRunner implements ProgramRunner { private static final Logger LOG = LoggerFactory.getLogger(InMemoryServiceRunner.class); private final Map<RunId, ProgramOptions> programOptions = Maps.newHashMap(); private final ProgramRunnerFactory programRunnerFactory; @Inject InMemoryServiceRunner(ProgramRunnerFactory programRunnerFactory) { this.programRunnerFactory = programRunnerFactory; } @Override public ProgramController run(Program program, ProgramOptions options) { // Extract and verify parameters ApplicationSpecification appSpec = program.getSpecification(); Preconditions.checkNotNull(appSpec, "Missing application specification."); ProgramType processorType = program.getType(); Preconditions.checkNotNull(processorType, "Missing processor type."); Preconditions.checkArgument(processorType == ProgramType.SERVICE, "Only SERVICE process type is supported."); ServiceSpecification serviceSpec = appSpec.getServices().get(program.getName()); Preconditions.checkNotNull(serviceSpec, "Missing ServiceSpecification for %s", program.getName()); //RunId for the service RunId runId = RunIds.generate(); programOptions.put(runId, options); final Table<String, Integer, ProgramController> serviceRunnables = createRunnables(program, runId, serviceSpec); return new ServiceProgramController(serviceRunnables, runId, program, serviceSpec); } private Table<String, Integer, ProgramController> createRunnables(Program program, RunId runId, ServiceSpecification serviceSpec) { Table<String, Integer, ProgramController> runnables = HashBasedTable.create(); try { for (Map.Entry<String, RuntimeSpecification> entry : serviceSpec.getRunnables().entrySet()) { int instanceCount = entry.getValue().getResourceSpecification().getInstances(); for (int instanceId = 0; instanceId < instanceCount; instanceId++) { runnables.put(entry.getKey(), instanceId, startRunnable(program, createRunnableOptions(entry.getKey(), instanceId, instanceCount, runId))); } } } catch (Throwable t) { // Need to stop all started runnable here. try { Futures.successfulAsList(Iterables.transform(runnables.values(), new Function<ProgramController, ListenableFuture<ProgramController>>() { @Override public ListenableFuture<ProgramController> apply(ProgramController input) { return input.stop(); } })).get(); } catch (Exception e) { LOG.error("Failed to stop all the runnables"); } throw Throwables.propagate(t); } return runnables; } private ProgramController startRunnable(Program program, ProgramOptions runnableOptions) { return programRunnerFactory.create(ProgramRunnerFactory.Type.RUNNABLE).run(program, runnableOptions); } private ProgramOptions createRunnableOptions(String name, int instanceId, int instances, RunId runId) { // Get the right user arguments. Arguments userArguments = new BasicArguments(); if (programOptions.containsKey(runId)) { userArguments = programOptions.get(runId).getUserArguments(); } return new SimpleProgramOptions(name, new BasicArguments(ImmutableMap.of(ProgramOptionConstants.INSTANCE_ID, Integer.toString(instanceId), ProgramOptionConstants.INSTANCES, Integer.toString(instances), ProgramOptionConstants.RUN_ID, runId.getId())), userArguments); } class ServiceProgramController extends AbstractProgramController { private final Table<String, Integer, ProgramController> runnables; private final Program program; private final ServiceSpecification serviceSpec; private final Lock lock = new ReentrantLock(); ServiceProgramController(Table<String, Integer, ProgramController> runnables, RunId runId, Program program, ServiceSpecification serviceSpec) { super(program.getName(), runId); this.program = program; this.runnables = runnables; this.serviceSpec = serviceSpec; started(); } public List<ProgramController> getProgramControllers() { return ImmutableList.copyOf(runnables.values()); } @Override protected void doSuspend() throws Exception { // No-op } @Override protected void doResume() throws Exception { // No-op } @Override protected void doStop() throws Exception { LOG.info("Stopping Service : " + serviceSpec.getName()); lock.lock(); try { Futures.successfulAsList(Iterables.transform(runnables.values(), new Function<ProgramController, ListenableFuture<ProgramController>>() { @Override public ListenableFuture<ProgramController> apply(ProgramController input) { return input.stop(); } })).get(); } finally { lock.unlock(); } LOG.info("Service stopped: " + serviceSpec.getName()); } @Override @SuppressWarnings("unchecked") protected void doCommand(String name, Object value) throws Exception { if (!ProgramOptionConstants.RUNNABLE_INSTANCES.equals(name) || !(value instanceof Map)) { return; } Map<String, String> command = (Map<String, String>) value; lock.lock(); try { changeInstances(command.get("runnable"), Integer.valueOf(command.get("newInstances"))); } catch (Throwable t) { LOG.error(String.format("Fail to change instances: %s", command), t); } finally { lock.unlock(); } } /** * Change the number of instances of the running runnable. * @param runnableName Name of the runnable * @param newCount New instance count * @throws java.util.concurrent.ExecutionException * @throws InterruptedException */ private void changeInstances(String runnableName, final int newCount) throws Exception { Map<Integer, ProgramController> liveRunnables = runnables.row(runnableName); int liveCount = liveRunnables.size(); if (liveCount == newCount) { return; } // stop any extra runnables if (liveCount > newCount) { List<ListenableFuture<ProgramController>> futures = Lists .newArrayListWithCapacity(liveCount - newCount); for (int instanceId = liveCount - 1; instanceId >= newCount; instanceId--) { futures.add(runnables.remove(runnableName, instanceId).stop()); } Futures.allAsList(futures).get(); } // create more runnable instances, if necessary. for (int instanceId = liveCount; instanceId < newCount; instanceId++) { ProgramOptions newProgramOpts = createRunnableOptions(runnableName, instanceId, newCount, getRunId()); ProgramController programController = startRunnable(program, newProgramOpts); runnables.put(runnableName, instanceId, programController); } } } }