/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.error;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceExceptionInterface;
import org.ebayopensource.turmeric.runtime.common.impl.utils.ReflectionUtils;
import org.ebayopensource.turmeric.runtime.common.pipeline.LoggingHandler;
import org.ebayopensource.turmeric.runtime.common.pipeline.LoggingHandlerStage;
import org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext;
import org.ebayopensource.turmeric.runtime.common.types.SOAConstants;
import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders;
import org.ebayopensource.turmeric.runtime.error.model.Error;
import org.ebayopensource.turmeric.runtime.error.model.ErrorValue;
import org.ebayopensource.turmeric.common.v1.types.CommonErrorData;
import org.ebayopensource.turmeric.common.v1.types.ErrorData;
import org.ebayopensource.turmeric.utils.jpa.JPAAroundAdvice;
import org.ebayopensource.turmeric.utils.jpa.PersistenceContext;
/**
* The errors are made up by the framework and by the application.
* Framework errors are most always wrapped up into a ServiceException that contains ErrorData objects.
* Application errors *must* be converted by the application to ErrorData objects.
* To do this, the framework provides {@link org.ebayopensource.turmeric.runtime.spf.impl.internal.servicse.ServiceImplHelper}
* which has a bunch of getCommonErrorData() methods that allow to convert the exception into an ErrorData object.
* If the application needs to rethrow some business exception, then the ErrorData object
* can be stuffed into the BaseResponse object, and it will be logged by the ResponseResidentErrorHandler
* (which must be a normal handler and not a logging handler - since the invocation did not throw).
*
* Note that ServiceImplHelper says that ErrorData is deprecated, and indeed it lacks a
* very important field, the errorName, which is present in CommonErrorData.
*
* Applications that build ErrorData object must pass to getCommonErrorData() a bunch of arguments
* which specify the resource bundle to look for to create errors.
*
* So for example:
*
* public WithdrawResponse withdraw(WithdrawRequest request)
* {
* WithdrawResponse response = new WithdrawResponse();
* response.setErrorMessage(new ErrorMessage());
* if (balance < request.amount)
* {
* ErrorDataProvider.ErrorDataKey errorDataKey =
* new ErrorDataProvider.ErrorDataKey("some_error_library", "some_error_bundle", "some_error_name");
* CommonErrorData error = ServiceImplHelper.getCommonErrorData(errorDataKey, new Object[]{}});
*
* // Either throw or add to the response:
* response.getErrorMessage().getError().add(error);
* throw new ServiceRuntimeException(error);
* }
* ...
* return response;
* }
*
* Error libraries and bundles are specified in META-INF/errorlibrary/[domain]/ErrorData.xml, along with
* bundles for L10N in the same directory named Errors (hence Errors_it.properties, for example).
*
* Since error libraries are not aware of each other, it is possible that they define the same error name;
* the definition is in an XML file, so there is no possibility to enforce uniqueness of the error name.
* When an error happens, this handler needs to be able to store the error event information to the DB,
* so it needs to identify uniquely which error must be linked to the event.
* This is achieved using errorIds. While errorIds are generated locally by the plugin (that creates the
* error library through a wizard), the errorId generation is pluggable, and the generator that will be
* plugged in must enforce uniqueness.
*/
public class DAOErrorLoggingHandler implements LoggingHandler
{
private LoggingHandler delegate;
@Override
public void init(InitContext ctx) throws ServiceException {
Map<String,String> options = ctx.getOptions();
String persistenceUnitName = options.get("persistenceUnitName");
EntityManagerFactory entityManagerFactory = PersistenceContext.createEntityManagerFactory(persistenceUnitName);
String errorDAOClassName = options.get("errorDAOClassName");
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
ErrorDAO errorDAO = ReflectionUtils.createInstance(errorDAOClassName, ErrorDAO.class, contextClassLoader);
ClassLoader classLoader = LoggingHandler.class.getClassLoader();
Class[] interfaces = {LoggingHandler.class};
Target target = new Target(errorDAO);
JPAAroundAdvice handler = new JPAAroundAdvice(entityManagerFactory, target);
delegate = (LoggingHandler) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
@Override
public void logProcessingStage(MessageContext ctx, LoggingHandlerStage stage) throws ServiceException {
delegate.logProcessingStage(ctx, stage);
}
@Override
public void logResponseResidentError(MessageContext ctx, ErrorData errorData) throws ServiceException {
delegate.logResponseResidentError(ctx, errorData);
}
@Override
public void logError(MessageContext ctx, Throwable throwable) throws ServiceException {
delegate.logError(ctx, throwable);
}
@Override
public void logWarning(MessageContext ctx, Throwable throwable) throws ServiceException {
delegate.logWarning(ctx, throwable);
}
private static class Target implements LoggingHandler {
private final ErrorDAO errorDAO;
private Target(ErrorDAO errorDAO) {
this.errorDAO = errorDAO;
}
@Override
public void init(InitContext ctx) throws ServiceException {
}
@Override
public void logProcessingStage(MessageContext ctx, LoggingHandlerStage stage) throws ServiceException {
}
@Override
public void logResponseResidentError(MessageContext ctx, ErrorData errorData) throws ServiceException {
persistErrors(ctx, Collections.singletonList(errorData));
}
@Override
public void logError(MessageContext ctx, Throwable throwable) throws ServiceException {
if (throwable instanceof ServiceExceptionInterface) {
ServiceExceptionInterface serviceException = (ServiceExceptionInterface) throwable;
persistErrors(ctx, serviceException.getErrorMessage().getError());
} else {
// TODO: create an error data from the exception itself ?
}
}
@Override
public void logWarning(MessageContext ctx, Throwable throwable) throws ServiceException {
logError(ctx, throwable);
}
private void persistErrors(MessageContext ctx, List<? extends ErrorData> errors) throws ServiceException {
long now = System.currentTimeMillis();
List<ErrorValue> errorValues = new ArrayList<ErrorValue>();
for (ErrorData data : errors) {
CommonErrorData errorData = (CommonErrorData) data;
org.ebayopensource.turmeric.runtime.error.model.Error error =
new Error(
errorData.getErrorId(),
errorData.getErrorName(),
errorData.getCategory(),
errorData.getSeverity(),
errorData.getDomain(),
errorData.getSubdomain(),
errorData.getOrganization()
);
Error existing = errorDAO.persistErrorIfAbsent(error);
if (existing != null)
error = existing;
String errorMessage = errorData.getMessage();
String serviceAdminName = ctx.getAdminName();
String operationName = ctx.getOperationName();
String consumerName = retrieveConsumerName(ctx);
boolean serverSide = !ctx.getServiceId().isClientSide();
ErrorValue errorValue = new ErrorValue(error, errorMessage, serviceAdminName, operationName, consumerName, now, serverSide, 0);
errorValues.add(errorValue);
}
errorDAO.persistErrorValues(errorValues);
}
private String retrieveConsumerName(MessageContext ctx) throws ServiceException {
String result = ctx.getRequestMessage().getTransportHeader(SOAHeaders.USECASE_NAME);
if (result == null)
result = SOAConstants.DEFAULT_USE_CASE;
return result;
}
}
}