/**
* Copyright (C) 2014 Stratio (http://stratio.com)
*
* 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 com.stratio.ingestion.source.rest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.collections.CollectionUtils;
import org.apache.flume.Event;
import org.codehaus.jackson.map.ObjectMapper;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.stratio.ingestion.source.rest.exception.RestSourceException;
import com.stratio.ingestion.source.rest.handler.RestSourceHandler;
import com.stratio.ingestion.source.rest.url.UrlHandler;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
/**
* RequestJob. Quartz Job that make a request to a RESTful service.
*/
public class RequestJob implements Job {
private static final Logger log = LoggerFactory.getLogger(RequestJob.class);
public static final String APPLICATION_TYPE = "applicationType";
public static final String METHOD = "method";
public static final String HEADERS = "headers";
public static final String DEFAULT_REST_SOURCE_HANDLER = "com.stratio.ingestion.source"
+ ".rest.DefaultRestSourceHandler";
public static final String HANDLER = "restSourceHandler";
public static final String URL_HANDLER = "urlHandler";
private Map<String, String> properties;
private LinkedBlockingQueue<Event> queue;
private Client client;
private MediaType mediaType;
private JobExecutionContext context;
private RestSourceHandler restSourceHandler;
private UrlHandler urlHandler;
/**
* {@inheritDoc}
*
* @param context
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
this.context = context;
SchedulerContext schedulerContext = null;
try {
log.debug("Executing quartz job");
schedulerContext = context.getScheduler().getContext();
initProperties(schedulerContext);
WebResource.Builder resourceBuilder = getBuilder();
ClientResponse response = getResponse(resourceBuilder);
if (response != null) {
String responseAsString = response.getEntity(String.class);
final List<Event> events = restSourceHandler
.getEvents(responseAsString, responseToHeaders(response.getHeaders()));
queue.addAll(events);
urlHandler.updateFilterParameters(getLastEvent(events));
}
} catch (Exception e) {
log.error("Error getting Response: " + e.getMessage());
}
}
protected String getLastEvent(List<Event> events) {
String lastEventAsString = "";
if (CollectionUtils.isNotEmpty(events)) {
final Event lastEvent = events.get(events.size() - 1);
lastEventAsString = new String(lastEvent.getBody());
}
return lastEventAsString;
}
private WebResource.Builder getBuilder() {
WebResource resource = client.resource(urlHandler.buildUrl(properties));
WebResource.Builder resourceBuilder = setApplicationType(resource, properties.get(APPLICATION_TYPE));
addHeaders(resourceBuilder, properties.get(HEADERS));
return resourceBuilder;
}
private ClientResponse getResponse(WebResource.Builder webResource) {
ClientResponse response;
if ("GET".equals(properties.get(METHOD))) {
response = webResource.get(ClientResponse.class);
} else {
//TODO pending review POST request implementation
response = webResource.post(ClientResponse.class);
}
return response;
}
/**
* Convert Multivalued Headers to Plain Map Headers accepted by Flume Event.
*
* @param map multivaluedMap.
* @return plain Map.
*/
private Map<String, String> responseToHeaders(MultivaluedMap<String, String> map) {
Map<String, String> newMap = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
newMap.put(entry.getKey(), multiValueHeaderToString(entry.getValue()));
}
return newMap;
}
/**
* Convert a multivalue header to String.
*
* @param list
* @return
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">w3.org</a>
*/
private String multiValueHeaderToString(List<String> list) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); ++i) {
sb.append(list.get(i));
if (i != list.size() - 1) {
sb.append(", ");
}
}
return sb.toString();
}
/**
* Initialize properties that are received in the {@code SchedulerContext}.
*
* @param context
*/
@SuppressWarnings("unchecked")
public void initProperties(SchedulerContext context) {
queue = (LinkedBlockingQueue<Event>) context.get("queue");
properties = (Map<String, String>) context.get("properties");
client = (Client) context.get("client");
restSourceHandler = (RestSourceHandler) context.get(HANDLER);
urlHandler = (UrlHandler) context.get(URL_HANDLER);
}
/**
* Set an Application Type to the request depending on a parameter and its corresponding
* {@code MediaType}.
*
* @param webResource Current target url.
* @param applicationType ApplicationType to set.
* @return
*/
public WebResource.Builder setApplicationType(WebResource webResource, String applicationType) {
if ("TEXT".equals(applicationType)) {
mediaType = MediaType.TEXT_PLAIN_TYPE;
} else {
mediaType = MediaType.APPLICATION_JSON_TYPE;
}
return webResource.accept(mediaType);
}
/**
* Map raw Json to an object and add each key-value to a headers request.
*
* @param builder Current REST request.
* @param jsonHeaders raw json.
* @return
*/
private WebResource.Builder addHeaders(WebResource.Builder builder, String jsonHeaders) {
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> headers = mapper.readValue(jsonHeaders, Map.class);
for (Map.Entry<String, Object> entry : headers.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
}
} catch (Exception e) {
throw new RestSourceException("An error occurred during headers parsing", e);
}
return builder;
}
}