blob: bc93dfc89d29c0f2048e8fbb25492ad86da03248 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.vxquery.rest.servlet;
import static org.apache.vxquery.rest.Constants.HttpHeaderValues.CONTENT_TYPE_JSON;
import static org.apache.vxquery.rest.Constants.HttpHeaderValues.CONTENT_TYPE_XML;
import java.io.IOException;
import java.io.StringWriter;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.htrace.fasterxml.jackson.core.JsonProcessingException;
import org.apache.htrace.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hyracks.http.api.IServletRequest;
import org.apache.hyracks.http.api.IServletResponse;
import org.apache.hyracks.http.server.AbstractServlet;
import org.apache.hyracks.http.server.utils.HttpUtil;
import org.apache.vxquery.exceptions.VXQueryRuntimeException;
import org.apache.vxquery.exceptions.VXQueryServletRuntimeException;
import org.apache.vxquery.rest.response.APIResponse;
import org.apache.vxquery.rest.response.AsyncQueryResponse;
import org.apache.vxquery.rest.response.ErrorResponse;
import org.apache.vxquery.rest.response.QueryResultResponse;
import org.apache.vxquery.rest.response.SyncQueryResponse;
import org.apache.vxquery.rest.service.Status;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
/**
* Abstract servlet to handle REST API requests.
*
* @author Erandi Ganepola
*/
public abstract class RestAPIServlet extends AbstractServlet {
protected final Logger LOGGER;
private JAXBContext jaxbContext;
public RestAPIServlet(ConcurrentMap<String, Object> ctx, String... paths) {
super(ctx, paths);
LOGGER = Logger.getLogger(this.getClass().getName());
try {
jaxbContext = JAXBContext.newInstance(QueryResultResponse.class, AsyncQueryResponse.class,
SyncQueryResponse.class, ErrorResponse.class);
} catch (JAXBException e) {
LOGGER.log(Level.SEVERE, "Error occurred when creating JAXB context", e);
throw new VXQueryRuntimeException("Unable to load JAXBContext", e);
}
}
@Override
protected final void post(IServletRequest request, IServletResponse response) {
getOrPost(request, response);
}
@Override
protected final void get(IServletRequest request, IServletResponse response) {
getOrPost(request, response);
}
private void getOrPost(IServletRequest request, IServletResponse response) {
try {
initResponse(request, response);
APIResponse entity = doHandle(request);
if (entity == null) {
LOGGER.log(Level.WARNING, "No entity found for request : " + request);
response.setStatus(HttpResponseStatus.BAD_REQUEST);
} else {
// Important to set Status OK before setting the entity because the response
// (chunked) checks it before
// writing the response to channel.
setResponseStatus(response, entity);
setEntity(request, response, entity);
}
} catch (IOException e) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
LOGGER.log(Level.SEVERE, "Error occurred when setting content type", e);
}
}
private void initResponse(IServletRequest request, IServletResponse response) throws IOException {
// enable cross-origin resource sharing
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
HttpUtil.setContentType(response, "text/plain");
}
private void setEntity(IServletRequest request, IServletResponse response, APIResponse entity) throws IOException {
String accept = request.getHeader(HttpHeaderNames.ACCEPT, "");
String entityString;
switch (accept) {
case CONTENT_TYPE_XML:
try {
HttpUtil.setContentType(response, CONTENT_TYPE_XML);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(entity, sw);
entityString = sw.toString();
} catch (JAXBException e) {
LOGGER.log(Level.SEVERE, "Error occurred when mapping java object into xml", e);
throw new VXQueryServletRuntimeException("Error occurred when marshalling entity", e);
}
break;
case CONTENT_TYPE_JSON:
default:
try {
HttpUtil.setContentType(response, CONTENT_TYPE_JSON);
ObjectMapper jsonMapper = new ObjectMapper();
entityString = jsonMapper.writeValueAsString(entity);
} catch (JsonProcessingException e) {
LOGGER.log(Level.SEVERE, "Error occurred when mapping java object into JSON", e);
throw new VXQueryServletRuntimeException("Error occurred when mapping entity", e);
}
break;
}
response.writer().print(entityString);
}
private void setResponseStatus(IServletResponse response, APIResponse entity) {
if (Status.SUCCESS.toString().equals(entity.getStatus())) {
response.setStatus(HttpResponseStatus.OK);
} else if (Status.FATAL.toString().equals(entity.getStatus())) {
HttpResponseStatus status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
if (entity instanceof ErrorResponse) {
status = HttpResponseStatus.valueOf(((ErrorResponse) entity).getError().getCode());
}
response.setStatus(status);
}
}
/**
* This abstract method is supposed to return an object which will be the entity
* of the response being sent to the client. Implementing classes doesn't have
* to worry about the content type of the request.
*
* @param request
* {@link IServletRequest} received
* @return Object to be set as the entity of the response
*/
protected abstract APIResponse doHandle(IServletRequest request);
}