/** * 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; } }