/******************************************************************************* * 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.config; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.namespace.QName; import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceCreationException; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.DomParseUtils; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.MessageProcessorConfigMapper; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.MetadataPropertyConfigHolder; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.NameValue; import org.ebayopensource.turmeric.runtime.common.impl.internal.config.OptionList; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.ServiceNameUtils; import org.ebayopensource.turmeric.runtime.common.monitoring.ErrorStatusOptions; import org.ebayopensource.turmeric.runtime.common.monitoring.MonitoringLevel; import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants; import org.ebayopensource.turmeric.runtime.spf.impl.internal.service.RequestParamsDescriptor; import org.ebayopensource.turmeric.runtime.spf.impl.transport.http.HTTPSupportedVerbs; import org.w3c.dom.Element; import org.w3c.dom.NodeList; // Copy non-null source data into the destination holder. // The very first time the ServiceInvokerOptions is initialized, public class ServiceConfigMapper { public static void map(String filename, Element serviceInstanceConfig, ServiceConfigHolder dst) throws ServiceCreationException { String errorClass = null; String errorDataProviderClass = null; NodeList customSerializers = null; Element providerOptions = DomParseUtils.getSingleElement(filename, serviceInstanceConfig, "provider-options"); if (providerOptions != null) { mapProviderOptions(filename, providerOptions, dst); errorClass = DomParseUtils.getElementText(filename, providerOptions, "error-mapping-handler-class-name"); errorDataProviderClass = DomParseUtils.getElementText(filename, providerOptions, "error-data-provider-class-name"); customSerializers = DomParseUtils.getImmediateChildrenByTagName( providerOptions, "custom-serializers"); } Element g11nOptions = DomParseUtils.getSingleElement(filename, serviceInstanceConfig, "G11N-options"); mapG11NOptions(filename, g11nOptions, dst); Element pipelineConfig = DomParseUtils.getSingleElement(filename, serviceInstanceConfig, "pipeline-config"); NodeList protocolProcessors = DomParseUtils. getImmediateChildrenByTagName(serviceInstanceConfig, "protocol-processor"); NodeList transports = DomParseUtils.getImmediateChildrenByTagName( serviceInstanceConfig, "transport"); Element dataBindingConfig = DomParseUtils.getSingleElement(filename, serviceInstanceConfig, "data-binding-config"); MessageProcessorConfigMapper.map(filename, customSerializers, errorClass, errorDataProviderClass, pipelineConfig, protocolProcessors, transports, dataBindingConfig, dst.getMessageProcessorConfig()); String serviceLayer = DomParseUtils.getElementText(filename, serviceInstanceConfig, "service-layer", false); if (serviceLayer != null) { dst.setServiceLayer(serviceLayer); } } private static void mapProviderOptions(String filename, Element providerOptions, ServiceConfigHolder dst) throws ServiceCreationException { if (providerOptions == null) { return; } // We're merging all the lists - supported global IDs, supported data bindings, // unsupported operation - as a complete replace; list values in the // service config (when specified) override the group config compltely // instead of being merged. List<String> unsupportedOperationsValues = DomParseUtils.getStringList( filename, providerOptions, "unsupported-operation"); if (unsupportedOperationsValues != null && !unsupportedOperationsValues.isEmpty()) { dst.setUnsupportedOperation(unsupportedOperationsValues); } List<String> supportedDataBindingsValues = DomParseUtils.getStringList( filename, providerOptions, "supported-data-bindings"); if (supportedDataBindingsValues != null && !supportedDataBindingsValues.isEmpty()) { dst.setSupportedDataBindings(supportedDataBindingsValues); } String monitoringLevelStr = DomParseUtils.getElementText(filename, providerOptions, "monitoring-level"); if (monitoringLevelStr != null) { MonitoringLevel level = DomParseUtils.mapMonitoringLevel(filename, monitoringLevelStr); dst.setMonitoringLevel(level); } String versionCheckHandler = DomParseUtils.getElementText(filename, providerOptions, "version-check-handler"); if (versionCheckHandler != null) { dst.setVersionCheckHandlerClassName(versionCheckHandler); } OptionList headerMappingOptions = DomParseUtils.getOptionList(filename, providerOptions, "header-mapping-options"); if (headerMappingOptions != null) { dst.setHeaderMappingOptions(headerMappingOptions); } OptionList requestHeaderMappingOptions = DomParseUtils.getOptionList( filename, providerOptions, "request-header-mapping-options"); if (requestHeaderMappingOptions != null) { dst.setRequestHeaderMappingOptions(requestHeaderMappingOptions); } OptionList responseHeaderMappingOptions = DomParseUtils.getOptionList( filename, providerOptions, "response-header-mapping-options"); if (responseHeaderMappingOptions != null) { dst.setResponseHeaderMappingOptions(responseHeaderMappingOptions); } String defaultRequestDataBinding = DomParseUtils.getElementText( filename, providerOptions, "default-request-data-binding"); if (defaultRequestDataBinding != null) { dst.setDefaultRequestDataBinding(defaultRequestDataBinding); } String defaultResponseDataBinding = DomParseUtils.getElementText( filename, providerOptions, "default-response-data-binding"); if (defaultResponseDataBinding != null) { dst.setDefaultResponseDataBinding(defaultResponseDataBinding); } Element errorStatusOptions = DomParseUtils.getSingleElement(filename, providerOptions, "error-status-options"); if (errorStatusOptions != null) { ErrorStatusOptions errorOptions = mapErrorStatusOptions(filename, errorStatusOptions); dst.setErrorStatusOptions(errorOptions); } Element operationMappingOptions = DomParseUtils.getSingleElement( filename, providerOptions, "operation-mapping-options"); if( operationMappingOptions != null ) { OperationMappings omo = mapOperationMappingOptions(filename, operationMappingOptions); dst.setOperationMappings(omo); } Element httpErrorMapperClass = DomParseUtils.getSingleElement(filename, providerOptions, "http-error-mapper-class-name"); if (httpErrorMapperClass != null) { String httpErrorMapperClassName = DomParseUtils .getText(httpErrorMapperClass); if (httpErrorMapperClassName == null || httpErrorMapperClassName.isEmpty()) { httpErrorMapperClassName = "org.ebayopensource.turmeric.runtime.spf.pipeline.DefaultHttpErrorMapper"; } dst.setHttpErrorMapper(httpErrorMapperClassName); } Element reqParamsMapping = DomParseUtils.getSingleElement(filename, providerOptions, "request-params-mapping"); if (reqParamsMapping != null) { dst.setRequestParamsDescriptor(mapOperationReqParamsMappings( filename, reqParamsMapping)); verifyPathIndicesUniqueness(dst, filename); } } private static void verifyPathIndicesUniqueness(ServiceConfigHolder dst, String filename) throws ServiceCreationException { RequestParamsDescriptor requestParamDesc = dst.getRequestParamsDescriptor(); if(requestParamDesc == null) { return; } Set<String> paramIndices = requestParamDesc.getPathIndices(); OptionList headerMappings = dst.getHeaderMappingOptions(); if(headerMappings == null) { return; } List<NameValue> nvList = headerMappings.getOption(); if(nvList == null || nvList.isEmpty()) { return; } for(NameValue nv : nvList) { String index = nv.getValue(); index = index.substring(index.indexOf("path[") + 5, index. indexOf(']')); if(index.startsWith("+")) { index = index.replace('+', '-'); } if(paramIndices.contains(index)) { throwError(filename, "Duplicates indices for url path elements"); } } } private static RequestParamsDescriptor mapOperationReqParamsMappings( String filename, Element operationReqParamsMapping) throws ServiceCreationException { RequestParamsDescriptor requestParams = new RequestParamsDescriptor(); NodeList operations = DomParseUtils.getImmediateChildrenByTagName( operationReqParamsMapping, "operation"); final Set<String> knownOperations = new HashSet<String>(); int numOperations = operations.getLength(); for(int i=0; i < numOperations; ++i) { Element operation = (Element) operations.item(i); String operationName = operation.getAttribute("name"); if( operationName == null ) { DomParseUtils.throwError(filename, "Missing operation name in operation element: '" + operation + "'"); } if(knownOperations.contains(operationName)) { throwError(filename, "Duplicate definition for request params for " + "operation '" + operationName + "'"); } NodeList params = DomParseUtils.getImmediateChildrenByTagName( operation, "option"); int paramsLen = params.getLength(); for(int j=0; j < paramsLen; ++j) { Element option = (Element) params.item(j); String name = option.getAttribute("name"); String alias = option.getAttribute("alias"); if (name == null || name.isEmpty()) { throwError(filename, "Missing option name in option list " + "\"operation\" "); } String value = DomParseUtils.getText(option); if (value == null) { throwError(filename, "Missing option value for option list " + "\"operation\" "); } try { String index = value.substring(value.indexOf("path[") + 5, value.indexOf(']')); if(index.startsWith("+")) { index = index.replace('+', '-'); } try { Integer.parseInt(index); } catch(NumberFormatException e) { throwError(filename, "Invalid value for request uri " + "path index. An integer is expected. Given :" + value); } if(!requestParams.map(operationName, index, name, alias)) { throwError(filename, "Invalid configuration for request " + "param mapping. Check for duplicate path indices or " + "duplicate aliases or duplicate request param names"); } } catch(StringIndexOutOfBoundsException exception) { throwError(filename, "Invalid value for request uri index. " + "It must of form 'path[i]'"); } } knownOperations.add(operationName); } return requestParams; } private static OperationMappings mapOperationMappingOptions(String filename, Element operationMappingOptions) throws ServiceCreationException { OperationMappings omOptions = new OperationMappings(); NodeList operations = DomParseUtils.getImmediateChildrenByTagName( operationMappingOptions, "operation"); for(int i=0; i<operations.getLength(); i++) { Element operation = (Element)operations.item(i); String name = operation.getAttribute("name"); if( name == null ) { DomParseUtils.throwError(filename, "Missing " + "operation name in operation: '" + operation + "'"); } String verb = operation.getAttribute("verb"); if (verb == null || verb.isEmpty()) { verb = ""; } else { try { verb = verb.toUpperCase(); HTTPSupportedVerbs.valueOf(verb); } catch (Exception e) { DomParseUtils.throwError(filename, "Unsupported Http Verb supplied (supported values are : " + HTTPSupportedVerbs.getAllValues() + "): '" + verb + "'"); } } String value = DomParseUtils.getText(operation); if (value == null) { throwError(filename, "Missing option value for " + "option list: '" + operation + "'"); } // i.e if the Verb is delete value = verb + value; OperationMapping old = omOptions.getOperationMapping(value); if( old != null ) { throwError(filename, "Duplicate options specified with key: " + value); } OperationMapping om = new OperationMapping(name, value); omOptions.add(om); } return omOptions; } private static ErrorStatusOptions mapErrorStatusOptions(String filename, Element errorStatusOptions) throws ServiceCreationException { ErrorStatusOptions errorOptions = new ErrorStatusOptions(); String metric = DomParseUtils.getElementText(filename, errorStatusOptions, "metric"); if (metric != null) { errorOptions.setMetric(metric); } String threshold = DomParseUtils.getElementText(filename, errorStatusOptions, "threshold"); if (threshold != null) { errorOptions.setThreshold(threshold); } Integer sampleSize = DomParseUtils.getElementInteger(filename, errorStatusOptions, "sample-size"); if (sampleSize != null) { errorOptions.setSampleSize(sampleSize.intValue()); } return errorOptions; } private static void mapG11NOptions(String filename, Element g11nOptions, ServiceConfigHolder dst) throws ServiceCreationException { if (g11nOptions == null) return; String defaultEncoding = DomParseUtils.getElementText(filename, g11nOptions, "default-encoding"); if (defaultEncoding != null) { dst.setDefaultEncoding(defaultEncoding); } List<String> supportedGlobalIdList = DomParseUtils.getStringList( filename, g11nOptions, "supported-global-id"); if (supportedGlobalIdList != null) { Set<String> supportedGlobalIdSet = new HashSet<String>(); for (String supportedGlobalId : supportedGlobalIdList) { if (supportedGlobalId.length() > 0) { supportedGlobalIdSet.add(supportedGlobalId); } } dst.setSupportedGlobalId(supportedGlobalIdSet); } List<String> supportedLocaleValueList = DomParseUtils.getStringList( filename, g11nOptions, "supported-locale"); if (supportedLocaleValueList != null) { Set<String> supportedLocaleValues = new HashSet<String>( supportedLocaleValueList); dst.setSupportedLocale(supportedLocaleValues); } } public static ServiceConfigHolder applyConfigs(String adminName, String configFilename, String groupFilename, Element serviceGroup, Element serviceConfig) throws ServiceCreationException, IOException { ServiceConfigHolder dst = new ServiceConfigHolder(adminName); dst.setConfigFilename(configFilename); dst.setGroupFilename(groupFilename); if (serviceGroup != null) { Element serviceInstanceInGroup = DomParseUtils.getSingleElement( groupFilename, serviceGroup, "service-config"); if (serviceInstanceInGroup != null) { // we allow client instance config to be absent - this would be a trivial config with no overridden parameter values. map(groupFilename, serviceInstanceInGroup, dst); } } if (serviceConfig == null) { // Shouldn't happen - this is the top-level document element. throwError(configFilename, "Missing service-config section"); return null; // throws error above } Element serviceInstance = DomParseUtils.getSingleElement( configFilename, serviceConfig, "service-instance-config"); if (serviceInstance != null) { map(configFilename, serviceInstance, dst); } /* * Create a new MetadataPropertyConfigHolder object out of service_metadata.properties file * The holder will have all the properties defined in service_metadata.properties. * Populate the ServiceConfigHolder with the MetadataPropertyConfigHolder for future references. */ MetadataPropertyConfigHolder metadataProps = null; metadataProps = ServiceConfigManager.getInstance(). getMetadataPropertyConfigHolder(adminName); dst.setMetaData(metadataProps); String serviceName = null; /* * Services created on pre-1.0 of SOA Framework do not have service_metadata.properties * e.g. Some QA services and ShippingCalculatorService */ if(metadataProps == null) { serviceName = serviceConfig.getAttribute("service-name"); } else { double smpVersion = metadataProps.getSmpVersion(); if(smpVersion == -1d) { throwError(configFilename, "smp_version property has invalid value"); } /* * smp_version property greater than or equal to 1.1 implies that * service name has to constructed using properties in the * service_metadata.properties. Otherwise, we read the service name * from service-config.xml */ if (smpVersion >= 1.1) { StringBuilder serviceqname = new StringBuilder(); serviceqname.append('{').append( metadataProps.getServiceNamespace()).append('}'); serviceqname.append(metadataProps.getServiceName()); serviceName = serviceqname.toString(); } else { serviceName = serviceConfig.getAttribute("service-name"); } } if (serviceName == null) { throwError(configFilename, "Missing service name"); } QName qname = ServiceNameUtils.normalizeQName(QName.valueOf(serviceName)); dst.setServiceQName(qname); /* * Service Implementation class must be provided in one of the two ways. * 1. Service Implementation class name. * 2. Service Implementation factory class name. */ String serviceImplClassName = DomParseUtils.getElementText( configFilename, serviceConfig, "service-impl-class-name", false); String serviceFactory = DomParseUtils.getElementText(configFilename, serviceConfig, "service-impl-factory-class-name", false); if(serviceImplClassName != null) { dst.setServiceImplClassName(serviceImplClassName); } else { // this means post 2.8 service if(serviceFactory != null) { dst.setServiceImplFactoryClassName(serviceFactory); String cacheable = DomParseUtils.getAttribute(configFilename, serviceConfig, "service-impl-factory-class-name", "cacheable"); boolean implCached = "true".equalsIgnoreCase(cacheable); dst.setImplCached(implCached); } else { // Required: service-impl-class-name // Same error message for backward compatibility throwError(configFilename, "Missing required element: " + "'service-impl-class-name'"); } } if(serviceImplClassName != null && serviceFactory != null) { throwError(configFilename, "Specify one of 'service-impl-class-name' " + "or 'service-impl-factory-class-name', not both."); } // Required: service-interface-class-name String serviceInterfaceClassName = DomParseUtils.getElementText( configFilename, serviceConfig, "service-interface-class-name", true); dst.setServiceInterfaceClassName(serviceInterfaceClassName); List<String> supportedVersionsStr = DomParseUtils.getStringList( configFilename, serviceConfig, "supported-version"); if (supportedVersionsStr != null && !supportedVersionsStr.isEmpty()) { dst.setSupportedVersions(supportedVersionsStr); } return dst; } // TODO - many of the errors thrown in this way should be individual exceptions so the text can // be localized better. private static void throwError(String filename, String cause) throws ServiceCreationException { throw new ServiceCreationException(ErrorDataFactory.createErrorData( ErrorConstants.CFG_VALIDATION_ERROR, ErrorConstants.ERRORDOMAIN, new Object[] {filename, cause})); } }