/* * 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.test.app; import co.cask.cdap.api.TxRunnable; import co.cask.cdap.api.annotation.Property; import co.cask.cdap.api.annotation.UseDataSet; import co.cask.cdap.api.app.AbstractApplication; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.api.data.DatasetContext; import co.cask.cdap.api.data.stream.Stream; import co.cask.cdap.api.dataset.lib.KeyValueTable; import co.cask.cdap.api.service.AbstractService; import co.cask.cdap.api.service.BasicService; import co.cask.cdap.api.service.http.AbstractHttpServiceHandler; import co.cask.cdap.api.service.http.HttpServiceContext; import co.cask.cdap.api.service.http.HttpServiceRequest; import co.cask.cdap.api.service.http.HttpServiceResponder; import co.cask.cdap.api.worker.AbstractWorker; import co.cask.cdap.api.worker.WorkerContext; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.TimeUnit; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * AppWithServices with a DummyService for unit testing. */ public class AppWithServices extends AbstractApplication { public static final String APP_NAME = "AppWithServices"; public static final String SERVICE_NAME = "ServerService"; public static final String DATASET_WORKER_SERVICE_NAME = "DatasetUpdateService"; public static final String DATASET_UPDATE_WORKER = "DatasetUpdateWorker"; public static final String DATASET_TEST_KEY = "testKey"; public static final String DATASET_TEST_VALUE = "testValue"; public static final String DATASET_TEST_KEY_STOP = "testKeyStop"; public static final String DATASET_TEST_KEY_STOP_2 = "testKeyStop2"; public static final String DATASET_TEST_VALUE_STOP = "testValueStop"; public static final String DATASET_TEST_VALUE_STOP_2 = "testValueStop2"; private static final String DATASET_NAME = "AppWithServicesDataset"; private static final String INIT_KEY = "init"; public static final String TRANSACTIONS_SERVICE_NAME = "TransactionsTestService"; public static final String TRANSACTIONS_DATASET_NAME = "TransactionsDatasetName"; public static final String DESTROY_KEY = "destroy"; public static final String VALUE = "true"; public static final String WRITE_VALUE_RUN_KEY = "write.value.run"; public static final String WRITE_VALUE_STOP_KEY = "write.value.stop"; @Override public void configure() { setName(APP_NAME); addService(new BasicService("NoOpService", new NoOpHandler())); addService(new BasicService(SERVICE_NAME, new ServerService())); addService(new DatasetUpdateService()); addService(new TransactionalHandlerService()); addWorker(new DatasetUpdateWorker()); createDataset(TRANSACTIONS_DATASET_NAME, KeyValueTable.class); } public static final class NoOpHandler extends AbstractHttpServiceHandler { @UseDataSet(DATASET_NAME) private KeyValueTable table; @GET @Path("ping/{key}") public void ping(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("key") String key) throws IOException { responder.sendJson(Bytes.toString(table.read(key))); } @Override protected void configure() { createDataset(DATASET_NAME, KeyValueTable.class); } } public static class TransactionalHandlerService extends AbstractService { @Override protected void configure() { setName(TRANSACTIONS_SERVICE_NAME); addHandler(new TransactionsHandler()); } public static final class TransactionsHandler extends AbstractHttpServiceHandler { @Override public void initialize(HttpServiceContext context) throws Exception { super.initialize(context); KeyValueTable table = getContext().getDataset(TRANSACTIONS_DATASET_NAME); table.write(INIT_KEY, VALUE); } @Path("/write/{key}/{value}/{sleep}") @GET public void handler(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("key") String key, @PathParam("value") String value, @PathParam("sleep") int sleep) throws InterruptedException { KeyValueTable table = getContext().getDataset(TRANSACTIONS_DATASET_NAME); //Check if data written in initialize method is persisted. Preconditions.checkArgument(Bytes.toString(table.read(INIT_KEY)).equals(VALUE)); table.write(key, value); Thread.sleep(sleep); responder.sendStatus(200); } @Path("/read/{key}") @GET public void readHandler(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("key") String key) { KeyValueTable table = getContext().getDataset(TRANSACTIONS_DATASET_NAME); String value = Bytes.toString(table.read(key)); if (value == null) { responder.sendStatus(204); } else { responder.sendJson(200, value); } } @Override public void destroy() { super.destroy(); KeyValueTable table = getContext().getDataset(TRANSACTIONS_DATASET_NAME); table.write(DESTROY_KEY, VALUE); } } } @Path("/") public class ServerService extends AbstractHttpServiceHandler { @Path("/ping2") @GET public void handler(HttpServiceRequest request, HttpServiceResponder responder) { responder.sendStatus(200); } @Path("/failure") @GET public void failure(HttpServiceRequest request, HttpServiceResponder responder) { responder.sendStatus(200); throw new IllegalStateException("Failed"); } @Path("verifyClassLoader") @GET public void verifyClassLoader(HttpServiceRequest request, HttpServiceResponder responder) { if (Thread.currentThread().getContextClassLoader() != getClass().getClassLoader()) { responder.sendStatus(500); } else { responder.sendStatus(200); } } @Path("/discover/{app}/{service}") @GET public void discoverService(HttpServiceRequest request, HttpServiceResponder responder, @PathParam("app") String appId, @PathParam("service") String serviceId) { URL url = getContext().getServiceURL(appId, serviceId); if (url == null) { responder.sendStatus(HttpURLConnection.HTTP_NO_CONTENT); } else { responder.sendJson(url); } } } private static final class DatasetUpdateService extends AbstractService { @Override protected void configure() { setName(DATASET_WORKER_SERVICE_NAME); addHandler(new NoOpHandler()); addStream(new Stream("text")); } private static final class NoOpHandler extends AbstractHttpServiceHandler { // no-op } } private static final class DatasetUpdateWorker extends AbstractWorker { private static int datasetHashCode; private volatile boolean workerStopped; @Property private long sleepMs = 1000; private String valueToWriteOnRun; private String valueToWriteOnStop; @Override public void configure() { setName(DATASET_UPDATE_WORKER); } @Override public void initialize(WorkerContext context) throws Exception { super.initialize(context); valueToWriteOnRun = context.getRuntimeArguments().get(WRITE_VALUE_RUN_KEY); valueToWriteOnStop = context.getRuntimeArguments().get(WRITE_VALUE_STOP_KEY); getContext().execute(new TxRunnable() { @Override public void run(DatasetContext context) throws Exception { KeyValueTable table = context.getDataset(DATASET_NAME); datasetHashCode = System.identityHashCode(table); } }); } @Override public void run() { try { // Run this loop till stop is called. while (!workerStopped) { getContext().execute(new TxRunnable() { @Override public void run(DatasetContext context) throws Exception { KeyValueTable table = context.getDataset(DATASET_NAME); // Write only if the dataset instance is the same as the one gotten in initialize. if (datasetHashCode == System.identityHashCode(table)) { table.write(DATASET_TEST_KEY, valueToWriteOnRun); } } }); TimeUnit.MILLISECONDS.sleep(sleepMs); } } catch (Exception e) { throw Throwables.propagate(e); } } @Override public void destroy() { getContext().execute(new TxRunnable() { @Override public void run(DatasetContext context) throws Exception { KeyValueTable table = context.getDataset(DATASET_NAME); table.write(DATASET_TEST_KEY_STOP, valueToWriteOnStop); // Test different cases of getting datasets - datasets with the same arguments should be the same instance // while datasets with different arguments should be different instances KeyValueTable table2 = context.getDataset(DATASET_NAME, ImmutableMap.of("arg", "value")); KeyValueTable table3 = context.getDataset(DATASET_NAME, ImmutableMap.of("arg", "value")); KeyValueTable table4 = context.getDataset(DATASET_NAME, ImmutableMap.of("arg", "value2")); // table and table2 have different arguments and thus should be different instances if (System.identityHashCode(table) != System.identityHashCode(table2)) { // table2 and table3 have the same arguments and thus should be the same instance if (System.identityHashCode(table2) == System.identityHashCode(table3)) { // table2 and table4 have different arguments and thus should be different instances if (System.identityHashCode(table2) != System.identityHashCode(table4)) { table2.write(DATASET_TEST_KEY_STOP_2, DATASET_TEST_VALUE_STOP_2); } } } } }); } @Override public void stop() { workerStopped = true; } } }