/*
* Copyright © 2014-2016 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 net.fake.test.app;
import co.cask.cdap.api.annotation.ProcessInput;
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.batch.Input;
import co.cask.cdap.api.data.batch.Output;
import co.cask.cdap.api.data.stream.Stream;
import co.cask.cdap.api.dataset.lib.KeyValueTable;
import co.cask.cdap.api.flow.AbstractFlow;
import co.cask.cdap.api.flow.flowlet.AbstractFlowlet;
import co.cask.cdap.api.flow.flowlet.StreamEvent;
import co.cask.cdap.api.mapreduce.AbstractMapReduce;
import co.cask.cdap.api.mapreduce.MapReduceContext;
import co.cask.cdap.api.schedule.Schedule;
import co.cask.cdap.api.service.BasicService;
import co.cask.cdap.api.service.http.AbstractHttpServiceHandler;
import co.cask.cdap.api.service.http.HttpServiceRequest;
import co.cask.cdap.api.service.http.HttpServiceResponder;
import co.cask.cdap.api.workflow.AbstractWorkflow;
import com.google.common.collect.ImmutableMap;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
* BundleJarApp contains a service that uses a third party library.
*/
public class BundleJarApp extends AbstractApplication {
private static final Logger LOG = LoggerFactory.getLogger(BundleJarApp.class);
public static final String EXPECTED_LOAD_TEST_CLASSES_OUTPUT =
"hello_HelloWorld__co_cask_cdap_api_schedule_Schedule";
@Override
public void configure() {
setName("BundleJarApp");
setDescription("Demonstrates usage of bundle jar applications");
addStream(new Stream("simpleInputStream"));
createDataset("simpleInputDataset", KeyValueTable.class);
createDataset("simpleOutputDataset", KeyValueTable.class);
addFlow(new SimpleFlow());
addService(new BasicService("SimpleGetOutput", new SimpleGetOutput()));
addService(new BasicService("SimpleGetInput", new SimpleGetInput()));
addService(new BasicService("PrintService", new PrintHandler()));
addMapReduce(new SimpleMapReduce());
}
public static String loadTestClasses() {
try {
// Use context classloader instead of BundleJarApp.class.getClassLoader() b/c this is used only in unit test
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String result = classLoader.loadClass("hello.HelloWorld").getName() + "__" + Schedule.class.getName();
return result.replaceAll("\\.", "_");
} catch (ClassNotFoundException e) {
LOG.error("Error loading test classes with " + BundleJarApp.class.getClassLoader(), e);
return "null";
}
}
/**
* Contains a method that can be run to check if expected classes are loaded.
*/
public static class PrintHandler extends AbstractHttpServiceHandler {
private static final Logger LOG = LoggerFactory.getLogger(PrintHandler.class);
@GET
@Path("load/{class}")
public void load(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("class") String className)
throws IOException, InterruptedException {
responder.sendJson(
ImmutableMap.builder()
.put("Class.forName", loadClassForName(className))
.build());
}
private String loadClassForName(String className) {
try {
// Use context classloader instead of BundleJarApp.class.getClassLoader() b/c this is used only in unit test
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return classLoader.loadClass(className).getName();
} catch (Exception e) {
LOG.error("Error", e);
return "null";
}
}
}
/**
* Queries simpleOutputDataset.
*/
public static class SimpleGetOutput extends AbstractHttpServiceHandler {
private static final Logger LOG = LoggerFactory.getLogger(SimpleGetOutput.class);
@UseDataSet("simpleOutputDataset")
private KeyValueTable output;
@GET
@Path("get/{key}")
public void get(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("key") String key)
throws IOException, InterruptedException {
LOG.info("Hello " + loadTestClasses());
String value = Bytes.toString(output.read(Bytes.toBytes(key)));
if (value == null) {
value = "null";
}
responder.sendJson(ImmutableMap.of(key, value));
}
}
/**
* Queries simpleInputDataset.
*/
public static class SimpleGetInput extends AbstractHttpServiceHandler {
private static final Logger LOG = LoggerFactory.getLogger(SimpleGetInput.class);
@UseDataSet("simpleInputDataset")
private KeyValueTable input;
@GET
@Path("get/{key}")
public void get(HttpServiceRequest request, HttpServiceResponder responder,
@PathParam("key") String key)
throws IOException, InterruptedException {
LOG.info("Hello " + loadTestClasses());
String value = Bytes.toString(input.read(Bytes.toBytes(key)));
if (value == null) {
value = "null";
}
responder.sendJson(ImmutableMap.of(key, value));
}
}
/**
* Transfers data without transformation from simpleInputDataset to simpleOutputDataset.
*/
public static class SimpleMapReduce extends AbstractMapReduce {
private static final Logger LOG = LoggerFactory.getLogger(SimpleMapReduce.class);
@UseDataSet("simpleInputDataset")
private KeyValueTable input;
/**
* Define a MapReduce job.
* @param context the context of a MapReduce job
* @throws Exception
*/
@Override
public void beforeSubmit(MapReduceContext context) throws Exception {
LOG.info("Hello " + loadTestClasses());
Job job = context.getHadoopJob();
job.setMapperClass(SimpleMapper.class);
job.setReducerClass(SimpleReducer.class);
context.addInput(Input.ofDataset("simpleInputDataset", input.getSplits()));
context.addOutput(Output.ofDataset("simpleOutputDataset"));
}
/**
* Transforms input key value data into key value + loadTestClasses().
*/
public static class SimpleMapper extends Mapper<byte[], byte[], BytesWritable, BytesWritable> {
private static final Logger LOG = LoggerFactory.getLogger(SimpleMapper.class);
public SimpleMapper() {
}
@Override
public void map(byte[] key, byte[] value, Context context) throws IOException, InterruptedException {
LOG.info("Hello " + loadTestClasses());
byte[] realVal = Bytes.toBytes(Bytes.toString(value) + "=map=" + loadTestClasses());
context.write(new BytesWritable(key), new BytesWritable(realVal));
}
}
/**
* Transforms input key value data into key value + loadTestClasses().
*/
public static class SimpleReducer extends Reducer<BytesWritable, BytesWritable, byte[], byte[]> {
private static final Logger LOG = LoggerFactory.getLogger(SimpleReducer.class);
public SimpleReducer() {
}
/**
* Aggregate the number of requests by hour and store the results in the output DataSet.
* @param key the timestamp in hour
* @param values the occurrence of logs sent in one hour
* @param context the context of a MapReduce job
* @throws java.io.IOException
* @throws InterruptedException
*/
@Override
protected void reduce(BytesWritable key, Iterable<BytesWritable> values, Context context)
throws IOException, InterruptedException {
LOG.info("Hello " + loadTestClasses());
for (BytesWritable val : values) {
byte[] realVal = Bytes.toBytes(Bytes.toString(val.getBytes()) + "=reduce=" + loadTestClasses());
context.write(key.getBytes(), realVal);
}
}
}
}
/**
* Runs a workflow action that calls loadTestClasses().
*/
public static class SimpleWorkflow extends AbstractWorkflow {
private static final Logger LOG = LoggerFactory.getLogger(SimpleWorkflow.class);
@Override
public void configure() {
setName("SimpleWorkflow");
setDescription("Description");
addAction(new SimpleWorkflowAction());
}
private class SimpleWorkflowAction extends co.cask.cdap.api.workflow.AbstractWorkflowAction {
@Override
public void configure() {
setName("SimpleWorkflowAction");
setDescription("Description");
}
@Override
public void run() {
LOG.info("Hello " + loadTestClasses());
}
}
}
/**
* Flow that writes from simpleInputStream to simpleInputDataset.
*/
public static class SimpleFlow extends AbstractFlow {
@Override
protected void configureFlow() {
setName("SimpleFlow");
setDescription("Description");
addFlowlet("simpleFlowlet", new SimpleFlowlet());
connectStream("simpleInputStream", "simpleFlowlet");
}
private static class SimpleFlowlet extends AbstractFlowlet {
private static final Logger LOG = LoggerFactory.getLogger(SimpleFlowlet.class);
@UseDataSet("simpleInputDataset")
private KeyValueTable input;
@ProcessInput
public void process(StreamEvent event) {
LOG.info("Hello " + loadTestClasses());
String body = Bytes.toString(event.getBody());
String key = body.split(":")[0];
String value = body.split(":")[1];
input.write(key, value + loadTestClasses());
}
}
}
}