package org.ovirt.engine.api.restapi.resource;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.api.common.util.CompletenessAssertor;
import org.ovirt.engine.api.common.util.EnumValidator;
import org.ovirt.engine.api.model.Fault;
import org.ovirt.engine.api.restapi.LocalConfig;
import org.ovirt.engine.api.restapi.invocation.Current;
import org.ovirt.engine.api.restapi.invocation.CurrentManager;
import org.ovirt.engine.api.restapi.logging.MessageBundle;
import org.ovirt.engine.api.restapi.logging.Messages;
import org.ovirt.engine.api.restapi.types.MappingLocator;
import org.ovirt.engine.api.restapi.util.ParametersHelper;
import org.ovirt.engine.api.restapi.utils.MalformedIdException;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.businessentities.aaa.DbUser;
import org.ovirt.engine.core.common.interfaces.BackendLocal;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.compat.Guid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BaseBackendResource {
private static final String FILTER = "filter";
private static final Logger log = LoggerFactory.getLogger(AbstractBackendResource.class);
protected MessageBundle messageBundle;
protected UriInfo uriInfo;
protected HttpHeaders httpHeaders;
protected MappingLocator mappingLocator;
protected <S extends BaseBackendResource> S inject(S resource) {
resource.setMappingLocator(mappingLocator);
resource.setMessageBundle(messageBundle);
resource.setUriInfo(uriInfo);
resource.setHttpHeaders(httpHeaders);
return resource;
}
public void setMappingLocator(MappingLocator mappingLocator) {
this.mappingLocator = mappingLocator;
}
public MappingLocator getMappingLocator() {
return mappingLocator;
}
public BackendLocal getBackend() {
return getCurrent().getBackend();
}
public void setMessageBundle(MessageBundle messageBundle) {
this.messageBundle = messageBundle;
}
public MessageBundle getMessageBundle() {
return messageBundle;
}
public UriInfo getUriInfo() {
return uriInfo;
}
@Context
public void setUriInfo(UriInfo uriInfo) {
this.uriInfo = uriInfo;
}
public HttpHeaders getHttpHeaders() {
return httpHeaders;
}
@Context
public void setHttpHeaders(HttpHeaders httpHeaders) {
this.httpHeaders = httpHeaders;
}
protected Current getCurrent() {
return CurrentManager.get();
}
protected <P extends VdcQueryParametersBase> P sessionize(P parameters) {
String sessionId = getCurrent().getSessionId();
parameters.setSessionId(sessionId);
return parameters;
}
protected <P extends VdcActionParametersBase> P sessionize(P parameters) {
String sessionId = getCurrent().getSessionId();
parameters.setSessionId(sessionId);
return parameters;
}
protected Fault fault(String reason, String detail) {
Fault fault = new Fault();
fault.setReason(reason);
fault.setDetail(detail);
return fault;
}
static String detail(Throwable t) {
String detail = null;
if (log.isDebugEnabled()) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw, true));
detail = sw.toString();
} else {
detail = t.getMessage();
}
return detail;
}
/**
* An exception which may be thrown from a BackendOperation invoke()
* method with a message containing details of the operation failure.
*/
protected static class BackendFailureException extends Exception {
private static final long serialVersionUID = 2244591834711331403L;
private Response.Status httpStatus;
public BackendFailureException(String failure, Response.Status httpStatus) {
super(failure);
this.httpStatus = httpStatus;
}
public Response.Status getHttpStatus() {
return httpStatus;
}
}
/**
* A BackendFailureException subclass specifically indicating that
* the entity targeted by the operation does not exist.
*/
protected class EntityNotFoundException extends BackendFailureException {
private static final long serialVersionUID = -761673260081428877L;
private String identifier;
public EntityNotFoundException(String identifier) {
super(localize(Messages.ENTITY_NOT_FOUND_TEMPLATE, identifier), Response.Status.NOT_FOUND);
this.identifier = identifier;
}
public String getIdentifier() {
return identifier;
}
}
protected class MalformedNumberException extends BackendFailureException {
private static final long serialVersionUID = 394735369823915802L;
public MalformedNumberException(String msg) {
super(msg, Status.BAD_REQUEST);
}
}
public class WebFaultException extends WebApplicationException {
private static final long serialVersionUID = 394735369823915802L;
private Fault fault;
public WebFaultException(Exception cause, String detail, Status status) {
this(cause, localize(Messages.BACKEND_FAILED), detail, status);
}
public WebFaultException(Exception cause, String reason, String detail, Status status) {
this(cause, fault(reason, detail), status);
}
public WebFaultException(Exception cause, Fault fault, Status status) {
super(cause, Response.status(status).entity(fault).build());
this.fault = fault;
}
public Fault getFault() {
return fault;
}
}
/**
* Handle a BackendFailureException or an exception thrown from a
* backend query/action and re-throw as a WebApplicationException.
*
* If the exception indicates that the referenced backend entity
* does not exist and @notFoundAs404 is true, then throw a
* WebApplicationException which wraps a 404 HTTP response.
* @param e the exception to handle
* @param notFoundAs404 whether to return a 404 if appropriate
*
* @return the result of the operation
*/
protected <T> T handleError(Exception e, boolean notFoundAs404) {
handleError(Void.class, e, notFoundAs404);
return null;
}
/**
* Handle a BackendFailureException or an exception thrown from a
* backend query/action and re-throw as a WebApplicationException.
*
* If the exception indicates that the referenced backend entity
* does not exist and @notFoundAs404 is true, then throw a
* WebApplicationException which wraps a 404 HTTP response.
* @param clz dummy explicit type parameter for use when type
* inference is not possible (irrelevant in any case as a value
* is never returned, rather an exception is always thrown)
* @param e the exception to handle
* @param notFoundAs404 whether to return a 404 if appropriate
*
* @return the result of the operation
*/
protected <T> T handleError(Class<T> clz, Exception e, boolean notFoundAs404) {
if ((e instanceof EntityNotFoundException) && notFoundAs404) {
throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
} else if ((e instanceof BackendFailureException) && !StringUtils.isEmpty(e.getMessage())) {
log.error(localize(Messages.BACKEND_FAILED_TEMPLATE, e.getMessage()));
BackendFailureException e2 = (BackendFailureException) e;
throw new WebFaultException(null, e.getMessage(), e2.getHttpStatus() != null ? e2.getHttpStatus()
: Response.Status.BAD_REQUEST);
} else if (e instanceof WebFaultException) {
WebFaultException e2 = (WebFaultException) e;
log.error(localize(Messages.BACKEND_FAILED_TEMPLATE, e2.getMessage()));
log.error("Exception", e2);
throw e2;
} else {
log.error(localize(Messages.BACKEND_FAILED_TEMPLATE, e.getMessage()));
log.error("Exception", e);
throw new WebFaultException(e, detail(e), Response.Status.INTERNAL_SERVER_ERROR);
}
}
@SuppressWarnings("unchecked")
protected <T> List<T> asCollection(Class<T> clz, Object o) {
List<T> collection = null;
if (o instanceof List) {
collection = (List<T>)o;
} else if (clz.isInstance(o)) {
collection = new ArrayList<>();
collection.add(clz.cast(o));
}
return collection;
}
static <T> ArrayList<T> asList(T t) {
ArrayList<T> list = new ArrayList<>();
list.add(t);
return list;
}
protected Guid asGuid(String id) {
try {
return new Guid(id);
}catch (IllegalArgumentException e) {
throw new MalformedIdException(e);
}
}
protected Guid asGuid(byte[] guid, boolean keepByteOrder) {
try {
return new Guid(guid, keepByteOrder);
} catch (IllegalArgumentException e) {
throw new MalformedIdException(e);
}
}
protected Long asLong(String id) {
try {
return Long.valueOf(id);
}catch (IllegalArgumentException e) {
throw new MalformedIdException(e);
}
}
protected <T> T instantiate(Class<T> clz) {
T ret = null;
try {
ret = clz.newInstance();
} catch (Exception e) {
// simple instantiation shouldn't fail
}
return ret;
}
protected Locale getEffectiveLocale() {
List<Locale> locales = httpHeaders.getAcceptableLanguages();
return locales != null && locales.size() > 0
? locales.get(0)
: null;
}
protected String localize(Messages message, Object... parameters) {
Locale locale = getEffectiveLocale();
return locale != null
? messageBundle.localize(message, locale, parameters)
: messageBundle.localize(message, parameters);
}
protected String localize(String error) {
BackendLocal backend = getBackend();
Locale locale = getEffectiveLocale();
return locale != null
? backend.getErrorsTranslator().translateErrorTextSingle(error, locale)
: backend.getErrorsTranslator().translateErrorTextSingle(error);
}
protected String localize(List<String> errors) {
BackendLocal backend = getBackend();
Locale locale = getEffectiveLocale();
return locale != null
? backend.getErrorsTranslator().translateErrorText(errors, locale).toString()
: backend.getErrorsTranslator().translateErrorText(errors).toString();
}
public void validateParameters(Object model, String... required) {
validateParameters(model, 2, required);
}
public void validateParameters(Object model, int frameOffset, String... required) {
String reason = localize(Messages.INCOMPLETE_PARAMS_REASON);
String detail = localize(Messages.INCOMPLETE_PARAMS_DETAIL_TEMPLATE);
CompletenessAssertor.validateParameters(reason, detail, model, frameOffset + 1, required);
}
public <E extends Enum<E>> E validateEnum(Class<E> clz, String name) {
String reason = localize(Messages.INVALID_ENUM_REASON);
String detail = localize(Messages.INVALID_ENUM_DETAIL);
return EnumValidator.validateEnum(reason, detail, clz, name);
}
/**
* Checks if the given value is within the range given by the {@code min} and {@code max} parameters. If the value
* is {@code null} it will do nothing.
*
* @param name the name of the attribute
* @param value the value of the attribute
* @param min the min value of the range
* @param max the max value of the range
*/
public void validateRange(String name, Integer value, int min, int max) {
if (value != null && (value < min || value > max)) {
Fault fault = new Fault();
fault.setReason(localize(Messages.VALUE_OUT_OF_RANGE_REASON));
fault.setDetail(localize(Messages.VALUE_OUT_OF_RANGE_DETAIL_TEMPLATE, value.toString(), name,
String.valueOf(min), String.valueOf(max)));
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(fault).build());
}
}
public <E extends Enum<E>> List<E> validateEnumValues(Class<E> clz, List<String> names) {
ArrayList<E> enumList = new ArrayList<>();
for (String name : names) {
enumList.add(validateEnum(clz, name));
}
return enumList;
}
/**
* Indicate whether data retrieval should be filtered according to user permissions.
*
* @return true if data should be filtered, otherwise queries are executed as admin.
*/
protected boolean isFiltered() {
Boolean result = ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, FILTER, true, null);
if (result == null) {
DbUser user = getCurrent().getUser();
if (!user.isAdmin()) {
LocalConfig config = LocalConfig.getInstance();
result = config.getFilterByDefault();
}
else {
result = Boolean.FALSE;
}
}
return result;
}
}