/******************************************************************************* * 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.spf.impl.internal.pipeline; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import java.util.logging.Level; import org.ebayopensource.turmeric.common.v1.types.CommonErrorData; import org.ebayopensource.turmeric.common.v1.types.ErrorCategory; import org.ebayopensource.turmeric.common.v1.types.ErrorMessage; import org.ebayopensource.turmeric.runtime.binding.BindingConstants; import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceCreationException; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceExceptionInterface; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceRuntimeException; import org.ebayopensource.turmeric.runtime.common.impl.internal.monitoring.SystemMetricDefs; import org.ebayopensource.turmeric.runtime.common.impl.internal.pipeline.MessageContextAccessorImpl; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.runtime.common.impl.utils.ReflectionUtils; import org.ebayopensource.turmeric.runtime.common.pipeline.Dispatcher; import org.ebayopensource.turmeric.runtime.common.pipeline.LoggingHandlerStage; 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.types.SOAConstants; import org.ebayopensource.turmeric.runtime.common.utils.Preconditions; import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants; import org.ebayopensource.turmeric.runtime.spf.exceptions.AppErrorWrapperException; import org.ebayopensource.turmeric.runtime.spf.pipeline.QueryCachePolicy; import org.ebayopensource.turmeric.runtime.spf.pipeline.ServiceImplFactory; import org.ebayopensource.turmeric.runtime.spf.pipeline.VersionCheckHandler; import org.ebayopensource.turmeric.runtime.spf.service.ServerServiceId; import com.ebay.kernel.logger.LogLevel; import com.ebay.kernel.logger.Logger; /** * Base class implementing functionality common for all code-generated service dispatchers. * This is a generic class parameterized by the type <code>T</code> of the service interface. * * @author ichernyshev */ public abstract class BaseServiceRequestDispatcher<T> implements Dispatcher { private static final String OP_GET_SERVICE_VERSION = "getServiceVersion"; private static Set<String> s_systemOpNames = new HashSet<String>(); private final Class<T> mGenServiceInterface; private final LinkedList<T> mServiceInstances = new LinkedList<T>(); private final Map<String,DispatchOperactionDef> mSupportedOps = new HashMap<String,DispatchOperactionDef>(); private ServerServiceId mServiceId; private ClassLoader mClassLoader; private String mServiceImplClassName; private int mServiceImplCount; private VersionCheckHandler mVersionCheckHandler; /* * The factory class name which implements the ServiceImplFactory interface. */ private String mServiceImplFactory; private boolean mCacheable; private static Logger LOGGER = Logger.getInstance( BaseServiceRequestDispatcher.class ); /** * Constructor; called by the type-specific derived class. * @param serviceInterface the Java class designating the service interface. */ protected BaseServiceRequestDispatcher(Class<T> serviceInterface) { Preconditions.checkNotNull(serviceInterface); mGenServiceInterface = serviceInterface; addSupportedOperation(SOAConstants.OP_GET_VERSION, null, new Class[] {Object.class}); addSupportedOperation(OP_GET_SERVICE_VERSION, null, new Class[] {Object.class}); addSupportedOperation(SOAConstants.OP_IS_SERVICE_VERSION_SUPPORTED, new Class[] {String.class}, new Class[] {Boolean.class}); addSupportedOperation(SOAConstants.OP_GET_CACHE_POLICY, null, new Class[] {String.class}); } /** * Adds the specified operation to the list of supported operations. Used by the type-specific derived class dispatcher * to register its coded operations, so that they can be cross-checked against the configured operations for the * service. * @param name the operation name being added * @param inParams the Java classes of the input parameters to the operation (as dispatched to the service implementation). * @param outParams the Java classes of the output parameters to the operation (as dispatched to the service implementation). */ protected final void addSupportedOperation(String name, Class[] inParams, Class[] outParams) { mSupportedOps.put(name, new DispatchOperactionDef(name, inParams, outParams)); } /** * Called by the framework to initialize the dispatcher at service initialization. * @param svcId the server-side service ID of the service for which this handler operates. * @param serviceInterface the class of the service interface (implemented by the * service implementation). * @param serviceImplClassName the class name of the service implementation. * @param cl the ClassLoader under which this service operates. * @param ops the collection of operation descriptions for this service (from the type * mapping configuration). * @param versionCheckHandler the version check handler used by this service. * @throws ServiceException */ public final void init(ServerServiceId svcId, Class serviceInterface, String serviceImplClassName, ClassLoader cl, Collection<ServiceOperationDesc> ops, VersionCheckHandler versionCheckHandler, String factoryClassName, boolean cacheable) throws ServiceException { Preconditions.checkNotNull(svcId); Preconditions.checkNotNull(serviceInterface); Preconditions.checkNotNull(cl); Preconditions.checkNotNull(ops); Preconditions.checkArgument(serviceImplClassName != null || factoryClassName != null); if (mGenServiceInterface != serviceInterface) { throw new ServiceCreationException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_FACTORY_GEN_USES_WRONG_INTERFACE, ErrorConstants.ERRORDOMAIN, new Object[] {this.getClass().getName(), mGenServiceInterface.getName(), serviceInterface.getName()})); } this.mServiceId = svcId; this.mServiceImplClassName = serviceImplClassName; this.mClassLoader = cl; this.mVersionCheckHandler = versionCheckHandler; this.mServiceImplFactory = factoryClassName; this.mCacheable = cacheable; if (mServiceImplClassName != null) { this.mCacheable = true; // create a single instance to make sure class is there T warmupInst = createServiceInstance(); returnServiceInstance(warmupInst); } verifyOperations(ops); } /** * Abstract method implemented by the specific typed service dispatcher class. * @param ctx the message context of the current invocation. * @param service passes an instance of the service implementation (which implements * <code>T</code>, the service interface). * @return true if the dispatch located the service method to which to dispatch the * current operation, otherwise false. * @throws ServiceException */ protected abstract boolean dispatch(MessageContext ctx, T service) throws ServiceException; /** * @param ctx the message context of the current invocation. * @param service passes an instance of the service implementation (which implements * <code>T</code>, the service interface). * @return true if the dispatch located the specified system operation (e.g. * getServiceVersion, isServiceVersionSupported). * @throws ServiceException */ private boolean dispatchSystemCall(MessageContext ctx, T service) throws ServiceException { String opName = ctx.getOperationName(); if (opName.equals(SOAConstants.OP_GET_VERSION) || opName.equals(OP_GET_SERVICE_VERSION)) { Message response = ctx.getResponseMessage(); String result = mVersionCheckHandler.getVersion(); if(((ServerMessageContextImpl)ctx).getServiceDesc().getConfig().getTypeMappings().getOperationAdded() && opName.equals(SOAConstants.OP_GET_VERSION)) response.setParam(0, result); else if(ctx.getOperation().getResponseType() != null && ctx.getOperation().getResponseType().getRootJavaTypes() != null){ Class clazz = ctx.getOperation().getResponseType().getRootJavaTypes().get(0); if(clazz.equals(String.class)) response.setParam(0, result); else { Object respObject = ReflectionUtils.createInstance(clazz); response.setParam(0, respObject); } } return true; } if (opName.equals(SOAConstants.OP_IS_SERVICE_VERSION_SUPPORTED)) { Message request = ctx.getRequestMessage(); Message response = ctx.getResponseMessage(); String version = (String)request.getParam(0); boolean result = mVersionCheckHandler.isVersionSupported(version); response.setParam(0, Boolean.valueOf(result)); return true; } if (opName.equals(SOAConstants.OP_GET_CACHE_POLICY)) { Message response = ctx.getResponseMessage(); QueryCachePolicy queryCachePolicy = new QueryCachePolicy(); String cachePolicy = queryCachePolicy.getCachePolicy(((ServerMessageContextImpl)ctx).getServiceDesc()); response.setParam(0, cachePolicy); return true; } return false; } /* (non-Javadoc) * @see org.ebayopensource.turmeric.runtime.common.pipeline.Dispatcher#dispatch(org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext) */ public final void dispatchSynchronously(MessageContext ctx) throws ServiceException { // trigger deserialization before logging/monitoring starts ServerMessageContextImpl ctxImpl = (ServerMessageContextImpl)ctx; /* Operation name can not be verified as the protobuf payload * does not have any operation related information */ if(!ctx.getPayloadType().equals(BindingConstants.PAYLOAD_PROTOBUF)) { ctxImpl.checkOperationName(); } ctx.getRequestMessage().getParamCount(); T service = getServiceInstance(ctx); ctxImpl.runLoggingHandlerStage(LoggingHandlerStage.REQUEST_DISPATCH_START); long startTime = System.nanoTime(); MessageContextAccessorImpl.setContext(ctx); try { boolean processed; if (s_systemOpNames.contains(ctx.getOperationName())) { processed = dispatchSystemCall(ctx, service); } else { processed = dispatch(ctx, service); } if (!processed) { throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_RT_OPERATION_UNKNOWN_AT_DISPATCH, ErrorConstants.ERRORDOMAIN, new Object[] {ctx.getAdminName() + "." + ctx.getOperationName()})); } } finally { MessageContextAccessorImpl.resetContext(); returnServiceInstance(service); long duration = System.nanoTime() - startTime; ctxImpl.updateSvcAndOpMetric(SystemMetricDefs.OP_TIME_CALL, startTime, duration); ctxImpl.runLoggingHandlerStage(LoggingHandlerStage.REQUEST_DISPATCH_COMPLETE); } } /* (non-Javadoc) * @see org.ebayopensource.turmeric.runtime.common.pipeline.Dispatcher#dispatch(org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext) */ public Future<?> dispatch(MessageContext ctx) throws ServiceException { // ServiceDispatcher is not allowed to implement async logic throw new UnsupportedOperationException(); } public void retrieve(MessageContext ctx, Future<?> name) throws ServiceException { throw new UnsupportedOperationException(); } /** * Handles an exceptions that occurs in the service implementation. Any contained * ErrorData are coerced to be error category <code>APPLICATION</code>, since errors * thrown by the service ipmlementation are application category by definition. * @param ctx the message context of the current invocation. * @param e the exception. * @throws ServiceException */ protected final void handleServiceException(MessageContext ctx, Throwable e) throws ServiceException { // reset error category to APPLICATION if (e instanceof ServiceExceptionInterface) { if (e instanceof ServiceException) { ((ServiceException)e).eraseSubcategory(); } if (e instanceof ServiceRuntimeException) { ((ServiceRuntimeException)e).eraseSubcategory(); } ServiceExceptionInterface e2 = (ServiceExceptionInterface)e; 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); /** * Only the SYSTEM category is not honored, since this is clearly an error raised * from the application space */ if ( ErrorCategory.SYSTEM.equals( errorData.getCategory() ) ) { if ( LOGGER.isLogEnabled( LogLevel.WARN ) ) LOGGER.log( LogLevel.WARN, "Mapping SYSTEM category to APPLICATION for errorData " + errorData.getErrorId() ); errorData.setCategory(ErrorCategory.APPLICATION); } } } } else { // wrap the exception, so that logger and error mapper can figure out it's an app error e = new AppErrorWrapperException(e); } // add to context only, ErrorMapper is responsible for setting ErrorResponse on the message ctx.addError(e); } /** * Private method to get service instance. * @param ctx * @return * @throws ServiceException */ private T getServiceInstance(MessageContext ctx) throws ServiceException { T result = null; if(mServiceImplClassName != null) { synchronized (this) { if (!mServiceInstances.isEmpty()) { result = mServiceInstances.removeFirst(); } } if (result == null) { result = createServiceInstance(); } } else { // Service Implementation Factory if(mCacheable) { synchronized (this) { if (!mServiceInstances.isEmpty()) { result = mServiceInstances.removeFirst(); } } if (result == null) { mServiceImplCount++; result = createServiceInstanceFromFactory(ctx); } } else { result = createServiceInstanceFromFactory(ctx); } } return result; } private synchronized void returnServiceInstance(T service) { if(mServiceImplClassName != null) { mServiceInstances.addFirst(service); } else { if(mCacheable) { mServiceInstances.addFirst(service); } } } private T createServiceInstance() throws ServiceException { if (mServiceId == null) { throw new IllegalStateException(this.getClass().getName() + " has not been initialized"); } T result = null; try { result = ReflectionUtils.createInstance(mServiceImplClassName, mGenServiceInterface, mClassLoader); } catch (ServiceException e) { ErrorMessage error = e.getErrorMessage(); List<CommonErrorData> elist = error.getError(); if (elist != null) { for (CommonErrorData edata : elist) { if (edata != null && edata.getErrorId() == 1007) { String message = edata.getMessage(); message += " \nPlease check the ServiceConfig.xml. " + "The class provided for the element 'service-impl-class-name' does not exist in the CLASSPATH."; edata.setMessage(message); throw e; } } } throw e; } mServiceImplCount++; if ((mServiceImplCount % 500) == 0) { tooManyInstances(); } return result; } @SuppressWarnings("unchecked") private T createServiceInstanceFromFactory(MessageContext context) throws ServiceException { if (mServiceId == null) { throw new IllegalStateException(this.getClass().getName() + " has not been initialized"); } T result = null; ServiceImplFactory<T> factory = null; try { factory = ReflectionUtils.createInstance(mServiceImplFactory, ServiceImplFactory.class, mClassLoader); } catch (ServiceException e) { ErrorMessage error = e.getErrorMessage(); List<CommonErrorData> elist = error.getError(); if (elist != null) { for (CommonErrorData edata : elist) { if (edata != null && edata.getErrorId() == 1007) { String message = edata.getMessage(); String service = context.getAdminName(); message += " \nPlease check the ServiceConfig.xml for the service '" + service + "'. " + "The class provided for the element 'service-impl-factory-class-name' does not exist in the CLASSPATH."; edata.setMessage(message); throw e; } } } throw e; } result = factory.createServiceImpl(context); if ((mServiceImplCount % 500) == 0) { tooManyInstances(); } return result; } private void tooManyInstances() { LogManager.getInstance(BaseServiceRequestDispatcher.class).log(Level.SEVERE, "Excessive creation of service instances " + mServiceImplClassName + " has been detected, " + mServiceImplCount + " instances have been created since service startup"); } private void verifyOperations(Collection<ServiceOperationDesc> ops) throws ServiceException { for (ServiceOperationDesc op: ops) { String name = op.getName(); /* if (s_systemOpNames.contains(name)) { return; } */ DispatchOperactionDef def = mSupportedOps.get(name); if (def == null) { throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_FACTORY_UNKNOWN_OPERATION_IN_CONFIG, ErrorConstants.ERRORDOMAIN, new Object[] {mServiceId.getAdminName() + "." + name})); } verifyParams(op.getRequestType().getRootJavaTypes(), def.m_inParams, name); verifyParams(op.getResponseType().getRootJavaTypes(), def.m_outParams, name); } } private void verifyParams(List<Class> configParams, Class[] realParams, String opName) throws ServiceException { if (configParams.size() == realParams.length) { return; } throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_FACTORY_INVALID_PARAM_COUNT_IN_CONFIG, ErrorConstants.ERRORDOMAIN, new Object[] {Integer.toString(realParams.length), mServiceId.getAdminName() + "." + opName, Integer.toString(configParams.size())})); } static { s_systemOpNames.add(SOAConstants.OP_GET_VERSION); s_systemOpNames.add(OP_GET_SERVICE_VERSION); s_systemOpNames.add(SOAConstants.OP_IS_SERVICE_VERSION_SUPPORTED); s_systemOpNames.add(SOAConstants.OP_GET_CACHE_POLICY); } private static class DispatchOperactionDef { final Class[] m_inParams; final Class[] m_outParams; DispatchOperactionDef(String name, Class[] inParams, Class[] outParams) { m_inParams = (inParams != null ? inParams : new Class[0]); m_outParams = (outParams != null ? outParams : new Class[0]); } } }