/* * Copyright (C) 2011 Jan Pokorsky * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.cas.lib.proarc.webapp.server.rest; import cz.cas.lib.proarc.webapp.server.rest.JacksonProvider.DefaultAdapter; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Wrapper suitable as a RestDataSource response * * @author Jan Pokorsky */ @XmlRootElement(name="response") @XmlAccessorType(XmlAccessType.FIELD) public class SmartGwtResponse<T> { public static final int STATUS_FAILURE = -1; public static final int STATUS_LOGIN_INCORRECT = -5; public static final int STATUS_LOGIN_REQUIRED = -7; public static final int STATUS_LOGIN_SUCCESS = -8; public static final int STATUS_MAX_LOGIN_ATTEMPTS_EXCEEDED = -6; public static final int STATUS_SERVER_TIMEOUT = -100; public static final int STATUS_SUCCESS = 0; public static final int STATUS_TRANSPORT_ERROR = -90; public static final int STATUS_VALIDATION_ERROR = -4; private int status; private Integer startRow; private Integer endRow; private Integer totalRows; @XmlTransient private List<T> typedData; /** The JAXB mapping for {@link #typedData} and {@link #errdata}. */ private Object data; @XmlTransient private String errdata; /** * errors holder; see RestDataSource doc * <br/> validation format: status:STATUS_VALIDATION_ERROR, * errors:[{fieldname:{errorMessage:"msg"}}] or errors:[fieldname:[{errorMessage:"msg1"},{errorMessage:"msg2"}]] * <br/> failure format: status:STATUS_FAILURE, errors:errormsg */ @XmlJavaTypeAdapter(ErrorAdapter.class) private Map<String, List<ErrorMessage>> errors; public SmartGwtResponse() { } public SmartGwtResponse(T singletonDataItem) { this(STATUS_SUCCESS, 0, 0, 1, singletonDataItem != null ? Collections.singletonList(singletonDataItem) : Collections.<T>emptyList()); } public SmartGwtResponse(List<T> data) { this(STATUS_SUCCESS, 0, Math.max(0, data.size() - 1), data.size(), data); } public SmartGwtResponse(int status, Integer startRow, Integer endRow, Integer totalRows, List<T> data) { this.status = status; this.startRow = startRow; this.endRow = endRow; this.totalRows = totalRows; setTypedData(data); } /** * Builds response as an unrecoverable error with status {@link #STATUS_FAILURE}. * @param <T> data type * @param msg error message send as data * @return the response */ public static <T> SmartGwtResponse<T> asError(String msg) { SmartGwtResponse<T> result = new SmartGwtResponse<T>(); result.setErrorData(msg); return result; } /** * @see #asError(java.lang.String) */ public static <T> SmartGwtResponse<T> asError(Throwable t) { return asError(null, t); } /** * @see #asError(java.lang.String) */ public static <T> SmartGwtResponse<T> asError(String msg, Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); if (msg == null || msg.isEmpty()) { msg = t.getMessage(); } if (msg != null && !msg.isEmpty()) { pw.println(msg); pw.println(); } t.printStackTrace(pw); pw.close(); return asError(sw.toString()); } public static <T> SmartGwtResponse<T> asError(String fieldName, String message) { return SmartGwtResponse.<T>asError().error(fieldName, message).build(); } public static <T> ErrorBuilder<T> asError() { return new ErrorBuilder<T>(); } public List<T> getData() { return typedData; } String getDataAsError() { return errdata; } private void setTypedData(List<T> data) { this.typedData = (data != null) ? data : Collections.<T>emptyList(); this.data = typedData; this.errdata = null; } private void setErrorData(String msg) { this.status = STATUS_FAILURE; this.errdata = msg; this.data = errdata; this.typedData = null; } public Integer getEndRow() { return endRow; } public Map<String, List<ErrorMessage>> getErrors() { return errors; } public Integer getStartRow() { return startRow; } public int getStatus() { return status; } public Integer getTotalRows() { return totalRows; } public static final class ErrorBuilder<T> { private Map<String, List<ErrorMessage>> errors = new LinkedHashMap<String, List<ErrorMessage>>(); private ErrorBuilder() { } public ErrorBuilder<T> error(String fieldName, String message) { List<ErrorMessage> msgs = errors.get(fieldName); if (msgs == null) { msgs = new ArrayList<ErrorMessage>(); errors.put(fieldName, msgs); } msgs.add(new ErrorMessage(message)); return this; } public SmartGwtResponse<T> build() { SmartGwtResponse<T> result = new SmartGwtResponse<T>(); result.errors = errors; result.status = STATUS_VALIDATION_ERROR; return result; } } @XmlAccessorType(XmlAccessType.FIELD) public static class ErrorMessage { private String errorMessage; public ErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } public ErrorMessage() { } public String getErrorMessage() { return errorMessage; } } /** * The JAXB mapping of Map to DOM Element that complies with the SmartGWT response schema. */ public static class ErrorAdapter extends XmlAdapter<Element, Map<String, List<ErrorMessage>>> { public ErrorAdapter() { } @Override public Map<String, List<ErrorMessage>> unmarshal(Element v) throws Exception { // not required yet throw new UnsupportedOperationException(); } @Override public Element marshal(Map<String, List<ErrorMessage>> v) throws Exception { if (v == null) { return null; } List<Element> errFields = errorsAsElements(v); if (errFields.isEmpty()) { return null; } else { Document doc = errFields.get(0).getOwnerDocument(); Element errorsElm = doc.createElement("errors"); for (Element errField : errFields) { errorsElm.appendChild(errField); } return errorsElm; } } /** * Gets list of {@code <fieldName><errorMessage>error</errorMessage></fieldName>} elements * where fieldName is replaced with real names. * @param errors maps field names to lists of errors * @return the list of DOM elements */ private static List<Element> errorsAsElements(Map<String, List<ErrorMessage>> errors) { try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); List<Element> errFields = new ArrayList<Element>(); for (Entry<String, List<ErrorMessage>> entry : errors.entrySet()) { String field = entry.getKey(); Element fieldElm = doc.createElement(field); errFields.add(fieldElm); for (ErrorMessage errmsg : entry.getValue()) { Element errMsgElm = doc.createElement("errorMessage"); errMsgElm.setTextContent(errmsg.getErrorMessage()); fieldElm.appendChild(errMsgElm); } } return errFields; } catch (ParserConfigurationException ex) { throw new IllegalStateException(ex); } } } /** * JSON JAXB mapping helper class. It removes errors XML adapter as Jackson can * serialize rather Map than DOM elements. */ public static abstract class AnnotatedSmartGwtResponse<T> extends SmartGwtResponse<T> { @XmlJavaTypeAdapter(SgwtErrorAdapter.class) private Map<String, List<ErrorMessage>> errors; } private static class SgwtErrorAdapter extends DefaultAdapter<Map<String, List<ErrorMessage>>> {} }