/* * 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.geode.cache.execute.internal; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionService; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.ClientCacheFactory; import org.apache.geode.cache.client.Pool; import org.apache.geode.cache.client.PoolManager; import org.apache.geode.cache.client.internal.ProxyCache; import org.apache.geode.cache.client.internal.ProxyRegion; import org.apache.geode.cache.execute.Execution; import org.apache.geode.cache.execute.Function; import org.apache.geode.cache.execute.FunctionException; import org.apache.geode.cache.execute.FunctionService; import org.apache.geode.cache.partition.PartitionRegionHelper; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.DistributedSystem; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.internal.InternalEntity; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.cache.LocalRegion; import org.apache.geode.internal.cache.execute.*; import org.apache.geode.internal.i18n.LocalizedStrings; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Provides the entry point into execution of user defined {@linkplain Function}s. * <p> * Function execution provides a means to route application behaviour to {@linkplain Region data} or * more generically to peers in a {@link DistributedSystem} or servers in a {@link Pool}. * </p> * * While {@link FunctionService} is a customer facing interface to this functionality, all of the * work is done here. In addition, internal only functionality is exposed in this class. * * @since GemFire 7.0 */ public class FunctionServiceManager { private final static ConcurrentHashMap<String, Function> idToFunctionMap = new ConcurrentHashMap<String, Function>(); /** * use when the optimization to execute onMember locally is not desired. */ public static final boolean RANDOM_onMember = Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "randomizeOnMember"); public FunctionServiceManager() {} /** * Returns an {@link Execution} object that can be used to execute a data dependent function on * the specified Region.<br> * When invoked from a GemFire client, the method returns an Execution instance that sends a * message to one of the connected servers as specified by the {@link Pool} for the region. <br> * Depending on the filters setup on the {@link Execution}, the function is executed on all * GemFire members that define the data region, or a subset of members. * {@link Execution#withFilter(Set)}). * * For DistributedRegions with DataPolicy.NORMAL, it throws UnsupportedOperationException. For * DistributedRegions with DataPolicy.EMPTY, execute the function on any random member which has * DataPolicy.REPLICATE <br> * . For DistributedRegions with DataPolicy.REPLICATE, execute the function locally. For Regions * with DataPolicy.PARTITION, it executes on members where the data resides as specified by the * filter. * * @param region * @return Execution * @throws FunctionException if the region passed in is null * @since GemFire 6.0 */ public final Execution onRegion(Region region) { if (region == null) { throw new FunctionException( LocalizedStrings.FunctionService_0_PASSED_IS_NULL.toLocalizedString("Region instance ")); } ProxyCache proxyCache = null; String poolName = region.getAttributes().getPoolName(); if (poolName != null) { Pool pool = PoolManager.find(poolName); if (pool.getMultiuserAuthentication()) { if (region instanceof ProxyRegion) { ProxyRegion pr = (ProxyRegion) region; region = pr.getRealRegion(); proxyCache = (ProxyCache) pr.getAuthenticatedCache(); } else { throw new UnsupportedOperationException(); } } } if (isClientRegion(region)) { return new ServerRegionFunctionExecutor(region, proxyCache); } if (PartitionRegionHelper.isPartitionedRegion(region)) { return new PartitionedRegionFunctionExecutor(region); } return new DistributedRegionFunctionExecutor(region); } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * a server in the provided {@link Pool}. * <p> * If the server goes down while dispatching or executing the function, an Exception will be * thrown. * * @param pool from which to chose a server for execution * @return Execution * @throws FunctionException if Pool instance passed in is null * @since GemFire 6.0 */ public final Execution onServer(Pool pool, String... groups) { if (pool == null) { throw new FunctionException( LocalizedStrings.FunctionService_0_PASSED_IS_NULL.toLocalizedString("Pool instance ")); } if (pool.getMultiuserAuthentication()) { throw new UnsupportedOperationException(); } return new ServerFunctionExecutor(pool, false, groups); } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * all the servers in the provided {@link Pool}. If one of the servers goes down while dispatching * or executing the function on the server, an Exception will be thrown. * * @param pool the set of servers to execute the function * @return Execution * @throws FunctionException if Pool instance passed in is null * @since GemFire 6.0 */ public final Execution onServers(Pool pool, String... groups) { if (pool == null) { throw new FunctionException( LocalizedStrings.FunctionService_0_PASSED_IS_NULL.toLocalizedString("Pool instance ")); } if (pool.getMultiuserAuthentication()) { throw new UnsupportedOperationException(); } return new ServerFunctionExecutor(pool, true, groups); } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * a server that the given cache is connected to. * <p> * If the server goes down while dispatching or executing the function, an Exception will be * thrown. * * @param regionService obtained from {@link ClientCacheFactory#create} or * {@link ClientCache#createAuthenticatedView(Properties)} . * @return Execution * @throws FunctionException if cache is null, is not on a client, or it does not have a default * pool * @since GemFire 6.5 */ public final Execution onServer(RegionService regionService, String... groups) { if (regionService == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("RegionService instance ")); } if (regionService instanceof GemFireCacheImpl) { GemFireCacheImpl gfc = (GemFireCacheImpl) regionService; if (!gfc.isClient()) { throw new FunctionException("The cache was not a client cache"); } else if (gfc.getDefaultPool() != null) { return onServer(gfc.getDefaultPool(), groups); } else { throw new FunctionException("The client cache does not have a default pool"); } } else { ProxyCache pc = (ProxyCache) regionService; return new ServerFunctionExecutor(pc.getUserAttributes().getPool(), false, pc, groups); } } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * all the servers that the given cache is connected to. If one of the servers goes down while * dispatching or executing the function on the server, an Exception will be thrown. * * @param regionService obtained from {@link ClientCacheFactory#create} or * {@link ClientCache#createAuthenticatedView(Properties)} . * @return Execution * @throws FunctionException if cache is null, is not on a client, or it does not have a default * pool * @since GemFire 6.5 */ public final Execution onServers(RegionService regionService, String... groups) { if (regionService == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("RegionService instance ")); } if (regionService instanceof GemFireCacheImpl) { GemFireCacheImpl gfc = (GemFireCacheImpl) regionService; if (!gfc.isClient()) { throw new FunctionException("The cache was not a client cache"); } else if (gfc.getDefaultPool() != null) { return onServers(gfc.getDefaultPool(), groups); } else { throw new FunctionException("The client cache does not have a default pool"); } } else { ProxyCache pc = (ProxyCache) regionService; return new ServerFunctionExecutor(pc.getUserAttributes().getPool(), true, pc, groups); } } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * a {@link DistributedMember} of the {@link DistributedSystem}. If the member is not found in the * system, the function execution will throw an Exception. If the member goes down while * dispatching or executing the function on the member, an Exception will be thrown. * * @param system defines the distributed system * @param distributedMember defines a member in the distributed system * @return Execution * @throws FunctionException if either input parameter is null * @since GemFire 6.0 * */ public final Execution onMember(DistributedSystem system, DistributedMember distributedMember) { if (system == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("DistributedSystem instance ")); } if (distributedMember == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("DistributedMember instance ")); } return new MemberFunctionExecutor(system, distributedMember); } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * all members of the {@link DistributedSystem}. If one of the members goes down while dispatching * or executing the function on the member, an Exception will be thrown. * * @param system defines the distributed system * @return Execution * * @throws FunctionException if DistributedSystem instance passed is null * @since GemFire 6.0 */ public final Execution onMembers(DistributedSystem system, String... groups) { if (system == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("DistributedSystem instance ")); } if (groups.length == 0) { return new MemberFunctionExecutor(system); } Set<DistributedMember> members = new HashSet<DistributedMember>(); for (String group : groups) { members.addAll(system.getGroupMembers(group)); } if (members.isEmpty()) { throw new FunctionException(LocalizedStrings.FunctionService_NO_MEMBERS_FOUND_IN_GROUPS .toLocalizedString(Arrays.toString(groups))); } return new MemberFunctionExecutor(system, members); } /** * Returns an {@link Execution} object that can be used to execute a data independent function on * the set of {@link DistributedMember}s of the {@link DistributedSystem}. If one of the members * goes down while dispatching or executing the function, an Exception will be thrown. * * @param system defines the distributed system * @param distributedMembers set of distributed members on which {@link Function} to be executed * @throws FunctionException if DistributedSystem instance passed is null * @since GemFire 6.0 */ public final Execution onMembers(DistributedSystem system, Set<DistributedMember> distributedMembers) { if (system == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("DistributedSystem instance ")); } if (distributedMembers == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("distributedMembers set ")); } return new MemberFunctionExecutor(system, distributedMembers); } /** * Returns the {@link Function} defined by the functionId, returns null if no function is found * for the specified functionId * * @param functionId * @return Function * @throws FunctionException if functionID passed is null * @since GemFire 6.0 */ public final Function getFunction(String functionId) { if (functionId == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("functionId instance ")); } return idToFunctionMap.get(functionId); } /** * Registers the given {@link Function} with the {@link FunctionService} using * {@link Function#getId()}. * <p> * Registering a function allows execution of the function using * {@link Execution#execute(String)}. Every member that could execute a function using its * {@link Function#getId()} should register the function. * </p> * * @throws FunctionException if function instance passed is null or Function.getId() returns null * @since GemFire 6.0 */ public final void registerFunction(Function function) { if (function == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("function instance ")); } if (function.getId() == null) { throw new FunctionException( LocalizedStrings.FunctionService_FUNCTION_GET_ID_RETURNED_NULL.toLocalizedString()); } if (function.isHA() && !function.hasResult()) { throw new FunctionException( LocalizedStrings.FunctionService_FUNCTION_ATTRIBUTE_MISMATCH.toLocalizedString()); } idToFunctionMap.put(function.getId(), function); } /** * Unregisters the given {@link Function} with the {@link FunctionService} using * {@link Function#getId()}. * <p> * * @throws FunctionException if function instance passed is null or Function.getId() returns null * @since GemFire 6.0 */ public final void unregisterFunction(String functionId) { if (functionId == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("functionId instance ")); } idToFunctionMap.remove(functionId); } /** * Returns true if the function is registered to FunctionService * * @throws FunctionException if function instance passed is null or Function.getId() returns null * @since GemFire 6.0 */ public final boolean isRegistered(String functionId) { if (functionId == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("functionId instance ")); } return idToFunctionMap.containsKey(functionId); } /** * Returns all locally registered functions * * @return A view of registered functions as a Map of {@link Function#getId()} to {@link Function} * @since GemFire 6.0 */ public final Map<String, Function> getRegisteredFunctions() { // We have to remove the internal functions before returning the map to the users final Map<String, Function> tempIdToFunctionMap = new HashMap<String, Function>(); for (Map.Entry<String, Function> entry : idToFunctionMap.entrySet()) { if (!(entry.getValue() instanceof InternalEntity)) { tempIdToFunctionMap.put(entry.getKey(), entry.getValue()); } } return tempIdToFunctionMap; } public final void unregisterAllFunctions() { // Unregistering all the functions registered with the FunctionService. Map<String, Function> functions = new HashMap<String, Function>(idToFunctionMap); for (String functionId : idToFunctionMap.keySet()) { unregisterFunction(functionId); } } /** * @param region * @return true if the method is called on a region has a {@link Pool}. * @since GemFire 6.0 */ private final boolean isClientRegion(Region region) { LocalRegion localRegion = (LocalRegion) region; return localRegion.hasServerProxy(); } public final Execution onMember(DistributedSystem system, String... groups) { if (system == null) { throw new FunctionException(LocalizedStrings.FunctionService_0_PASSED_IS_NULL .toLocalizedString("DistributedSystem instance ")); } Set<DistributedMember> members = new HashSet<DistributedMember>(); for (String group : groups) { List<DistributedMember> grpMembers = new ArrayList<DistributedMember>(system.getGroupMembers(group)); if (!grpMembers.isEmpty()) { if (!RANDOM_onMember && grpMembers.contains(system.getDistributedMember())) { members.add(system.getDistributedMember()); } else { Collections.shuffle(grpMembers); members.add(grpMembers.get(0)); } } } if (members.isEmpty()) { throw new FunctionException(LocalizedStrings.FunctionService_NO_MEMBERS_FOUND_IN_GROUPS .toLocalizedString(Arrays.toString(groups))); } return new MemberFunctionExecutor(system, members); } }