/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.apache.ofbiz.service; import java.io.Serializable; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.Future; import javax.wsdl.WSDLException; import org.apache.ofbiz.base.component.ComponentConfig; import org.apache.ofbiz.base.concurrent.ExecutionPool; import org.apache.ofbiz.base.config.GenericConfigException; import org.apache.ofbiz.base.config.MainResourceHandler; import org.apache.ofbiz.base.config.ResourceHandler; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.cache.UtilCache; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityConfException; import org.apache.ofbiz.entity.config.model.DelegatorElement; import org.apache.ofbiz.entity.config.model.EntityConfig; import org.apache.ofbiz.security.Security; import org.apache.ofbiz.service.config.ServiceConfigUtil; import org.apache.ofbiz.service.config.model.GlobalServices; import org.apache.ofbiz.service.eca.ServiceEcaUtil; import org.w3c.dom.Document; /** * Dispatcher Context */ @SuppressWarnings("serial") public class DispatchContext implements Serializable { public static final String module = DispatchContext.class.getName(); private static final UtilCache<String, Map<String, ModelService>> modelServiceMapByModel = UtilCache.createUtilCache("service.ModelServiceMapByModel", 0, 0, false); // these four fields represent the immutable state of a DispatchContext object private final String name; private final transient ClassLoader loader; private final transient LocalDispatcher dispatcher; private final String model; /** * Creates new DispatchContext as an immutable object. * The "dispatcher" argument can be null if the "name" argument matches the name of a valid entity model reader. * The thread safety of a DispatchContext object is a consequence of its immutability. * * @param name The immutable name of the DispatchContext * @param loader The immutable class loader * @param dispatcher The immutable dispatcher associated to the DispatchContext * */ public DispatchContext(String name, ClassLoader loader, LocalDispatcher dispatcher) { this.name = name; this.loader = loader; this.dispatcher = dispatcher; String modelName = null; if (this.dispatcher != null) { Delegator delegator = dispatcher.getDelegator(); if (delegator != null) { DelegatorElement delegatorInfo = null; try { delegatorInfo = EntityConfig.getInstance().getDelegator(delegator.getDelegatorBaseName()); } catch (GenericEntityConfException e) { Debug.logWarning(e, "Exception thrown while getting delegator config: ", module); } if (delegatorInfo != null) { modelName = delegatorInfo.getEntityModelReader(); } } } if (modelName == null) { // if a modelName is not associated to the dispatcher (e.g. dispatcher is null) then use the name // of the DispatchContext as the model reader name modelName = name; } this.model = modelName; getGlobalServiceMap(); } /** * Gets the classloader of this context * @return ClassLoader of the context */ public ClassLoader getClassLoader() { return this.loader; } /** * Gets the name of the local dispatcher * @return String name of the LocalDispatcher object */ public String getName() { return name; } /** * Gets the LocalDispatcher used with this context * @return LocalDispatcher that was used to create this context */ public LocalDispatcher getDispatcher() { return this.dispatcher; } /** * Gets the Delegator associated with this context/dispatcher * @return Delegator associated with this context */ public Delegator getDelegator() { return dispatcher.getDelegator(); } /** * Gets the Security object associated with this dispatcher * @return Security object associated with this dispatcher */ public Security getSecurity() { return dispatcher.getSecurity(); } // All the methods that follow are helper methods to retrieve service model information from cache (and manage the cache) // The cache object is static but most of these methods are not because the same service definition, is used with different // DispatchContext objects may result in different in/out attributes: this happens because the DispatchContext is associated to // a LocalDispatcher that is associated to a Delegator that is associated to a ModelReader; different ModelReaders could load the // same entity name from different files with different fields, and the service definition could automatically get the input/output // attributes from an entity. /** * Uses an existing map of name value pairs and extracts the keys which are used in serviceName * Note: This goes not guarantee the context will be 100% valid, there may be missing fields * @param serviceName The name of the service to obtain parameters for * @param mode The mode to use for building the new map (i.e. can be IN or OUT) * @param context The initial set of values to pull from * @return Map contains any valid values * @throws GenericServiceException */ public Map<String, Object> makeValidContext(String serviceName, String mode, Map<String, ? extends Object> context) throws GenericServiceException { ModelService model = getModelService(serviceName); return makeValidContext(model, mode, context); } /** * Uses an existing map of name value pairs and extracts the keys which are used in serviceName * Note: This goes not guarantee the context will be 100% valid, there may be missing fields * @param model The ModelService object of the service to obtain parameters for * @param mode The mode to use for building the new map (i.e. can be IN or OUT) * @param context The initial set of values to pull from * @return Map contains any valid values * @throws GenericServiceException */ public static Map<String, Object> makeValidContext(ModelService model, String mode, Map<String, ? extends Object> context) throws GenericServiceException { Map<String, Object> newContext; int modeInt = 0; if (mode.equalsIgnoreCase("in")) { modeInt = 1; } else if (mode.equalsIgnoreCase("out")) { modeInt = 2; } if (model == null) { throw new GenericServiceException("Model service is null! Should never happen."); } else { switch (modeInt) { case 2: newContext = model.makeValid(context, ModelService.OUT_PARAM, true, null); break; case 1: newContext = model.makeValid(context, ModelService.IN_PARAM, true, null); break; default: throw new GenericServiceException("Invalid mode, should be either IN or OUT"); } return newContext; } } /** * Gets the ModelService instance that corresponds to given the name * @param serviceName Name of the service * @return GenericServiceModel that corresponds to the serviceName */ public ModelService getModelService(String serviceName) throws GenericServiceException { Map<String, ModelService> serviceMap = getGlobalServiceMap(); ModelService retVal = null; if (serviceMap != null) { retVal = serviceMap.get(serviceName); if (retVal != null && !retVal.inheritedParameters()) { retVal.interfaceUpdate(this); } } if (retVal == null) { throw new GenericServiceException("Cannot locate service by name (" + serviceName + ")"); } return retVal; } public Set<String> getAllServiceNames() { Set<String> serviceNames = new TreeSet<String>(); Map<String, ModelService> globalServices = modelServiceMapByModel.get(this.model); if (globalServices != null) { serviceNames.addAll(globalServices.keySet()); } return serviceNames; } public Document getWSDL(String serviceName, String locationURI) throws GenericServiceException, WSDLException { ModelService model = this.getModelService(serviceName); return model.toWSDL(locationURI); } private Callable<Map<String, ModelService>> createServiceReaderCallable(final ResourceHandler handler) { return new Callable<Map<String, ModelService>>() { public Map<String, ModelService> call() throws Exception { return ModelServiceReader.getModelServiceMap(handler, DispatchContext.this.getDelegator()); } }; } private Map<String, ModelService> getGlobalServiceMap() { Map<String, ModelService> serviceMap = modelServiceMapByModel.get(this.model); if (serviceMap == null) { serviceMap = new HashMap<String, ModelService>(); List<Future<Map<String, ModelService>>> futures = new LinkedList<Future<Map<String, ModelService>>>(); List<GlobalServices> globalServicesList = null; try { globalServicesList = ServiceConfigUtil.getServiceEngine().getGlobalServices(); } catch (GenericConfigException e) { // FIXME: Refactor API so exceptions can be thrown and caught. Debug.logError(e, module); throw new RuntimeException(e.getMessage()); } for (GlobalServices globalServices : globalServicesList) { ResourceHandler handler = new MainResourceHandler(ServiceConfigUtil.getServiceEngineXmlFileName(), globalServices.getLoader(), globalServices.getLocation()); futures.add(ExecutionPool.GLOBAL_FORK_JOIN.submit(createServiceReaderCallable(handler))); } // get all of the component resource model stuff, ie specified in each ofbiz-component.xml file for (ComponentConfig.ServiceResourceInfo componentResourceInfo: ComponentConfig.getAllServiceResourceInfos("model")) { futures.add(ExecutionPool.GLOBAL_FORK_JOIN.submit(createServiceReaderCallable(componentResourceInfo.createResourceHandler()))); } for (Map<String, ModelService> servicesMap: ExecutionPool.getAllFutures(futures)) { if (servicesMap != null) { serviceMap.putAll(servicesMap); } } if (serviceMap != null) { Map<String, ModelService> cachedServiceMap = modelServiceMapByModel.putIfAbsentAndGet(this.model, serviceMap); if (cachedServiceMap == serviceMap) { // same object: this means that the object created by this thread was actually added to the cache ServiceEcaUtil.reloadConfig(); } } } return serviceMap; } }