/*******************************************************************************
* 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.sif.impl.internal.pipeline;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.ebayopensource.turmeric.runtime.common.errors.ErrorSubcategory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceExceptionInterface;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceInvocationExceptionInterface;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceRuntimeException;
import org.ebayopensource.turmeric.runtime.common.impl.internal.pipeline.BaseMessageImpl;
import org.ebayopensource.turmeric.runtime.common.impl.internal.service.ProtocolProcessorDesc;
import org.ebayopensource.turmeric.runtime.common.impl.utils.HTTPCommonUtils;
import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager;
import org.ebayopensource.turmeric.runtime.common.pipeline.Message;
import org.ebayopensource.turmeric.runtime.common.pipeline.Transport;
import org.ebayopensource.turmeric.runtime.common.pipeline.TransportOptions;
import org.ebayopensource.turmeric.runtime.common.service.HeaderMappingsDesc;
import org.ebayopensource.turmeric.runtime.common.service.ServiceOperationDesc;
import org.ebayopensource.turmeric.runtime.common.types.SOAConstants;
import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders;
import org.ebayopensource.turmeric.runtime.common.types.ServiceAddress;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.config.ClientConfigHolder;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.config.ClientConfigManager;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.config.ClientServiceConfigBeanManager;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.config.ClientServiceInvokerConfigBean;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.security.ClientSecurityContextImpl;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.service.ClientServiceDesc;
import org.ebayopensource.turmeric.runtime.sif.impl.internal.service.ClientServiceDescFactory;
import org.ebayopensource.turmeric.runtime.sif.pipeline.ClientMessageContext;
import org.ebayopensource.turmeric.runtime.sif.service.ClientServiceContext;
import org.ebayopensource.turmeric.runtime.sif.service.ServiceInvokerOptions;
import org.ebayopensource.turmeric.common.v1.types.ErrorCategory;
import org.ebayopensource.turmeric.common.v1.types.CommonErrorData;
import org.ebayopensource.turmeric.common.v1.types.ErrorMessage;
import com.ebay.kernel.exception.BaseRuntimeException;
/**
* Internal representation of the client-side message context.
*
*/
public final class ClientMessageContextImpl extends
ReducedClientMessageContextImpl implements ClientMessageContext {
private final ServiceInvokerOptions m_invokerOptions;
private final String m_responseTransport;
private final String m_useCase;
private final String m_consumerId;
/**
* Constructor. Client and service writers should never call this method.
*
* @param serviceDesc
* @param operation
* @param protocolProcessor
* @param transport
* @param requestMessage
* @param responseMessage
* @param serviceAddress
* @param systemProperties
* @param serviceVersion
* @param invokerOptions
* @param responseTransport
* @param useCase
* @param requestCharset
* @param userAsync
*/
public ClientMessageContextImpl(ClientServiceDesc serviceDesc,
ServiceOperationDesc operation,
ProtocolProcessorDesc protocolProcessor, Transport transport,
BaseMessageImpl requestMessage, BaseMessageImpl responseMessage,
ServiceAddress serviceAddress,
Map<String, Object> systemProperties, String serviceVersion,
ServiceInvokerOptions invokerOptions, String responseTransport,
String useCase, String consumerId, Charset requestCharset, String requestUri,
boolean useAsync) throws ServiceException {
super(serviceDesc, operation, protocolProcessor, transport,
requestMessage, responseMessage, buildClientAddress(),
serviceAddress, systemProperties, serviceVersion,
requestCharset, requestUri, null, useAsync);
if (invokerOptions != null) {
m_invokerOptions = invokerOptions;
} else {
m_invokerOptions = new ServiceInvokerOptions();
}
m_responseTransport = responseTransport; // null OK
m_useCase = useCase; // null OK
m_consumerId = consumerId; // null OK
m_securityContext = new ClientSecurityContextImpl(this);
}
/**
* Constructor. Client and service writers should never call this method.
*
* @param serviceDesc
* @param operation
* @param protocolProcessor
* @param transport
* @param requestMessage
* @param responseMessage
* @param serviceAddress
* @param systemProperties
* @param serviceVersion
* @param invokerOptions
* @param responseTransport
* @param useCase
* @param requestCharset
*/
public ClientMessageContextImpl(ClientServiceDesc serviceDesc,
ServiceOperationDesc operation,
ProtocolProcessorDesc protocolProcessor, Transport transport,
BaseMessageImpl requestMessage, BaseMessageImpl responseMessage,
ServiceAddress serviceAddress,
Map<String, Object> systemProperties, String serviceVersion,
ServiceInvokerOptions invokerOptions, String responseTransport,
String useCase, String consumerId, Charset requestCharset, String requestUri)
throws ServiceException {
this(serviceDesc,operation,protocolProcessor,transport,
requestMessage,responseMessage,serviceAddress,
systemProperties,serviceVersion,invokerOptions,
responseTransport,useCase, consumerId, requestCharset,requestUri,false);
}
@Override
public final ClientServiceContext getServiceContext() {
// TODO: find out why do we need this hack... this method is defined in
// the base class
return super.getServiceContext();
}
/**
* Returns the service invoker options that were supplied for this specific
* invocation. This value is never null. Any service invoker options will
* override their corresponding values in configuration.
*
*/
public ServiceInvokerOptions getInvokerOptions() {
return m_invokerOptions;
}
/**
* Returns the transport options that were supplied for this specific
* invocation, within the ServiceInvokerOptions. Any transport options will
* override their corresponding values in configuration (provided that the
* transport is capable of supporting per-request override of these values).
*/
public TransportOptions getTransportOptions() {
return m_invokerOptions.getTransportOptions();
}
/*
* (non-Javadoc)
*
* @see org.ebayopensource.turmeric.runtime.common.impl.pipeline.BaseMessageContextImpl#buildOutputHeaders(java.util.Map)
*/
@Override
protected Map<String, String> buildOutputHeaders(
Map<String, String> customHeaders) throws ServiceException {
Map<String, String> result = new HashMap<String, String>();
if (customHeaders != null) {
result.putAll(customHeaders);
}
// Operation name, service name
HTTPCommonUtils.addServiceAndOperationHeaders(getServiceQName(),
getOperationName(), result);
Message requestMsg = getRequestMessage();
Message responseMsg = getResponseMessage();
// build request/response data format headers
String requestPayload = requestMsg.getPayloadType();
String responsePayload = responseMsg.getPayloadType();
result.put(SOAHeaders.REQUEST_DATA_FORMAT, requestPayload);
if (responsePayload != null) {
result.put(SOAHeaders.RESPONSE_DATA_FORMAT, responsePayload);
}
String requestId = getRequestId();
if (requestId == null) {
// also check the transport headers for requestId
requestId = getRequestMessage().getTransportHeader(SOAHeaders.REQUEST_ID);
if (requestId != null) {
setRequestId(requestId, null);
}
}
if (requestId != null) {
result.put(SOAHeaders.REQUEST_ID, requestId);
} else {
String requestGuid = getRequestGuid();
if (null == requestGuid) {
requestGuid = getRequestMessage().getTransportHeader(SOAHeaders.REQUEST_GUID);
if (null == requestGuid || requestGuid.isEmpty()) {
requestGuid = HTTPCommonUtils.generateRequestGuid();
}
setRequestGuid(requestGuid);
setProperty(SOAConstants.CTX_PROP_GUID_CREATED, Boolean.valueOf(true));
}
result.put(SOAHeaders.REQUEST_GUID, requestGuid);
}
HTTPCommonUtils.addG11nHeaders(requestMsg.getG11nOptions(), result);
// version
if (getServiceVersion() != null && !isVersionIgnored()) {
result.put(SOAHeaders.VERSION, getServiceVersion());
}
String messageProtocol = getProtocolProcessor().getMessageProtocol();
if (messageProtocol != null
&& !messageProtocol.equals(SOAConstants.MSG_PROTOCOL_NONE)) {
result.put(SOAHeaders.MESSAGE_PROTOCOL, messageProtocol);
}
if (m_responseTransport != null) {
result.put(SOAHeaders.RESPONSE_TRANSPORT, m_responseTransport);
}
if (m_useCase != null) {
result.put(SOAHeaders.USECASE_NAME, m_useCase);
}
if (m_consumerId != null) {
result.put(SOAHeaders.CONSUMER_ID, m_consumerId);
}
HeaderMappingsDesc requestHeaderMappings = getServiceDesc()
.getRequestHeaderMappings();
HTTPCommonUtils.applyHeaderMap(requestHeaderMappings.getHeaderMap(),
result);
HTTPCommonUtils.applySuppressHeaderSet(requestHeaderMappings
.getSuppressHeaderSet(), result); // map first, then suppress
return result;
}
@SuppressWarnings("cast")
private boolean isVersionIgnored() {
return ((ClientConfigHolder) ((ClientServiceDesc) getServiceDesc())
.getConfig()).isIgnoreServiceVersion();
}
@Override
protected void processInboundHeaders(Map<String, String> transportHeaders)
throws ServiceException {
super.processInboundHeaders(transportHeaders);
if (transportHeaders == null) {
return;
}
HeaderMappingsDesc responseHeaderMappings = getServiceDesc()
.getResponseHeaderMappings();
HTTPCommonUtils.applyHeaderMap(responseHeaderMappings.getHeaderMap(),
transportHeaders);
HTTPCommonUtils.applySuppressHeaderSet(responseHeaderMappings
.getSuppressHeaderSet(), transportHeaders);
String requestId = transportHeaders.get(SOAHeaders.REQUEST_ID);
String guid = transportHeaders.get(SOAHeaders.REQUEST_GUID);
if (requestId != null) {
setRequestId(requestId, guid);
if (getCallerMessageContext() != null) {
getCallerMessageContext().setRequestId(requestId, guid);
}
} else if (guid != null) {
setRequestId(null, guid);
if (getCallerMessageContext() != null) {
getCallerMessageContext().setRequestId(null, guid);
}
} // else if both requestId and guid are null, do nothing.
}
@Override
protected Throwable validateNewError(Throwable t) {
if (t instanceof ServiceInvocationExceptionInterface) {
LogManager
.getInstance(this.getClass())
.log(
Level.SEVERE,
"A ServiceInvocationException was added to the ClientMessageContext. "
+ "This is disallowed and SOA framework has made its best efforts "
+ "to convert error to a system error. Exception text: "
+ t.toString(), t);
t = new ServiceException(
ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_INVOCATION_FAILED_SYS_CLIENT,
ErrorConstants.ERRORDOMAIN, new Object[] { getAdminName(), t.toString() }), t);
return super.validateNewError(t);
}
// erase any hints that it could be an application exception
if (t instanceof ServiceExceptionInterface) {
boolean isAppError = false;
if (t instanceof ServiceException) {
ServiceException e2 = (ServiceException) t;
if (e2.getSubcategory() == ErrorSubcategory.APPLICATION) {
e2.eraseSubcategory();
isAppError = true;
}
}
if (t instanceof ServiceRuntimeException) {
ServiceRuntimeException e2 = (ServiceRuntimeException) t;
if (e2.getSubcategory() == ErrorSubcategory.APPLICATION) {
e2.eraseSubcategory();
isAppError = true;
}
}
ServiceExceptionInterface e2 = (ServiceExceptionInterface) t;
ErrorMessage errorMessage = e2.getErrorMessage();
if (errorMessage != null) {
List<CommonErrorData> errorDataList = errorMessage.getError();
for (int i = 0; i < errorDataList.size(); i++) {
CommonErrorData errorData = errorDataList.get(i);
if (errorData != null
&& errorData.getCategory() == ErrorCategory.APPLICATION) {
errorData.setCategory(ErrorCategory.SYSTEM);
isAppError = true;
}
}
}
if (isAppError) {
LogManager
.getInstance(this.getClass())
.log(
Level.SEVERE,
"Application error was added to the ClientMessageContext. "
+ "This is disallowed and SOA framework has made its best efforts "
+ "to convert error to a system error. Exception text: "
+ t.toString(), t);
}
}
return super.validateNewError(t);
}
private static ServiceAddress buildClientAddress() {
// return local address
return new ServiceAddress(null);
}
/*
* Updates the ClientConfigHolder and reloads the ServiceDesc for a valid
* env (ie. serviceLocation is not null)
*/
public String setServiceLocationFromLocationMapping(String env)
throws ServiceException {
ClientConfigManager configMgr = ClientConfigManager.getInstance();
ClientConfigHolder holdercopy = configMgr.getConfigForUpdate(
getServiceId().getAdminName(), getServiceId()
.getClientName(), getServiceId().getEnvName());
String serviceLocation = holdercopy
.setServiceLocationFromLocationMapping(env);
if (serviceLocation != null && serviceLocation.length() > 0) {
// Constructing the URL shouldn't be throwing an error as the
// ServiceLocations in Config
// have been validated
try {
getServiceAddress().setServiceLocationUrl(
new URL(serviceLocation));
} catch (MalformedURLException e) {
throw new ServiceException(
ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_BAD_REQUEST_URL,
ErrorConstants.ERRORDOMAIN, new Object[] {getServiceDesc().getClientName(),serviceLocation }));
}
// The 'env' is a valid key, and the holder is updated.
configMgr.updateConfig(getServiceDesc().getAdminName(),
getServiceDesc().getClientName(), holdercopy);
ClientServiceDescFactory descFactory = ClientServiceDescFactory
.getInstance();
descFactory.reloadServiceDesc(getServiceId().getAdminName(), getServiceId()
.getClientName(), getServiceId().getEnvName());
// Update the ClientConfig Invoker bean with the Service Location
//env is the EnvironmentName read via envmapper.
try {
ClientServiceInvokerConfigBean bean = ClientServiceConfigBeanManager
.getInvokerInstance(getServiceId().getAdminName(), getServiceId()
.getClientName(), getServiceId().getEnvName());
bean.setServiceUrl(serviceLocation);
} catch (BaseRuntimeException be) {
// For backward compatibility
try {
ClientServiceInvokerConfigBean bean = ClientServiceConfigBeanManager
.getInvokerInstance(getServiceDesc().getAdminName(),
getServiceDesc().getClientName());
bean.setServiceUrl(serviceLocation);
} catch (BaseRuntimeException e) {
// if we do not find a ClientServiceInvokerConfigBean with
// no environment specified then we re-throw the exception
// ClientServiceInvokerConfigBean not initialized for input
// environment (env). This would only happen If some one
// mixes old style (Using EnvironmentResolutionHandler)
// with new style i.e. with multiple client config support.
throw be;
}
}
}
return serviceLocation;
}
}