/*
* 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.management.internal.cli.functions;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
import org.apache.geode.cache.AttributesMutator;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.CacheListener;
import org.apache.geode.cache.CacheLoader;
import org.apache.geode.cache.CacheWriter;
import org.apache.geode.cache.ExpirationAction;
import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.FunctionAdapter;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.cache.execute.ResultSender;
import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.internal.InternalEntity;
import org.apache.geode.internal.cache.AbstractRegion;
import org.apache.geode.internal.cache.xmlcache.CacheXml;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.management.internal.cli.CliUtil;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.util.RegionPath;
import org.apache.geode.management.internal.configuration.domain.XmlEntity;
/**
* Function used by the 'alter region' gfsh command to alter a region on each member.
*
* @since GemFire 8.0
*/
public class RegionAlterFunction extends FunctionAdapter implements InternalEntity {
private static final Logger logger = LogService.getLogger();
private static final long serialVersionUID = -4846425364943216425L;
@Override
public boolean isHA() {
return false;
}
@Override
public void execute(FunctionContext context) {
ResultSender<Object> resultSender = context.getResultSender();
Cache cache = CacheFactory.getAnyInstance();
String memberNameOrId =
CliUtil.getMemberNameOrId(cache.getDistributedSystem().getDistributedMember());
RegionFunctionArgs regionAlterArgs = (RegionFunctionArgs) context.getArguments();
try {
Region<?, ?> alteredRegion = alterRegion(cache, regionAlterArgs);
XmlEntity xmlEntity = new XmlEntity(CacheXml.REGION, "name", alteredRegion.getName());
resultSender.lastResult(new CliFunctionResult(memberNameOrId, xmlEntity,
CliStrings.format(CliStrings.ALTER_REGION__MSG__REGION_0_ALTERED_ON_1,
new Object[] {alteredRegion.getFullPath(), memberNameOrId})));
} catch (IllegalStateException e) {
logger.error(e.getMessage(), e);
resultSender.lastResult(new CliFunctionResult(memberNameOrId, false, e.getMessage()));
} catch (IllegalArgumentException e) {
logger.error(e.getMessage(), e);
resultSender.lastResult(new CliFunctionResult(memberNameOrId, false, e.getMessage()));
} catch (VirtualMachineError e) {
SystemFailure.initiateFailure(e);
throw e;
} catch (Throwable th) {
SystemFailure.checkFailure();
logger.error(th.getMessage(), th);
String exceptionMsg = th.getMessage();
if (exceptionMsg == null) {
exceptionMsg = CliUtil.stackTraceAsString(th);
}
resultSender.lastResult(new CliFunctionResult(memberNameOrId, false, exceptionMsg));
}
}
private <K, V> Region<?, ?> alterRegion(Cache cache, RegionFunctionArgs regionAlterArgs) {
final String regionPathString = regionAlterArgs.getRegionPath();
RegionPath regionPath = new RegionPath(regionPathString);
AbstractRegion region = (AbstractRegion) cache.getRegion(regionPathString);
if (region == null) {
throw new IllegalArgumentException(CliStrings
.format(CliStrings.ALTER_REGION__MSG__REGION_DOESNT_EXIST_0, new Object[] {regionPath}));
}
AttributesMutator mutator = region.getAttributesMutator();
if (regionAlterArgs.isCloningEnabled() != null) {
mutator.setCloningEnabled(regionAlterArgs.isCloningEnabled());
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - cloning");
}
}
if (regionAlterArgs.getEvictionMax() != null) {
mutator.getEvictionAttributesMutator().setMaximum(regionAlterArgs.getEvictionMax());
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - eviction attributes max");
}
}
// Alter expiration attributes
final RegionFunctionArgs.ExpirationAttrs newEntryExpirationIdleTime =
regionAlterArgs.getEntryExpirationIdleTime();
if (newEntryExpirationIdleTime != null) {
mutator.setEntryIdleTimeout(
parseExpirationAttributes(newEntryExpirationIdleTime, region.getEntryIdleTimeout()));
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - entry idle timeout");
}
}
final RegionFunctionArgs.ExpirationAttrs newEntryExpirationTTL =
regionAlterArgs.getEntryExpirationTTL();
if (newEntryExpirationTTL != null) {
mutator.setEntryTimeToLive(
parseExpirationAttributes(newEntryExpirationTTL, region.getEntryTimeToLive()));
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - entry TTL");
}
}
final RegionFunctionArgs.ExpirationAttrs newRegionExpirationIdleTime =
regionAlterArgs.getRegionExpirationIdleTime();
if (newRegionExpirationIdleTime != null) {
mutator.setRegionIdleTimeout(
parseExpirationAttributes(newRegionExpirationIdleTime, region.getRegionIdleTimeout()));
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - region idle timeout");
}
}
final RegionFunctionArgs.ExpirationAttrs newRegionExpirationTTL =
regionAlterArgs.getRegionExpirationTTL();
if (newRegionExpirationTTL != null) {
mutator.setRegionTimeToLive(
parseExpirationAttributes(newRegionExpirationTTL, region.getRegionTimeToLive()));
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - region TTL");
}
}
// Alter Gateway Sender Ids
final Set<String> newGatewaySenderIds = regionAlterArgs.getGatewaySenderIds();
if (newGatewaySenderIds != null) {
// Remove old gateway sender ids that aren't in the new list
Set<String> oldGatewaySenderIds = region.getGatewaySenderIds();
if (!oldGatewaySenderIds.isEmpty()) {
for (String gatewaySenderId : oldGatewaySenderIds) {
if (!newGatewaySenderIds.contains(gatewaySenderId)) {
mutator.removeGatewaySenderId(gatewaySenderId);
}
}
}
// Add new gateway sender ids that don't already exist
for (String gatewaySenderId : newGatewaySenderIds) {
if (!oldGatewaySenderIds.contains(gatewaySenderId)) {
mutator.addGatewaySenderId(gatewaySenderId);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - gateway sender IDs");
}
}
// Alter Async Queue Ids
final Set<String> newAsyncEventQueueIds = regionAlterArgs.getAsyncEventQueueIds();
if (newAsyncEventQueueIds != null) {
// Remove old async event queue ids that aren't in the new list
Set<String> oldAsyncEventQueueIds = region.getAsyncEventQueueIds();
if (!oldAsyncEventQueueIds.isEmpty()) {
for (String asyncEventQueueId : oldAsyncEventQueueIds) {
if (!newAsyncEventQueueIds.contains(asyncEventQueueId)) {
mutator.removeAsyncEventQueueId(asyncEventQueueId);
}
}
}
// Add new async event queue ids that don't already exist
for (String asyncEventQueueId : newAsyncEventQueueIds) {
if (!oldAsyncEventQueueIds.contains(asyncEventQueueId)) {
mutator.addAsyncEventQueueId(asyncEventQueueId);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - async event queue IDs");
}
}
// Alter Cache Listeners
final Set<String> newCacheListenerNames = regionAlterArgs.getCacheListeners();
if (newCacheListenerNames != null) {
// Remove old cache listeners that aren't in the new list
CacheListener[] oldCacheListeners = region.getCacheListeners();
for (CacheListener oldCacheListener : oldCacheListeners) {
if (!newCacheListenerNames.contains(oldCacheListener.getClass().getName())) {
mutator.removeCacheListener(oldCacheListener);
}
}
// Add new cache listeners that don't already exist
for (String newCacheListenerName : newCacheListenerNames) {
if (newCacheListenerName.isEmpty()) {
continue;
}
boolean nameFound = false;
for (CacheListener oldCacheListener : oldCacheListeners) {
if (oldCacheListener.getClass().getName().equals(newCacheListenerName)) {
nameFound = true;
break;
}
}
if (!nameFound) {
Class<CacheListener<K, V>> cacheListenerKlass =
forName(newCacheListenerName, CliStrings.ALTER_REGION__CACHELISTENER);
mutator.addCacheListener(
newInstance(cacheListenerKlass, CliStrings.ALTER_REGION__CACHELISTENER));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - cache listeners");
}
}
final String cacheLoader = regionAlterArgs.getCacheLoader();
if (cacheLoader != null) {
if (cacheLoader.isEmpty()) {
mutator.setCacheLoader(null);
} else {
Class<CacheLoader<K, V>> cacheLoaderKlass =
forName(cacheLoader, CliStrings.ALTER_REGION__CACHELOADER);
mutator.setCacheLoader(newInstance(cacheLoaderKlass, CliStrings.ALTER_REGION__CACHELOADER));
}
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - cache loader");
}
}
final String cacheWriter = regionAlterArgs.getCacheWriter();
if (cacheWriter != null) {
if (cacheWriter.isEmpty()) {
mutator.setCacheWriter(null);
} else {
Class<CacheWriter<K, V>> cacheWriterKlass =
forName(cacheWriter, CliStrings.ALTER_REGION__CACHEWRITER);
mutator.setCacheWriter(newInstance(cacheWriterKlass, CliStrings.ALTER_REGION__CACHEWRITER));
}
if (logger.isDebugEnabled()) {
logger.debug("Region successfully altered - cache writer");
}
}
return region;
}
/**
* Converts the expiration attributes passed as arguments from the command to the function into a
* type suitable for applying to a Region.
*
* @param newExpirationAttrs Attributes supplied by the command
* @param oldExpirationAttributes Attributes currently applied to the Region.
*
* @return A new pair of expiration attributes taken from the command if it was given or the
* current value from the Region if it was not.
*/
private ExpirationAttributes parseExpirationAttributes(
RegionFunctionArgs.ExpirationAttrs newExpirationAttrs,
ExpirationAttributes oldExpirationAttributes) {
ExpirationAction action = oldExpirationAttributes.getAction();
int timeout = oldExpirationAttributes.getTimeout();
if (newExpirationAttrs.getTime() != null) {
timeout = newExpirationAttrs.getTime();
}
if (newExpirationAttrs.getAction() != null) {
action = newExpirationAttrs.getAction();
}
return new ExpirationAttributes(timeout, action);
}
@SuppressWarnings("unchecked")
private static <K> Class<K> forName(String classToLoadName, String neededFor) {
Class<K> loadedClass = null;
try {
// Set Constraints
ClassPathLoader classPathLoader = ClassPathLoader.getLatest();
if (classToLoadName != null && !classToLoadName.isEmpty()) {
loadedClass = (Class<K>) classPathLoader.forName(classToLoadName);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(
CliStrings.format(CliStrings.ALTER_REGION__MSG__COULDNOT_FIND_CLASS_0_SPECIFIED_FOR_1,
new Object[] {classToLoadName, neededFor}),
e);
} catch (ClassCastException e) {
throw new RuntimeException(CliStrings.format(
CliStrings.ALTER_REGION__MSG__CLASS_SPECIFIED_FOR_0_SPECIFIED_FOR_1_IS_NOT_OF_EXPECTED_TYPE,
new Object[] {classToLoadName, neededFor}), e);
}
return loadedClass;
}
private static <K> K newInstance(Class<K> klass, String neededFor) {
K instance = null;
try {
instance = klass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(CliStrings.format(
CliStrings.ALTER_REGION__MSG__COULDNOT_INSTANTIATE_CLASS_0_SPECIFIED_FOR_1,
new Object[] {klass, neededFor}), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
CliStrings.format(CliStrings.ALTER_REGION__MSG__COULDNOT_ACCESS_CLASS_0_SPECIFIED_FOR_1,
new Object[] {klass, neededFor}),
e);
}
return instance;
}
@Override
public String getId() {
return RegionAlterFunction.class.getName();
}
}