/*******************************************************************************
* 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.common.impl.internal.pipeline;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import org.ebayopensource.turmeric.runtime.binding.objectnode.ObjectNode;
import org.ebayopensource.turmeric.runtime.binding.objectnode.impl.JavaObjectNodeImpl;
import org.ebayopensource.turmeric.runtime.binding.schema.DataElementSchema;
import org.ebayopensource.turmeric.runtime.binding.utils.CollectionUtils;
import org.ebayopensource.turmeric.runtime.common.binding.DataBindingDesc;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.impl.attachment.BaseMessageAttachments;
import org.ebayopensource.turmeric.runtime.common.pipeline.Message;
import org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext;
import org.ebayopensource.turmeric.runtime.common.service.ServiceOperationDesc;
import org.ebayopensource.turmeric.runtime.common.service.ServiceOperationParamDesc;
import org.ebayopensource.turmeric.runtime.common.types.Cookie;
import org.ebayopensource.turmeric.runtime.common.types.G11nOptions;
import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
/**
* Abstract base internal class, representing a unit of data being sent or received by the SOA framework.
*
* BaseMessageImpl provides all base functionality required to keep and change the data.
* However, it does not define the source of the data, or the serialization mechanisms.
*
* @author ichernyshev
*/
public abstract class BaseMessageImpl implements Message {
private static final Cookie[] EMPTY_COOKIES = new Cookie[0];
private BaseMessageContextImpl m_context;
private final boolean m_isRequestMessage;
private final String m_transportProtocol;
private final DataBindingDesc m_dataBindingDesc;
private final ServiceOperationDesc m_operationDesc;
private boolean m_paramsLoaded;
private boolean m_cleanedData;
private Map<String,String> m_transportHeaders;
private Map<String,Cookie> m_cookies;
private Object m_transportData;
protected Collection<ObjectNode> m_messageHeaders;
protected ByteBuffer m_byteBuffer;
private boolean m_bufferingMode;
// On server side, we will change the g11n options in request pipeline
// g11n handler.
protected G11nOptions m_g11nOptions;
protected Object[] m_params;
protected Object m_errorResponse;
protected BaseMessageAttachments m_attachments;
/**
* Internal constructor to be called by the derived classes.
* @param isRequestMessage
* @param transportProtocol
* @param dataBindingDesc
* @param g11nOptions
* @param transportHeaders
* @param cookies
* @param attachments
* @param operationDesc
*/
protected BaseMessageImpl(boolean isRequestMessage,
String transportProtocol,
DataBindingDesc dataBindingDesc, G11nOptions g11nOptions,
Map<String,String> transportHeaders, Cookie[] cookies,
Collection<ObjectNode> messageHeaders,
BaseMessageAttachments attachments, ServiceOperationDesc operationDesc, boolean bufferingMode)
throws ServiceException
{
if (transportProtocol == null || dataBindingDesc == null ||
g11nOptions == null || operationDesc == null)
{
throw new NullPointerException();
}
m_isRequestMessage = isRequestMessage;
m_transportProtocol = transportProtocol;
m_dataBindingDesc = dataBindingDesc;
m_g11nOptions = g11nOptions;
m_transportHeaders = transportHeaders;
m_messageHeaders = messageHeaders;
if (m_messageHeaders == null) {
m_messageHeaders = new ArrayList<ObjectNode>();
}
if (cookies != null) {
for (int i=0; i<cookies.length; i++) {
setCookie(cookies[i]);
}
}
m_attachments = attachments;
m_operationDesc = operationDesc;
m_bufferingMode = bufferingMode;
}
/*
* Reset state in order to allow memory to be freed for the message.
*/
protected void cleanupData() {
m_cleanedData = true;
m_params = null;
m_errorResponse = null;
}
final void setContext(BaseMessageContextImpl context) {
// this method is called from the context constructor,
// which means context is not fully initialized yet
// do not use context here !!!
if (context == null) {
throw new NullPointerException();
}
if (m_context != null) {
throw new IllegalStateException();
}
m_context = context;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getContext()
*/
public final MessageContext getContext() {
return getContextImpl();
}
/**
* Internal function to get the framework-level message context information.
* @return the full internal version of the (base class) message context.
*/
protected final BaseMessageContextImpl getContextImpl() {
if (m_context == null) {
throw new IllegalStateException();
}
return m_context;
}
protected final boolean hasContext() {
return m_context != null;
}
/**
* Returns true if this message is a request message (outbound client request or inbound service request).
* @return whether this is a request message
*/
protected final boolean isRequestMessage() {
return m_isRequestMessage;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getPayloadType()
*/
public final String getPayloadType() throws ServiceException {
return m_dataBindingDesc.getPayloadType();
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getTransportProtocol()
*/
public final String getTransportProtocol() throws ServiceException {
return m_transportProtocol;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getDataBindingDesc()
*/
public final DataBindingDesc getDataBindingDesc() throws ServiceException {
return m_dataBindingDesc;
}
/**
* Get the configuration, such as request, response, and error message types, for the message's associated service operation.
* @return the operation-specific configuration
*/
protected final ServiceOperationDesc getOperationDesc() {
return m_operationDesc;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getParamDesc()
*/
public final ServiceOperationParamDesc getParamDesc() throws ServiceException {
if (isErrorMessage()) {
return m_operationDesc.getErrorType();
}
if (isRequestMessage()) {
return m_operationDesc.getRequestType();
}
return m_operationDesc.getResponseType();
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getHeaderParamDesc()
*/
public final ServiceOperationParamDesc getHeaderParamDesc() throws ServiceException {
if (isRequestMessage()) {
return m_operationDesc.getRequestHeaders();
}
return m_operationDesc.getResponseHeaders();
}
/**
* Internal check function; throws an exception if the data in the message is cleaned (state is released).
* @throws ServiceException
*/
protected final void checkNotCleaned() throws ServiceException {
if (m_cleanedData) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_MSG_DATA_CLEANED,
ErrorConstants.ERRORDOMAIN));
}
}
/**
* Internal function to test if the message is cleaned (state is released).
* @return true if the message is cleaned
*/
protected final boolean hasCleanedData() {
return m_cleanedData;
}
/**
* Internal function; check not cleaned, and load parameters, if not loaded already.
* @throws ServiceException
*/
protected final void checkParams() throws ServiceException {
checkNotCleaned();
if (!m_paramsLoaded) {
loadParamsImpl();
m_paramsLoaded = true;
}
}
/**
* Internal function; set the parameters loaded state to true.
* @throws ServiceException
*/
protected final void markAsLoaded() throws ServiceException {
checkNotCleaned();
m_paramsLoaded = true;
}
/**
* Internal function; abstract function to trigger actual loading of parameter information from the stream
* within inbound messages.
* @throws ServiceException
*/
protected abstract void loadParamsImpl() throws ServiceException;
/**
* Return the list of expected Java types (classes) for this message's arguments, in operation argument
* order. Note that a response message may contain either a output arguments or an error response, but
* only the output argument types are given here.
*
* WARNING: this does not return the Error Types!
*
* @return the list of expected java types
*/
public final List<Class> getParamTypes() throws ServiceException {
List<Class> paramTypes;
if (isRequestMessage()) {
paramTypes = m_operationDesc.getRequestType().getRootJavaTypes();
} else {
paramTypes = m_operationDesc.getResponseType().getRootJavaTypes();
}
return paramTypes;
}
/**
* Return the error response type for this message.
* @return a list containing the single error response type (always).
*/
protected final List<Class> getErrorParamTypeList() {
return m_operationDesc.getErrorType().getRootJavaTypes();
}
/**
* Returns the single error response type for this message.
* @return the single error response type
*/
protected final Class getErrorParamType() {
return m_operationDesc.getErrorType().getRootJavaTypes().get(0);
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getParamCount()
*/
public final int getParamCount() throws ServiceException {
checkParams();
return (m_params != null ? m_params.length : 0);
}
/**
* Verify that the specified index is in range for this message, or throw an exception otherwise.
* @param idx the index to be validated
* @throws ServiceException
*/
private void checkParamAccess(int idx) throws ServiceException {
checkParams();
if (m_errorResponse != null) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_MSG_ERROR_RESPONSE,
ErrorConstants.ERRORDOMAIN));
}
if (m_params == null || m_params.length == 0) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_MSG_ACCESS_VOID_DATA,
ErrorConstants.ERRORDOMAIN));
}
if (idx < 0 || idx >= m_params.length) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_MSG_ACCESS_WRONG_IDX,
ErrorConstants.ERRORDOMAIN, new Object[] {Integer.toString(idx), Integer.toString(m_params.length+1)}));
}
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getParam(int)
*/
public final Object getParam(int idx) throws ServiceException {
checkParamAccess(idx);
return m_params[idx];
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#setParam(int, java.lang.Object)
*/
public final void setParam(int idx, Object value) throws ServiceException {
checkParamAccess(idx);
setParamInt(idx, value);
}
/**
* Internal function to set a parameter on the message.
* @param idx the index of the paremeter to be set
* @param value the parameter value
* @throws ServiceException
*/
protected final void setParamInt(int idx, Object value) throws ServiceException {
if (value != null) {
// no need to check index as we're in parallel arrays
List<Class> paramTypes = getParamTypes();
Class<?> clazz = paramTypes.get(idx);
if (!clazz.isAssignableFrom(value.getClass())) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_MSG_INCOMPATIBLE_SET,
ErrorConstants.ERRORDOMAIN, new Object[] {value.getClass().getName(), clazz.getName()}));
}
}
m_params[idx] = value;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getErrorResponse()
*/
public final Object getErrorResponse() throws ServiceException {
checkParams();
return m_errorResponse;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getG11nOptions()
*/
public final G11nOptions getG11nOptions() throws ServiceException {
return m_g11nOptions;
}
public void addMessageHeader(ObjectNode header) throws ServiceException {
if (null == header) {
return;
}
m_messageHeaders.add(header);
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getTransportHeaders()
*/
public final Map<String,String> getTransportHeaders() throws ServiceException {
if (m_transportHeaders == null) {
return CollectionUtils.EMPTY_STRING_MAP;
}
// we have to return a copy to avoid ConcurrentModification errors
return Collections.unmodifiableMap(new HashMap<String,String>(m_transportHeaders));
}
/**
* Internal function providing write access to the message's transport headers. Service and client writers
* should never call this.
* @return the internal header map
*/
protected final Map<String,String> getTransportHeadersNoCopy() throws ServiceException {
return m_transportHeaders;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getTransportHeader(java.lang.String)
*/
public final String getTransportHeader(String name) throws ServiceException {
if (m_transportHeaders != null && name != null) {
String value = m_transportHeaders.get(name);
if(value != null){
return value;
}
name = SOAHeaders.normalizeName(name, true);
return m_transportHeaders.get(name);
}
return null;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#hasTransportHeader(java.lang.String)
*/
public final boolean hasTransportHeader(String name) throws ServiceException {
if (m_transportHeaders != null && name != null) {
if(m_transportHeaders.containsKey(name)) {
return true;
}
name = SOAHeaders.normalizeName(name, true);
return m_transportHeaders.containsKey(name);
}
return false;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#setTransportHeader(java.lang.String, java.lang.String)
*/
public final void setTransportHeader(String name, String value) throws ServiceException {
if (name == null) {
throw new NullPointerException();
}
name = SOAHeaders.normalizeName(name, true);
value = SOAHeaders.normalizeValue(name, value);
if (m_transportHeaders == null) {
m_transportHeaders = new HashMap<String,String>();
}
m_transportHeaders.put(name, value);
if (hasAttachment()) {
m_attachments.transportHeaderAdded(name, value);
}
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#setCookie(Cookie)
*/
public final void setCookie(Cookie cookie) throws ServiceException {
if (m_cookies == null) {
m_cookies = new HashMap<String,Cookie>();
}
m_cookies.put(cookie.getName(), cookie);
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getCookie(java.lang.String)
*/
public final Cookie getCookie(String name) throws ServiceException {
if (name != null && m_cookies != null) {
return m_cookies.get(name.toUpperCase());
}
return null;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getCookies()
*/
public final Cookie[] getCookies() throws ServiceException {
if (m_cookies == null) {
return EMPTY_COOKIES;
}
Cookie[] result = m_cookies.values().toArray(new Cookie[m_cookies.size()]);
return result;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getByteBuffer()
*/
public ByteBuffer getByteBuffer() throws ServiceException {
return m_byteBuffer;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#setByteBuffer(java.nio.ByteBuffer)
*/
public void setByteBuffer(ByteBuffer buffer) throws ServiceException {
m_byteBuffer = buffer;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getTransportData()
*/
public Object getTransportData() throws ServiceException {
return m_transportData;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#setTransportData(java.lang.Object)
*/
public void setTransportData(Object data) throws ServiceException {
m_transportData = data;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#getMessageBody()
*/
public ObjectNode getMessageBody() throws ServiceException {
List<DataElementSchema> responseElements = getParamDesc().getRootElements();
if (responseElements == null || responseElements.isEmpty()) {
return null;
}
QName responseName = responseElements.get(0).getElementName();
if (getParamCount() == 0) {
return null;
}
Object object = getParam(0);
if (null == object) {
return null;
}
return new JavaObjectNodeImpl(responseName, object);
}
/**
* TODO - this should apparently be in InboundMessageImpl, not BaseMessageImpl. This matches functionality
* declared in InboundMessage.
* Get the data handler for this message, from which attachment streaming can occur.
* @param cid the attachment identifier.
* @return the data handler
*/
public DataHandler getDataHandler(String cid) throws ServiceException {
if (null == m_attachments) {
return null;
}
return m_attachments.getDataHandler(cid);
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.pipeline.Message#hasAttachment()
*/
public boolean hasAttachment() throws ServiceException {
return m_attachments != null;
}
protected QName getMessageXMLName(ServiceOperationParamDesc paramDesc, Class clz) throws ServiceException {
QName xmlName = paramDesc.getXmlNameForJavaType(clz);
if (null == xmlName) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_DATA_SERIALIZATION_ERROR,
ErrorConstants.ERRORDOMAIN, new Object[] {"mapped XML element name not found for class " + clz.getName()} ));
}
return xmlName;
}
protected Class getJavaTypeFromXMLName(ServiceOperationParamDesc paramDesc, QName eleName) throws ServiceException {
Class javaType = paramDesc.getJavaTypeForXmlName(eleName);
return javaType;
}
public void resetContext()
{
m_context = null;
}
public boolean isBufferingMode() {
return m_bufferingMode;
}
}