package ameba.mvc;
import ameba.core.ServiceLocators;
import ameba.message.error.ErrorMessage;
import ameba.mvc.template.internal.Viewables;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.glassfish.jersey.server.mvc.Viewable;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 错误处理页面配置
*
* @author icode
*
*/
@Singleton
public class ErrorPageGenerator implements MessageBodyWriter<ErrorMessage> {
// 模板引擎会去掉第一个斜线
/**
* Constant <code>DEFAULT_ERROR_PAGE_DIR="/_protected/error/"</code>
*/
protected static final String DEFAULT_ERROR_PAGE_DIR = "/_protected/error/";
/**
* Constant <code>DEFAULT_500_RED_PRODUCT_ERROR_PAGE="DEFAULT_ERROR_PAGE_DIR + 500r.httl"</code>
*/
protected static final String DEFAULT_500_RED_PRODUCT_ERROR_PAGE = DEFAULT_ERROR_PAGE_DIR + "500r.httl";
/**
* Constant <code>DEFAULT_501_ERROR_PAGE="DEFAULT_ERROR_PAGE_DIR + 500p.httl"</code>
*/
protected static final String DEFAULT_500_PURPLE_ERROR_PAGE = DEFAULT_ERROR_PAGE_DIR + "500p.httl";
/**
* Constant <code>DEFAULT_400_BLACK_ERROR_PAGE="DEFAULT_ERROR_PAGE_DIR + 403.httl"</code>
*/
protected static final String DEFAULT_400_BLACK_ERROR_PAGE = DEFAULT_ERROR_PAGE_DIR + "400b.httl";
/**
* Constant <code>DEFAULT_400_ORANGE_ERROR_PAGE="DEFAULT_ERROR_PAGE_DIR + 400o.httl"</code>
*/
protected static final String DEFAULT_400_ORANGE_ERROR_PAGE = DEFAULT_ERROR_PAGE_DIR + "400o.httl";
/**
* Constant <code>errorTemplateMap</code>
*/
protected static final Map<Integer, String> errorTemplateMap = Maps.newHashMap();
private static String defaultErrorTemplate;
@Inject
protected Provider<ContainerRequestContext> requestProvider;
@Inject
private Provider<MessageBodyWorkers> workers;
static void pushErrorMap(int status, String tpl) {
errorTemplateMap.put(status, tpl);
}
static void pushAllErrorMap(HashMap<Integer, String> map) {
errorTemplateMap.putAll(map);
}
/**
* <p>Getter for the field <code>errorTemplateMap</code>.</p>
*
* @return a {@link java.util.HashMap} object.
*/
public static Map<Integer, String> getErrorTemplateMap() {
return Collections.unmodifiableMap(errorTemplateMap);
}
/**
* <p>Getter for the field <code>defaultErrorTemplate</code>.</p>
*
* @return a {@link java.lang.String} object.
*/
public static String getDefaultErrorTemplate() {
return defaultErrorTemplate;
}
static void setDefaultErrorTemplate(String template) {
defaultErrorTemplate = template;
}
/**
* <p>createViewable.</p>
*
* @param tplName a {@link java.lang.String} object.
* @param request a {@link javax.ws.rs.container.ContainerRequestContext} object.
* @param status a int.
* @param exception a {@link java.lang.Throwable} object.
* @param errorMessage a {@link ameba.message.error.ErrorMessage} object.
* @return a {@link org.glassfish.jersey.server.mvc.Viewable} object.
*/
protected Viewable createViewable(String tplName, ContainerRequestContext request,
int status, Throwable exception, ErrorMessage errorMessage) {
Error error = new Error(
request,
status,
exception,
errorMessage);
return Viewables.newDefaultViewable(tplName, error);
}
/** {@inheritDoc} */
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return ErrorMessage.class.isAssignableFrom(type)
&& mediaType != null && (mediaType.getSubtype().equals("html")
|| mediaType.getSubtype().equals("xhtml+xml"));
}
/** {@inheritDoc} */
@Override
public long getSize(ErrorMessage errorMessage,
Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
/**
* <p>getErrorTemplate.</p>
*
* @param status a int.
* @return a {@link java.lang.String} object.
*/
protected String getErrorTemplate(int status) {
return errorTemplateMap.get(status);
}
/** {@inheritDoc} */
@Override
public void writeTo(ErrorMessage errorMessage,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException {
ContainerRequestContext request = requestProvider.get();
int status = errorMessage.getStatus();
String tplName = getErrorTemplate(status);
if (StringUtils.isBlank(tplName)) {
if (StringUtils.isBlank(defaultErrorTemplate)) {
if (status < 500) {
switch (status) {
case 401:
case 403:
case 405:
case 406:
case 415:
tplName = DEFAULT_400_BLACK_ERROR_PAGE;
break;
default:
tplName = DEFAULT_400_ORANGE_ERROR_PAGE;
}
} else {
switch (status) {
case 501:
tplName = DEFAULT_500_PURPLE_ERROR_PAGE;
break;
default:
tplName = DEFAULT_500_RED_PRODUCT_ERROR_PAGE;
}
}
} else {
tplName = defaultErrorTemplate;
}
}
Viewable viewable = createViewable(tplName, request, status, errorMessage.getThrowable(), errorMessage);
writeViewable(viewable, mediaType, httpHeaders, entityStream);
}
/**
* <p>writeViewable.</p>
*
* @param viewable a {@link org.glassfish.jersey.server.mvc.Viewable} object.
* @param mediaType a {@link javax.ws.rs.core.MediaType} object.
* @param httpHeaders a {@link javax.ws.rs.core.MultivaluedMap} object.
* @param entityStream a {@link java.io.OutputStream} object.
* @throws java.io.IOException if any.
*/
protected void writeViewable(Viewable viewable,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException {
MessageBodyWriter<Viewable> writer = ServiceLocators.getViewableMessageBodyWriter(workers.get());
if (writer != null) {
writer.writeTo(viewable,
Viewable.class,
Viewable.class,
new Annotation[0],
mediaType,
httpHeaders,
entityStream);
}
}
public static class Error {
private int status;
private ContainerRequestContext request;
private Throwable exception;
private ErrorMessage errorMessage;
public Error() {
}
public Error(ContainerRequestContext request, int status, Throwable exception, ErrorMessage message) {
this.status = status;
this.exception = exception;
this.request = request;
this.errorMessage = message;
}
public int getStatus() {
return status;
}
public ErrorMessage getErrorMessage() {
return errorMessage;
}
public ContainerRequestContext getRequest() {
return request;
}
public Throwable getException() {
return exception;
}
}
}