/* * 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.mapreduce.service; import co.cask.cdap.api.app.AbstractApplication; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.api.dataset.DatasetProperties; import co.cask.cdap.api.mapreduce.AbstractMapReduce; import co.cask.cdap.api.mapreduce.MapReduceContext; 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.test.app.MyKeyValueTableDefinition; import com.google.common.io.ByteStreams; import org.apache.commons.io.Charsets; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.TaskInputOutputContext; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; /** * A dummy app with MapReduce program with service discovery for testing purpose */ public class TestMapReduceServiceIntegrationApp extends AbstractApplication { public static final String COUNT_METHOD_NAME = "count"; public static final String INPUT_DATASET = "words"; public static final String MR_NAME = "WordCountMR"; public static final String OUTPUT_DATASET = "totals"; public static final String SERVICE_NAME = "WordsCount"; public static final String SERVICE_URL = "WordsCountServiceURL"; public static final String SQUARE_METHOD_NAME = "square"; public static final String SQUARED_TOTAL_WORDS_COUNT = "squared_total_words_count"; @Override public void configure() { setName("MRServiceIntegration"); addDatasetModule("my-kv", MyKeyValueTableDefinition.Module.class); createDataset(INPUT_DATASET, "myKeyValueTable", DatasetProperties.EMPTY); createDataset(OUTPUT_DATASET, "myKeyValueTable", DatasetProperties.EMPTY); addMapReduce(new CountTotal()); addService(SERVICE_NAME, new WordsCountHandler()); } /** * Map Reduce to count squared amount of all words in input dataset. */ public static class CountTotal extends AbstractMapReduce { @Override public void configure() { setName(MR_NAME); setInputDataset(INPUT_DATASET); setOutputDataset(OUTPUT_DATASET); } @Override public void beforeSubmit(MapReduceContext context) throws Exception { Job job = context.getHadoopJob(); job.setMapperClass(MyMapper.class); job.setMapOutputKeyClass(BytesWritable.class); job.setMapOutputValueClass(LongWritable.class); job.setReducerClass(MyReducer.class); URL serviceURL = context.getServiceURL(SERVICE_NAME); job.getConfiguration().set(SERVICE_URL, serviceURL.toString()); } private static URL getServiceUrl(TaskInputOutputContext context) throws MalformedURLException { Configuration configuration = context.getConfiguration(); return new URL(configuration.get(SERVICE_URL)); } /** * Mapper to count amount of words in sentence using service. */ public static class MyMapper extends Mapper<String, String, BytesWritable, LongWritable> { private URL serviceUrl; @Override protected void setup(Context context) throws IOException, InterruptedException { serviceUrl = getServiceUrl(context); } @Override protected void map(String key, String value, Context context) throws IOException, InterruptedException { URL url = new URL(serviceUrl.toString() + COUNT_METHOD_NAME + "?words=" + URLEncoder.encode(value, Charsets.UTF_8.name())); String wordCount = doRequest(url); context.write(new BytesWritable(Bytes.toBytes("total")), new LongWritable(Long.valueOf(wordCount))); } } /** * Reducer to count squared amount of words using service. */ public static class MyReducer extends Reducer<BytesWritable, LongWritable, String, String> { private URL serviceUrl; @Override protected void setup(Reducer.Context context) throws IOException, InterruptedException { serviceUrl = getServiceUrl(context); } @Override protected void reduce(BytesWritable key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException { long total = 0; for (LongWritable longWritable : values) { total += longWritable.get(); } URL url = new URL(serviceUrl.toString() + SQUARE_METHOD_NAME + "?num=" + total); String squaredTotal = doRequest(url); context.write(SQUARED_TOTAL_WORDS_COUNT, squaredTotal); } } private static String doRequest(URL url) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); String response; InputStream inputStream = null; try { inputStream = connection.getInputStream(); response = new String(ByteStreams.toByteArray(inputStream), Charsets.UTF_8); } finally { if (inputStream != null) { inputStream.close(); } connection.disconnect(); } return response; } } public class WordsCountHandler extends AbstractHttpServiceHandler { @Path(COUNT_METHOD_NAME) @GET public void count(HttpServiceRequest request, HttpServiceResponder responder, @QueryParam("words") String words) { if (StringUtils.isEmpty(words)) { responder.sendStatus(HttpURLConnection.HTTP_BAD_REQUEST); } else { responder.sendString(HttpURLConnection.HTTP_OK, Integer.toString(words.split(" ").length), Charsets.UTF_8); } } @Path(SQUARE_METHOD_NAME) @GET public void square(HttpServiceRequest request, HttpServiceResponder responder, @QueryParam("num") Long num) { if (num == null) { responder.sendStatus(HttpURLConnection.HTTP_BAD_REQUEST); } else { responder.sendString(HttpURLConnection.HTTP_OK, Long.toString(num * num), Charsets.UTF_8); } } } }