/**
* Copyright (C) 2014 BonitaSoft S.A.
* BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation
* version 2.1 of the License.
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.bonitasoft.web.rest.server.api.resource;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.bonitasoft.engine.bpm.contract.ContractViolationException;
import org.bonitasoft.engine.exception.NotFoundException;
import org.bonitasoft.engine.search.SearchOptions;
import org.bonitasoft.engine.search.SearchResult;
import org.bonitasoft.engine.session.APISession;
import org.bonitasoft.web.rest.server.datastore.filter.Filters;
import org.bonitasoft.web.rest.server.datastore.utils.SearchOptionsCreator;
import org.bonitasoft.web.rest.server.datastore.utils.Sorts;
import org.bonitasoft.web.rest.server.framework.APIServletCall;
import org.bonitasoft.web.toolkit.client.common.exception.api.APIException;
import org.restlet.data.CharacterSet;
import org.restlet.data.Header;
import org.restlet.data.Status;
import org.restlet.ext.servlet.ServletUtils;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
import org.restlet.util.Series;
import com.fasterxml.jackson.core.JsonParseException;
/**
* @author Emmanuel Duchastenier
*/
public class CommonResource extends ServerResource {
private APISession sessionSingleton = null;
/**
* Logger
*/
private static final Logger LOGGER = Logger.getLogger(CommonResource.class.getName());
/**
* Get the tenant session to access the engine APIs
*/
public APISession getEngineSession() {
if (sessionSingleton == null) {
final HttpSession session = getHttpSession();
sessionSingleton = (APISession) session.getAttribute("apiSession");
}
return sessionSingleton;
}
public HttpSession getHttpSession() {
return getHttpRequest().getSession();
}
public HttpServletRequest getHttpRequest() {
return ServletUtils.getRequest(getRequest());
}
protected Map<String, String> getSearchFilters() {
return parseFilters(getParameterAsList(APIServletCall.PARAMETER_FILTER));
}
protected String getQueryParameter(final boolean mandatory) {
return getParameter(APIServletCall.PARAMETER_QUERY, mandatory);
}
/**
* Builds a map where keys are Engine constants defining filter keys, and values are values corresponding to those keys.
*
* @param parameters The filters passed as string according to the form ["key1=value1", "key2=value2"].
* @return a map of the form: [key1: value1, key2: value2].
*/
protected Map<String, String> parseFilters(final List<String> parameters) {
if (parameters == null) {
return null;
}
final Map<String, String> results = new HashMap<>();
for (final String parameter : parameters) {
final String[] split = parameter.split("=");
if (split.length < 2) {
results.put(split[0], null);
} else {
results.put(split[0], parameter.substring(split[0].length() + 1));
}
}
return results;
}
protected String getSearchOrder() {
return getParameter(APIServletCall.PARAMETER_ORDER, false);
}
protected String getSearchTerm() {
return getParameter(APIServletCall.PARAMETER_SEARCH, false);
}
public Integer getIntegerParameter(final String parameterName, final boolean mandatory) {
final String parameterValue = getParameter(parameterName, mandatory);
if (parameterValue != null) {
return Integer.parseInt(parameterValue);
}
return null;
}
public Long getLongParameter(final String parameterName, final boolean mandatory) {
final String parameterValue = getParameter(parameterName, mandatory);
if (parameterValue != null) {
return Long.parseLong(parameterValue);
}
return null;
}
public String getParameter(final String parameterName, final boolean mandatory) {
final String parameter = getRequestParameter(parameterName);
if (mandatory) {
verifyNotNullParameter(parameter, parameterName);
}
return parameter;
}
protected String getRequestParameter(final String parameterName) {
return getQueryValue(parameterName);
}
protected void verifyNotNullParameter(final Object parameter, final String parameterName) throws APIException {
if (parameter == null) {
throw new APIException("Parameter " + parameterName + " is mandatory.");
}
}
/**
* Get a list of parameter values by name. If the parameter doesn't exist the result will be an empty list.
*
* @param name
* The name of the parameter (case sensitive).
* @return The values of a parameter as a list of String.
*/
public List<String> getParameterAsList(final String name) {
return Arrays.asList(getQuery().getValuesArray(name));
}
public SearchOptions buildSearchOptions() {
return new SearchOptionsCreator(getSearchPageNumber(), getSearchPageSize(), getSearchTerm(), new Sorts(
getSearchOrder()), buildFilters()).create();
}
protected Filters buildFilters() {
return new Filters(getSearchFilters());
}
@Override
protected void doCatch(final Throwable throwable) {
final Throwable t = throwable.getCause() != null ? throwable.getCause() : throwable;
// Don't need to log the wrapping exception, the cause itself is more interesting:
super.doCatch(t);
final String message = "Error while querying REST resource " + getClass().getName() + " message: " + t.getMessage();
final ErrorMessage errorMessage = new ErrorMessage(t);
getResponse().setStatus(getStatus(), message);
if (t instanceof IllegalArgumentException || t instanceof JsonParseException) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "***" + message);
}
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
} else if (t instanceof FileNotFoundException) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "***" + message);
}
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
errorMessage.setMessage("File Not Found");
} else if (t instanceof NotFoundException) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "***" + message);
}
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
} else {
LOGGER.log(Level.SEVERE, t.getMessage(), t);
}
getResponse().setEntity(errorMessage.toEntity());
}
@Override
protected Representation doHandle(final Variant variant) throws ResourceException {
// Used to ensure output is correctly encoded:
variant.setCharacterSet(CharacterSet.UTF_8);
return super.doHandle(variant);
}
@Override
public String getAttribute(final String name) {
final String attribute = super.getAttribute(name);
try {
if (attribute != null) {
return URLDecoder.decode(attribute, "UTF-8");
}
} catch (final UnsupportedEncodingException e) {
}
return attribute;
}
public Long getPathParamAsLong(final String parameterName) {
final String value = getAttribute(parameterName);
return convertToLong(value);
}
private Long convertToLong(final String value) {
try {
return Long.parseLong(value);
} catch (final NumberFormatException e) {
throw new IllegalArgumentException("[ " + value + " ] must be a number");
}
}
public List<Long> getParameterAsLongList(final String parameterName) {
final String values = getQuery().getValues(parameterName);
if (values != null) {
final String[] parameterValues = values.split(",");
if (parameterValues != null && parameterValues.length > 0) {
final List<Long> longValues = new ArrayList<>();
for (final String parameterValue : parameterValues) {
longValues.add(convertToLong(parameterValue));
}
return longValues;
}
}
return null;
}
public String getPathParam(final String name) {
return getAttribute(name);
}
protected int getSearchPageNumber() {
try {
return getIntegerParameter(APIServletCall.PARAMETER_PAGE, true);
} catch (final APIException e) {
throw new IllegalArgumentException("query parameter p (page) is mandatory");
} catch (final NumberFormatException e) {
throw new IllegalArgumentException("query parameter p (page) should be a number");
}
}
protected int getSearchPageSize() {
try {
return getIntegerParameter(APIServletCall.PARAMETER_LIMIT, true);
} catch (final APIException e) {
throw new IllegalArgumentException("query parameter c (count) is mandatory");
} catch (final NumberFormatException e) {
throw new IllegalArgumentException("query parameter c (count) should be a number");
}
}
protected void setContentRange(final SearchResult<?> searchResult) {
setContentRange(getSearchPageNumber(), getSearchPageSize(), searchResult.getCount());
}
protected void setContentRange(final int pageNumber, final int pageSize, final long count) {
final Series<Header> headers = getResponse().getHeaders();
headers.add(new Header("Content-range", pageNumber + "-" + pageSize + "/" + count));
}
protected void manageContractViolationException(final ContractViolationException e, final String statusErrorMessage) {
if (getLogger().isLoggable(Level.INFO)) {
final StringBuilder explanations = new StringBuilder();
for (final String explanation : e.getExplanations()) {
explanations.append(explanation);
}
getLogger().log(Level.INFO, e.getSimpleMessage() + "\nExplanations:\n" + explanations);
}
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, statusErrorMessage);
final ErrorMessageWithExplanations errorMessage = new ErrorMessageWithExplanations(e);
errorMessage.setMessage(e.getSimpleMessage());
errorMessage.setExplanations(e.getExplanations());
getResponse().setEntity(errorMessage.toEntity());
}
}