/* * 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.beans; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.logging.log4j.Logger; import org.apache.geode.SystemFailure; import org.apache.geode.cache.Cache; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.Region; import org.apache.geode.cache.execute.Function; import org.apache.geode.cache.execute.FunctionAdapter; import org.apache.geode.cache.execute.FunctionContext; import org.apache.geode.cache.execute.FunctionException; import org.apache.geode.cache.execute.FunctionService; import org.apache.geode.cache.execute.RegionFunctionContext; import org.apache.geode.cache.execute.ResultCollector; import org.apache.geode.cache.query.Query; import org.apache.geode.cache.query.QueryInvalidException; import org.apache.geode.cache.query.QueryService; import org.apache.geode.cache.query.SelectResults; import org.apache.geode.cache.query.internal.CompiledValue; import org.apache.geode.cache.query.internal.DefaultQuery; import org.apache.geode.cache.query.internal.QCompiler; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.internal.InternalEntity; import org.apache.geode.internal.cache.BucketRegion; import org.apache.geode.internal.cache.LocalDataSet; import org.apache.geode.internal.cache.PartitionedRegion; import org.apache.geode.internal.cache.PartitionedRegionHelper; import org.apache.geode.internal.logging.LogService; import org.apache.geode.management.DistributedRegionMXBean; import org.apache.geode.management.ManagementService; import org.apache.geode.management.internal.ManagementConstants; import org.apache.geode.management.internal.ManagementStrings; import org.apache.geode.management.internal.SystemManagementService; import org.apache.geode.management.internal.cli.commands.DataCommands; import org.apache.geode.management.internal.cli.json.GfJsonException; import org.apache.geode.management.internal.cli.json.GfJsonObject; import org.apache.geode.management.internal.cli.json.TypedJson; /** * This function is executed on one or multiple members based on the member input to * DistributedSystemMXBean.queryData() */ @SuppressWarnings({"deprecation", "unchecked"}) public class QueryDataFunction extends FunctionAdapter implements InternalEntity { private static final long serialVersionUID = 1L; private static final Logger logger = LogService.getLogger(); private static final String MEMBER_KEY = "member"; private static final String RESULT_KEY = "result"; private static final String NO_DATA_FOUND = "No Data Found"; private static final String QUERY_EXEC_SUCCESS = "Query Executed Successfully"; private static final int DISPLAY_MEMBERWISE = 0; private static final int QUERY = 1; private static final int REGION = 2; private static final int LIMIT = 3; private static final int QUERY_RESULTSET_LIMIT = 4; private static final int QUERY_COLLECTIONS_DEPTH = 5; private static final String SELECT_EXPR = "\\s*SELECT\\s+.+\\s+FROM.+"; private static final Pattern SELECT_EXPR_PATTERN = Pattern.compile(SELECT_EXPR, Pattern.CASE_INSENSITIVE); private static final String SELECT_WITH_LIMIT_EXPR = "\\s*SELECT\\s+.+\\s+FROM(\\s+|(.*\\s+))LIMIT\\s+[0-9]+.*"; private static final Pattern SELECT_WITH_LIMIT_EXPR_PATTERN = Pattern.compile(SELECT_WITH_LIMIT_EXPR, Pattern.CASE_INSENSITIVE); @Override public boolean hasResult() { return true; } @Override public void execute(final FunctionContext context) { Object[] functionArgs = (Object[]) context.getArguments(); boolean showMember = (Boolean) functionArgs[DISPLAY_MEMBERWISE]; String queryString = (String) functionArgs[QUERY]; String regionName = (String) functionArgs[REGION]; int limit = (Integer) functionArgs[LIMIT]; int queryResultSetLimit = (Integer) functionArgs[QUERY_RESULTSET_LIMIT]; int queryCollectionsDepth = (Integer) functionArgs[QUERY_COLLECTIONS_DEPTH]; try { context.getResultSender().lastResult(selectWithType(context, queryString, showMember, regionName, limit, queryResultSetLimit, queryCollectionsDepth)); } catch (Exception e) { context.getResultSender().sendException(e); } } @Override public String getId() { return ManagementConstants.QUERY_DATA_FUNCTION; } private QueryDataFunctionResult selectWithType(final FunctionContext context, String queryString, final boolean showMember, final String regionName, final int limit, final int queryResultSetLimit, final int queryCollectionsDepth) throws Exception { Cache cache = CacheFactory.getAnyInstance(); Function loclQueryFunc = new LocalQueryFunction("LocalQueryFunction", regionName, showMember) .setOptimizeForWrite(true); queryString = applyLimitClause(queryString, limit, queryResultSetLimit); try { TypedJson result = new TypedJson(queryCollectionsDepth); Region region = cache.getRegion(regionName); if (region == null) { throw new Exception( ManagementStrings.QUERY__MSG__REGIONS_NOT_FOUND_ON_MEMBER.toLocalizedString(regionName, cache.getDistributedSystem().getDistributedMember().getId())); } Object results = null; boolean noDataFound = true; if (region.getAttributes().getDataPolicy() == DataPolicy.NORMAL) { QueryService queryService = cache.getQueryService(); Query query = queryService.newQuery(queryString); results = query.execute(); } else { ResultCollector rcollector = null; PartitionedRegion parRegion = PartitionedRegionHelper.getPartitionedRegion(regionName, cache); if (parRegion != null && showMember) { if (parRegion.isDataStore()) { Set<BucketRegion> localPrimaryBucketRegions = parRegion.getDataStore().getAllLocalPrimaryBucketRegions(); Set<Integer> localPrimaryBucketSet = new HashSet<>(); for (BucketRegion bRegion : localPrimaryBucketRegions) { localPrimaryBucketSet.add(bRegion.getId()); } LocalDataSet lds = new LocalDataSet(parRegion, localPrimaryBucketSet); DefaultQuery query = (DefaultQuery) cache.getQueryService().newQuery(queryString); SelectResults selectResults = (SelectResults) lds.executeQuery(query, null, localPrimaryBucketSet); results = selectResults; } } else { rcollector = FunctionService.onRegion(cache.getRegion(regionName)).withArgs(queryString) .execute(loclQueryFunc); results = rcollector.getResult(); } } if (results != null && results instanceof SelectResults) { SelectResults selectResults = (SelectResults) results; for (Iterator iter = selectResults.iterator(); iter.hasNext();) { Object object = iter.next(); result.add(RESULT_KEY, object); noDataFound = false; } } else if (results != null && results instanceof ArrayList) { ArrayList listResults = (ArrayList) results; ArrayList actualResult = (ArrayList) listResults.get(0); for (Object object : actualResult) { result.add(RESULT_KEY, object); noDataFound = false; } } if (!noDataFound && showMember) { result.add(MEMBER_KEY, cache.getDistributedSystem().getDistributedMember().getId()); } if (noDataFound) { return new QueryDataFunctionResult(QUERY_EXEC_SUCCESS, BeanUtilFuncs.compress(new JsonisedErroMessage(NO_DATA_FOUND).toString())); } return new QueryDataFunctionResult(QUERY_EXEC_SUCCESS, BeanUtilFuncs.compress(result.toString())); } catch (Exception e) { logger.warn(e.getMessage(), e); throw e; } } /** * Matches the input query with query with limit pattern. If limit is found in input query this * function ignores. Else it will append a default limit .. 1000 If input limit is 0 then also it * will append default limit of 1000 * * @param query input query * @param limit limit on the result set * * @return a string having limit clause */ protected static String applyLimitClause(final String query, int limit, final int queryResultSetLimit) { Matcher matcher = SELECT_EXPR_PATTERN.matcher(query); if (matcher.matches()) { Matcher limit_matcher = SELECT_WITH_LIMIT_EXPR_PATTERN.matcher(query); boolean queryAlreadyHasLimitClause = limit_matcher.matches(); if (!queryAlreadyHasLimitClause) { if (limit == 0) { limit = queryResultSetLimit; } String result = query; result += " LIMIT " + limit; return result; } } return query; } private static Object callFunction(final Object functionArgs, final Set<DistributedMember> members, final boolean zipResult) throws Exception { try { if (members.size() == 1) { DistributedMember member = members.iterator().next(); ResultCollector collector = FunctionService.onMember(member).withArgs(functionArgs) .execute(ManagementConstants.QUERY_DATA_FUNCTION); List list = (List) collector.getResult(); Object object = null; if (list.size() > 0) { object = list.get(0); } if (object instanceof Throwable) { throw (Throwable) object; } QueryDataFunctionResult result = (QueryDataFunctionResult) object; if (zipResult) { // The result is already compressed return result.compressedBytes; } else { Object[] functionArgsList = (Object[]) functionArgs; boolean showMember = (Boolean) functionArgsList[DISPLAY_MEMBERWISE]; if (showMember) {// Added to show a single member similar to multiple // member. // Note , if no member is selected this is the code path executed. A // random associated member is chosen. List<String> decompressedList = new ArrayList<String>(); decompressedList.add(BeanUtilFuncs.decompress(result.compressedBytes)); return wrapResult(decompressedList.toString()); } return BeanUtilFuncs.decompress(result.compressedBytes); } } else { // More than 1 Member ResultCollector coll = FunctionService.onMembers(members).withArgs(functionArgs) .execute(ManagementConstants.QUERY_DATA_FUNCTION); List list = (List) coll.getResult(); Object object = list.get(0); if (object instanceof Throwable) { throw (Throwable) object; } Iterator<QueryDataFunctionResult> it = list.iterator(); List<String> decompressedList = new ArrayList<String>(); while (it.hasNext()) { String decompressedStr = null; decompressedStr = BeanUtilFuncs.decompress(it.next().compressedBytes); decompressedList.add(decompressedStr); } if (zipResult) { return BeanUtilFuncs.compress(wrapResult(decompressedList.toString())); } else { return wrapResult(decompressedList.toString()); } } } catch (FunctionException fe) { throw new Exception( ManagementStrings.QUERY__MSG__QUERY_EXEC.toLocalizedString(fe.getMessage())); } catch (IOException e) { throw new Exception( ManagementStrings.QUERY__MSG__QUERY_EXEC.toLocalizedString(e.getMessage())); } catch (Exception e) { throw new Exception( ManagementStrings.QUERY__MSG__QUERY_EXEC.toLocalizedString(e.getMessage())); } catch (VirtualMachineError e) { SystemFailure.initiateFailure(e); throw e; } catch (Throwable e) { SystemFailure.checkFailure(); throw new Exception( ManagementStrings.QUERY__MSG__QUERY_EXEC.toLocalizedString(e.getMessage())); } } private static String wrapResult(final String str) { StringWriter w = new StringWriter(); synchronized (w.getBuffer()) { w.write("{\"result\":"); w.write(str); w.write("}"); return w.toString(); } } public static Object queryData(final String query, final String members, final int limit, final boolean zipResult, final int queryResultSetLimit, final int queryCollectionsDepth) throws Exception { if (query == null || query.isEmpty()) { return new JsonisedErroMessage(ManagementStrings.QUERY__MSG__QUERY_EMPTY.toLocalizedString()) .toString(); } Set<DistributedMember> inputMembers = null; if (members != null && !members.trim().isEmpty()) { inputMembers = new HashSet<DistributedMember>(); StringTokenizer st = new StringTokenizer(members, ","); while (st.hasMoreTokens()) { String member = st.nextToken(); DistributedMember distributedMember = BeanUtilFuncs.getDistributedMemberByNameOrId(member); inputMembers.add(distributedMember); if (distributedMember == null) { return new JsonisedErroMessage( ManagementStrings.QUERY__MSG__INVALID_MEMBER.toLocalizedString(member)).toString(); } } } Cache cache = CacheFactory.getAnyInstance(); try { SystemManagementService service = (SystemManagementService) ManagementService.getExistingManagementService(cache); Set<String> regionsInQuery = compileQuery(cache, query); // Validate region existence if (regionsInQuery.size() > 0) { for (String regionPath : regionsInQuery) { DistributedRegionMXBean regionMBean = service.getDistributedRegionMXBean(regionPath); if (regionMBean == null) { return new JsonisedErroMessage( ManagementStrings.QUERY__MSG__REGIONS_NOT_FOUND.toLocalizedString(regionPath)) .toString(); } else { Set<DistributedMember> associatedMembers = DataCommands.getRegionAssociatedMembers(regionPath, cache, true); if (inputMembers != null && inputMembers.size() > 0) { if (!associatedMembers.containsAll(inputMembers)) { return new JsonisedErroMessage( ManagementStrings.QUERY__MSG__REGIONS_NOT_FOUND_ON_MEMBERS .toLocalizedString(regionPath)).toString(); } } } } } else { return new JsonisedErroMessage(ManagementStrings.QUERY__MSG__INVALID_QUERY .toLocalizedString("Region mentioned in query probably missing /")).toString(); } // Validate if (regionsInQuery.size() > 1 && inputMembers == null) { for (String regionPath : regionsInQuery) { DistributedRegionMXBean regionMBean = service.getDistributedRegionMXBean(regionPath); if (regionMBean.getRegionType().equals(DataPolicy.PARTITION.toString()) || regionMBean.getRegionType().equals(DataPolicy.PERSISTENT_PARTITION.toString())) { return new JsonisedErroMessage( ManagementStrings.QUERY__MSG__JOIN_OP_EX.toLocalizedString()).toString(); } } } String randomRegion = regionsInQuery.iterator().next(); Set<DistributedMember> associatedMembers = DataCommands.getQueryRegionsAssociatedMembers(regionsInQuery, cache, false);// First // available // member if (associatedMembers != null && associatedMembers.size() > 0) { Object[] functionArgs = new Object[6]; if (inputMembers != null && inputMembers.size() > 0) {// on input // members functionArgs[DISPLAY_MEMBERWISE] = true; functionArgs[QUERY] = query; functionArgs[REGION] = randomRegion; functionArgs[LIMIT] = limit; functionArgs[QUERY_RESULTSET_LIMIT] = queryResultSetLimit; functionArgs[QUERY_COLLECTIONS_DEPTH] = queryCollectionsDepth; Object result = callFunction(functionArgs, inputMembers, zipResult); return result; } else { // Query on any random member functionArgs[DISPLAY_MEMBERWISE] = false; functionArgs[QUERY] = query; functionArgs[REGION] = randomRegion; functionArgs[LIMIT] = limit; functionArgs[QUERY_RESULTSET_LIMIT] = queryResultSetLimit; functionArgs[QUERY_COLLECTIONS_DEPTH] = queryCollectionsDepth; Object result = callFunction(functionArgs, associatedMembers, zipResult); return result; } } else { return new JsonisedErroMessage(ManagementStrings.QUERY__MSG__REGIONS_NOT_FOUND .toLocalizedString(regionsInQuery.toString())).toString(); } } catch (QueryInvalidException qe) { return new JsonisedErroMessage( ManagementStrings.QUERY__MSG__INVALID_QUERY.toLocalizedString(qe.getMessage())) .toString(); } } private static class JsonisedErroMessage { private static String message = "message"; private GfJsonObject gFJsonObject = new GfJsonObject(); public JsonisedErroMessage(final String errorMessage) throws Exception { try { gFJsonObject.put(message, errorMessage); } catch (GfJsonException e) { throw new Exception(e); } } @Override public String toString() { return gFJsonObject.toString(); } } /** * Compile the query and return a set of regions involved in the query It throws an * QueryInvalidException if the query is not proper * * @param cache current cache * @param query input query * * @return a set of regions involved in the query */ private static Set<String> compileQuery(final Cache cache, final String query) throws QueryInvalidException { QCompiler compiler = new QCompiler(); Set<String> regionsInQuery = null; try { CompiledValue compiledQuery = compiler.compileQuery(query); Set<String> regions = new HashSet<String>(); compiledQuery.getRegionsInQuery(regions, null); regionsInQuery = Collections.unmodifiableSet(regions); return regionsInQuery; } catch (QueryInvalidException qe) { logger.error("{} Failed, Error {}", query, qe.getMessage(), qe); throw qe; } } /** * Function to gather data locally. This function is required to execute query with region context */ private class LocalQueryFunction extends FunctionAdapter { private static final long serialVersionUID = 1L; private final String id; private boolean optimizeForWrite = false; private boolean showMembers = false; private String regionName; public LocalQueryFunction(final String id, final String regionName, final boolean showMembers) { super(); this.id = id; this.regionName = regionName; this.showMembers = showMembers; } @Override public boolean hasResult() { return true; } @Override public boolean isHA() { return false; } @Override public boolean optimizeForWrite() { return optimizeForWrite; } public LocalQueryFunction setOptimizeForWrite(final boolean optimizeForWrite) { this.optimizeForWrite = optimizeForWrite; return this; } @Override public void execute(final FunctionContext context) { Cache cache = CacheFactory.getAnyInstance(); QueryService queryService = cache.getQueryService(); String qstr = (String) context.getArguments(); Region r = cache.getRegion(regionName); try { Query query = queryService.newQuery(qstr); SelectResults sr; if (r.getAttributes().getPartitionAttributes() != null && showMembers) { sr = (SelectResults) query.execute((RegionFunctionContext) context); context.getResultSender().lastResult(sr.asList()); } else { sr = (SelectResults) query.execute(); context.getResultSender().lastResult(sr.asList()); } } catch (Exception e) { throw new FunctionException(e); } } @Override public String getId() { return this.id; } } private static class QueryDataFunctionResult implements Serializable { private static final long serialVersionUID = 1L; private final String message; private final byte[] compressedBytes; public QueryDataFunctionResult(final String message, final byte[] compressedBytes) { this.message = message; this.compressedBytes = compressedBytes; } public String getMessage() { return message; } public byte[] getCompressedBytes() { return compressedBytes; } } }