All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jkoolcloud.tnt4j.streams.inputs.AbstractWsStream Maven / Gradle / Ivy

/*
 * Copyright 2014-2023 JKOOL, LLC.
 *
 * 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
 *
 * 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 com.jkoolcloud.tnt4j.streams.inputs;

import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.script.CompiledScript;
import javax.script.ScriptException;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.AbstractTrigger;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jkoolcloud.tnt4j.core.OpLevel;
import com.jkoolcloud.tnt4j.core.UsecTimestamp;
import com.jkoolcloud.tnt4j.streams.configure.WsStreamProperties;
import com.jkoolcloud.tnt4j.streams.fields.ActivityInfo;
import com.jkoolcloud.tnt4j.streams.matchers.Matchers;
import com.jkoolcloud.tnt4j.streams.parsers.data.CommonActivityData;
import com.jkoolcloud.tnt4j.streams.scenario.*;
import com.jkoolcloud.tnt4j.streams.utils.*;

/**
 * Base class for scheduled service or system command request/call/query produced activity stream, where each
 * call/request//query response is assumed to represent a single activity or event which should be recorded.
 * 

* This activity stream requires parsers that can support {@code RS} defined type data to parse * {@link com.jkoolcloud.tnt4j.streams.scenario.WsResponse#getData()} provided data. *

* This activity stream supports the following configuration properties (in addition to those supported by * {@link com.jkoolcloud.tnt4j.streams.inputs.AbstractBufferedStream}): *

    *
  • SynchronizeRequests - flag indicating that stream issued WebService requests shall be synchronized and handled in * configuration defined sequence - waiting for prior request to complete before issuing next. Default value - * {@code false}. (Optional)
  • *
  • DropRecurrentRequests - flag indicating whether to drop streaming stream input buffer contained recurring * requests, when stream input scheduler invokes requests faster than they can be processed (parsed and sent to sink, * e.g. because of sink/JKool limiter throttling). Default value - {@code true}. (Optional)
  • *
  • List of Quartz configuration properties. See * Quartz Configuration * Reference for details.
  • *
* * @param * type of handled request data * @param * type of handled response data * * @version $Revision: 4 $ * * @see com.jkoolcloud.tnt4j.streams.parsers.ActivityParser#isDataClassSupported(Object) */ public abstract class AbstractWsStream extends AbstractBufferedStream> { /** * Constant for name of built-in scheduler job property {@value}. */ protected static final String JOB_PROP_STREAM_KEY = "streamObj"; // NON-NLS /** * Constant for name of built-in scheduler job property {@value}. */ protected static final String JOB_PROP_SCENARIO_STEP_KEY = "scenarioStepObj"; // NON-NLS /** * Constant for name of built-in scheduler job property {@value}. */ protected static final String JOB_PROP_SEMAPHORE = "semaphoreObj"; // NON-NLS /** * Constant for name of built-in request parameter {@value}. */ protected static final String REQ_URL_PARAM = "WS_REQ_URL"; // NON-NLS private static final String OBJECT_TYPE = "OBJECT";// NON-NLS // private static final Pattern GROOVY_EXP_PATTERN = Pattern.compile("\\s*use\\s*\\(.+\\)\\s*\\{(?s).+\\}"); private final Cache scriptsCache = CacheBuilder.newBuilder().maximumSize(100) .expireAfterAccess(30, TimeUnit.MINUTES).build(); /** * Semaphore for synchronizing stream requests. */ private Semaphore semaphore; private List scenarioList; private Scheduler scheduler; private boolean synchronizeRequests = false; private boolean dropRecurrentRequests = true; private final Properties quartzProperties = new Properties(); private final Set parsedRequests = new HashSet<>(); @Override public void setProperty(String name, String value) { super.setProperty(name, value); if (WsStreamProperties.PROP_SYNCHRONIZE_REQUESTS.equalsIgnoreCase(name)) { synchronizeRequests = Utils.toBoolean(value); } else if (WsStreamProperties.PROP_DROP_RECURRENT_REQUESTS.equalsIgnoreCase(name)) { dropRecurrentRequests = Utils.toBoolean(value); } else if (name.startsWith("org.quartz.")) { // NON-NLS quartzProperties.setProperty(name, value); } } @Override protected void applyProperties() throws Exception { super.applyProperties(); if (synchronizeRequests) { semaphore = new Semaphore(1); } } @Override public Object getProperty(String name) { if (WsStreamProperties.PROP_SYNCHRONIZE_REQUESTS.equalsIgnoreCase(name)) { return synchronizeRequests; } if (WsStreamProperties.PROP_DROP_RECURRENT_REQUESTS.equalsIgnoreCase(name)) { return dropRecurrentRequests; } if (name.startsWith("org.quartz.")) { // NON-NLS quartzProperties.getProperty(name); } return super.getProperty(name); } /** * Initiates required Quartz configuration properties, if not set over streams configuration: * *
    *
  • {@code org.quartz.scheduler.instanceName} - name of stream appended by {@code "Scheduler"} suffix.
  • *
  • {@code org.quartz.threadPool.threadCount=2}
  • *
*/ protected void initQuartzProperties() { Utils.setPropertyIfAbsent(quartzProperties, "org.quartz.scheduler.instanceName", getName() + "Scheduler"); // NON-NLS Utils.setPropertyIfAbsent(quartzProperties, "org.quartz.threadPool.threadCount", "2"); // NON-NLS } @Override protected void initialize() throws Exception { super.initialize(); if (scheduler == null) { initQuartzProperties(); StdSchedulerFactory schf = new StdSchedulerFactory(quartzProperties); scheduler = schf.getScheduler(); scheduler.start(); } logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.scheduler.started", getName()); loadScenarios(); } /** * Loads scenario steps into scheduler. * * @throws Exception * If any exception occurs while loading scenario steps to scheduler */ private void loadScenarios() throws Exception { int scenariosCount = 0; if (CollectionUtils.isNotEmpty(scenarioList)) { for (WsScenario scenario : scenarioList) { if (!scenario.isEmpty()) { for (WsScenarioStep step : scenario.getStepsList()) { scheduleScenarioStep(step); } scenariosCount++; } } } if (scenariosCount == 0) { throw new IllegalStateException(StreamsResources.getStringFormatted(WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.no.scenarios.defined", getName())); } else { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.stream.scenarios.loaded", getName(), scenariosCount); } } /** * Schedules scenario step to be executed by step defined scheduler configuration data. * * @param step * scenario step instance to schedule * * @throws SchedulerException * if scheduler fails to schedule job for defined step */ protected void scheduleScenarioStep(WsScenarioStep step) throws SchedulerException { if (scheduler == null) { throw new SchedulerException(StreamsResources.getStringFormatted(WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.null.scheduler", getName())); } if (isShotDown()) { return; } String enabledProp = step.getProperty("Enabled"); // NON-NLS if (StringUtils.equalsIgnoreCase("false", enabledProp)) { // NON-NLS logger().log(OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.stream.step.disabled", getName(), step.getScenario().getName(), step.getName()); return; } JobDataMap jobAttrs = new JobDataMap(); jobAttrs.put(JOB_PROP_STREAM_KEY, this); jobAttrs.put(JOB_PROP_SCENARIO_STEP_KEY, step); jobAttrs.put(JOB_PROP_SEMAPHORE, semaphore); String jobId = step.getScenario().getName() + ':' + step.getName(); JobDetail job = buildJob(step.getScenario().getName(), step.getName(), jobAttrs); ScheduleBuilder scheduleBuilder; AbstractSchedulerData schedulerData = (AbstractSchedulerData) step.getSchedulerData(); if (schedulerData instanceof CronSchedulerData) { CronSchedulerData csd = (CronSchedulerData) schedulerData; scheduleBuilder = CronScheduleBuilder.cronSchedule(csd.getExpression()); } else { SimpleSchedulerData ssd = (SimpleSchedulerData) schedulerData; Integer repCount = ssd == null ? null : ssd.getRepeatCount(); if (repCount != null && repCount == 0) { return; } if (repCount == null) { repCount = -1; } TimeUnit timeUnit = ssd == null ? TimeUnit.SECONDS : ssd.getUnits(); long interval = ssd == null ? 1 : ssd.getInterval(); scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInMilliseconds(timeUnit.toMillis(interval)) .withRepeatCount(repCount > 0 ? repCount - 1 : repCount); } Date startAt = schedulerData == null ? new Date() : schedulerData.getStartAt(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity(String.valueOf(job.getKey()), getName()) .startAt(startAt).withSchedule(scheduleBuilder).build(); if (schedulerData != null && schedulerData.getStartDelay() != null && schedulerData.getStartDelay() > 0) { logger().log(OpLevel.INFO, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.stream.step.start.delayed", getName(), jobId, schedulerData.getStartDelay(), schedulerData.getStartDelayUnits()); } logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.stream.scheduling.job", getName(), jobId); scheduler.scheduleJob(job, trigger); } /** * Builds scheduler job for call scenario step. * * @param group * jobs group name * @param jobId * job identifier * @param jobAttrs * additional job attributes * @return scheduler job detail object. */ protected abstract JobDetail buildJob(String group, String jobId, JobDataMap jobAttrs); @Override public void addReference(Object refObject) throws IllegalStateException { if (refObject instanceof WsScenario) { addScenario((WsScenario) refObject); } super.addReference(refObject); } /** * Adds scenario to scenarios list. * * @param scenario * scenario to be added to list */ public void addScenario(WsScenario scenario) { if (scenarioList == null) { scenarioList = new ArrayList<>(); } scenarioList.add(scenario); } /** * Returns list of defined streaming scenarios. * * @return list of streaming scenarios */ public List getScenarios() { return scenarioList; } @Override protected void cleanup() { if (scenarioList != null) { scenarioList.clear(); } if (scheduler != null) { try { scheduler.shutdown(true); } catch (SchedulerException exc) { Utils.logThrowable(logger(), OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.error.closing.scheduler", exc); } scheduler = null; } synchronized (parsedRequests) { parsedRequests.clear(); } scriptsCache.invalidateAll(); super.cleanup(); } @Override protected boolean isItemConsumed(WsResponse item) { if (item == null || item.getData() == null) { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.response.consumption.null"); return true; } if (dropRecurrentRequests) { WsResponse recurringItem = getRecurrentResponse(item, inputBuffer); if (recurringItem != null) { logger().log(OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.response.consumption.drop", item.getOriginalRequest().fqn()); inputBuffer.remove(recurringItem); cleanupItem(recurringItem); } } boolean consumed = isResponseConsumed(item); if (consumed) { cleanupItem(item); postParse(item); } return consumed; } /** * Checks whether provided response item is consumed, and stream should take next response from buffer. * * @param item * response item to check * @return {@code true} if response is consumed, {@code false} - otherwise */ protected boolean isResponseConsumed(WsResponse item) { return true; } @Override protected void setCurrentItem(WsResponse item) { super.setCurrentItem(item); if (item != null) { requestParsingStarted(item); } } @Override protected void cleanupItem(WsResponse item) { if (item != null) { if (item.getData() != null) { closeResponse(item.getData()); } responseConsumed(item); } } /** * Removes all inactive jobs from stream scheduler. */ protected void purgeInactiveSchedulerJobs() { if (scheduler != null && !isShotDown()) { try { int rCount = 0; int lCount = 0; Set triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyGroup()); if (CollectionUtils.isNotEmpty(triggerKeys)) { for (TriggerKey tKey : triggerKeys) { try { Trigger t = scheduler.getTrigger(tKey); if (t == null) { continue; } if (isInactiveTrigger(t)) { scheduler.deleteJob(t.getJobKey()); rCount++; logger().log(OpLevel.TRACE, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.scheduler.removed.inactive.job", getName(), tKey); } else { lCount++; } } catch (SchedulerException exc) { } } } logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.scheduler.removed.inactive.jobs", getName(), rCount, lCount); } catch (SchedulerException exc) { } } } @Override protected boolean isInputEnded() { if (scheduler != null && !isShotDown()) { // Check if scheduler has any jobs executed - WS streams does not allow void schedulers. try { if (scheduler.getMetaData().getNumberOfJobsExecuted() == 0) { return false; } } catch (SchedulerException exc) { } // Check if scheduler is executing some jobs try { List runningJobs = scheduler.getCurrentlyExecutingJobs(); logger().log(OpLevel.TRACE, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.currently.executing", runningJobs.size()); if (CollectionUtils.isNotEmpty(runningJobs)) { return false; } } catch (SchedulerException exc) { } // Check scheduler triggers - have they been fired and may fire again try { Set triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyGroup()); logger().log(OpLevel.TRACE, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.scheduler.triggers", triggerKeys.size()); if (CollectionUtils.isNotEmpty(triggerKeys)) { for (TriggerKey tKey : triggerKeys) { try { Trigger t = scheduler.getTrigger(tKey); if (t == null) { continue; } logger().log(OpLevel.TRACE, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.scheduler.trigger", t instanceof AbstractTrigger ? ((AbstractTrigger) t).getFullName() : t.getClass().getName(), t.getPreviousFireTime(), t.getNextFireTime()); if (isActiveTrigger(t)) { return false; } } catch (SchedulerException exc) { } } } } catch (SchedulerException exc) { } // Looks like this scheduler already has done it's job... try { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.inactive.scheduler", scheduler.getMetaData()); } catch (SchedulerException exc) { } } return true; } private static boolean isActiveTrigger(Trigger t) { return t.getPreviousFireTime() == null || t.mayFireAgain(); } private static boolean isInactiveTrigger(Trigger t) { return t.getPreviousFireTime() != null && !t.mayFireAgain(); } @Override public String[] getDataTags(Object data) { return data instanceof WsResponse ? ((WsResponse) data).getTags() : super.getDataTags(data); } @Override @SuppressWarnings("unchecked") protected ActivityInfo applyParsers(Object data, String... tags) throws IllegalStateException, ParseException { if (data instanceof WsResponse) { WsResponse response = (WsResponse) data; RS respData = response.getData(); Map reqMetadata = response.getOriginalRequest().getParametersMap(); CommonActivityData dataPack = new CommonActivityData<>(respData, reqMetadata); return super.applyParsers(dataPack, tags); } return super.applyParsers(data, tags); } /** * Performs post parsing actions for provided activity data item. *

* Generic post parsing case is to release all acquired requests synchronization semaphores. * * @param item * processed activity data item */ protected void postParse(WsResponse item) { if (item != null) { if (semaphore != null) { releaseSemaphore(semaphore, getName(), item.getOriginalRequest()); } Semaphore reqSemaphore = item.getSemaphore(); if (reqSemaphore != null) { releaseSemaphore(reqSemaphore, item.getScenarioStep().getName(), item.getOriginalRequest()); } } else { if (semaphore != null) { releaseSemaphore(semaphore, getName(), null); } } } /** * Fills-in request/query/command string having variable expressions with values stored in stream configuration * properties maps and streams cache {@link com.jkoolcloud.tnt4j.streams.utils.StreamsCache}. * * @param reqDataStr * request/query/command string * @return variable values filled-in request/query/command string * * @see #fillInRequestData(String, String, String) */ public String fillInRequestData(String reqDataStr) { return fillInRequestData(reqDataStr, null, null); } /** * Fills-in request/query/command string having variable expressions with values stored in stream configuration * properties maps and streams cache {@link com.jkoolcloud.tnt4j.streams.utils.StreamsCache}. * * @param reqDataStr * request/query/command string * @param format * format of value to fill * @return variable values filled-in request/query/command string * * @see #fillInRequestData(DataFillContext) */ public String fillInRequestData(String reqDataStr, String format) { return fillInRequestData(reqDataStr, format, null); } /** * Fills-in request/query/command string having variable expressions with values stored in stream configuration * properties maps and streams cache {@link com.jkoolcloud.tnt4j.streams.utils.StreamsCache}. * * @param reqDataStr * request/query/command string * @param format * format of value to fill * @return variable values filled-in request/query/command string * * @see #fillInRequestData(DataFillContext) */ public String fillInRequestData(String reqDataStr, String format, String tz) { if (StringUtils.isEmpty(reqDataStr)) { return reqDataStr; } DataFillContext ctx = makeDataContext(reqDataStr, format, tz, null); return (String) fillInRequestData(ctx); } /** * Fills-in request/query/command string having variable expressions with values configured over provided * {@code context}. * * @param context * request data fill-in context to use * @return variable values filled-in request/query/command data entity * * @see #getVariableValue(String, DataFillContext) * @see #formattedValue(Object, String, String) */ protected Object fillInRequestData(DataFillContext context) { if (context == null || StringUtils.isEmpty(context.getData())) { return context.getData(); } String reqData = context.getData(); Set vars = new HashSet<>(); Utils.resolveExpressionVariables(vars, reqData); // Utils.resolveCfgVariables(vars, reqData); if (CollectionUtils.isNotEmpty(vars)) { for (String rdVar : vars) { Object cValue = getVariableValue(Utils.getVarName(rdVar), context); if (OBJECT_TYPE.equals(context.getType())) { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.filling.req.data.variable", rdVar, Utils.toString(cValue)); return cValue; } else { String varVal = formattedValue(cValue, context.getFormat(), context.getTimeZone()); reqData = reqData.replace(rdVar, varVal); logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.filling.req.data.variable", rdVar, varVal); } } if (!reqData.equals(context.getData()) && Utils.isVariableExpression(reqData)) { context.setData(reqData); return fillInRequestData(context); } } return reqData; } /** * Resolves variable defined value from: *

    *
  • {@code context} parameter passed entries: request, step and scenario. Value resolution from context provided * entries is stream dependent.
  • *
  • streams cache - {@link com.jkoolcloud.tnt4j.streams.utils.StreamsCache#getValue(String)}
  • *
  • stream configuration properties - {@link #getProperty(String)}
  • *
  • groovy expression evaluated value - see * Groovy API TimeCategory * for details. See {@link #getScript(String)} for supported groovy script additions. NOTE: expressions shall not * contain any whitespace symbols!
  • *
* * @param varName * variable name to resolve * @param context * variable value resolution context to use * @return variable resolved value * * @see #getReqContextProperty(String, com.jkoolcloud.tnt4j.streams.inputs.AbstractWsStream.DataFillContext) * @see com.jkoolcloud.tnt4j.streams.utils.StreamsCache#getValue(String) * @see #getProperty(String) * @see #evaluateExpr(String) */ protected Object getVariableValue(String varName, DataFillContext context) { Object rValue = getReqContextProperty(varName, context); if (rValue == null) { rValue = StreamsCache.getValue(varName); } if (rValue == null) { rValue = getProperty(varName); } if (rValue == null) { try { rValue = evaluateExpr(varName); } catch (ScriptException e) { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.expr.evaluation.failed", varName, e.getMessage()); } } return rValue; } /** * Resolves request context provided property value. Property value resolution is made from context provided * entities: request, step and scenario. * * @param varName * property name to resolve * @param context * request context context to use * @return resolved property value * * @see com.jkoolcloud.tnt4j.streams.inputs.AbstractWsStream.DataFillContext#getReqParameter(String) * @see com.jkoolcloud.tnt4j.streams.scenario.WsScenarioStep#getProperty(String) * @see com.jkoolcloud.tnt4j.streams.scenario.WsScenario#getProperty(String) */ protected Object getReqContextProperty(String varName, DataFillContext context) { Object rValue = context.getReqParameter(varName); if (rValue == null) { WsScenarioStep reqStep = context.getRequest().getScenarioStep(); if (reqStep != null) { rValue = reqStep.getProperty(varName); } if (rValue == null) { WsScenario reqScenario = reqStep.getScenario(); if (reqScenario != null) { rValue = reqScenario.getProperty(varName); } } } return rValue; } /** * Evaluates groovy script defined variable expression. * * @param varExpr * variable expression to evaluate * @return groovy script evaluated expression value * * @throws ScriptException * if can't compose valid or compile groovy script * * @see #getScript(String) */ protected Object evaluateExpr(String varExpr) throws ScriptException { String[] vet = varExpr.split(":"); // NON-NLS if (vet.length == 1 || !vet[0].equalsIgnoreCase("groovy") || StringUtils.isEmpty(vet[1])) { // NON-NLS return null; } String scriptStr = vet[1]; CompiledScript compiledScript = scriptsCache.getIfPresent(scriptStr); try { if (compiledScript == null) { String script = getScript(scriptStr); logger().log(OpLevel.DEBUG, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.expr.script", scriptStr, script); if (script == null) { throw new ScriptException(StreamsResources.getStringFormatted( WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.expr.compose.failed", scriptStr)); } compiledScript = StreamsScriptingUtils.compileGroovyScript(script); scriptsCache.put(scriptStr, compiledScript); } return compiledScript.eval(); } catch (ScriptException se) { throw se; } } /** * Prepares groovy script for provided variable expression. *

* Supports these expression keywords: *

    *
  • HOUR_BEGIN - to pick 00:00 minute and second of the evaluated hour value
  • *
  • HOUR_END - to pick 59:59 minute and second of the evaluated hour value
  • *
* * @param varExpr * variable expression to use for groovy script * @return expression groovy script */ protected String getScript(String varExpr) { String scriptStr = StringUtils.trim(varExpr); if (StringUtils.isEmpty(scriptStr)) { return null; } // if (GROOVY_EXP_PATTERN.matcher(scriptStr).matches()) { // return scriptStr; // } String gScript = translateKeyword(scriptStr); if (scriptStr.length() >= 8) { switch (scriptStr.substring(0, 8)) { case "HOUR_BEG": // NOTE: full value is HOUR_BEGIN String dateVariable = translateKeyword(scriptStr.substring(11)); gScript = " def vDate = " + dateVariable + ";"; gScript += " vDate.putAt('minutes',0);"; gScript += " vDate.putAt('seconds',0);"; gScript += "return vDate;"; break; case "HOUR_END": dateVariable = translateKeyword(scriptStr.substring(9)); gScript = " def vDate = " + dateVariable + ";"; gScript += " vDate.putAt('minutes',59);"; gScript += " vDate.putAt('seconds',59);"; gScript += "return vDate;"; break; default: } } return "use( groovy.time.TimeCategory ) { " + gScript + " }"; // NON-NLS } private String translateKeyword(String script) { switch (script) { case "now": return "new Date()"; default: return script; } } /** * Fills-in request data and parameter values having variable expressions. * * @param req * request instance to fill-in data * @return request instance having filled-in values * * @throws VoidRequestException * if request can't be build from request context data or is meaningless * * @see #fillInRequest(WsRequest, RequestFillContext) */ protected WsRequest fillInRequest(WsRequest req) throws VoidRequestException { RequestFillContext context = new RequestFillContext(isDirectRequestUse()); return fillInRequest(req, context); } /** * Returns flag indicating if stream will use filled-in request directly. Indirect use is when some additional * object (e.g. SQL statement) is created from provided request data. * * @return {@code true} if stream uses request directly, {@code false} - if stream will make additional request * object from request data */ protected boolean isDirectRequestUse() { // TODO: clear naming return true; } /** * Fills-in request data and parameter values having variable expressions. *

* Request entities filled-in: *

    *
  • parameters (identifiers and values)
  • *
  • identifier
  • *
  • data
  • *
* * @param req * request instance to fill-in data * @param context * request values fill-in context to use * @return request instance having filled-in values * * @throws VoidRequestException * if request can't be build from request context data or is meaningless * * @see #fillInRequestData(DataFillContext) */ protected WsRequest fillInRequest(WsRequest req, RequestFillContext context) throws VoidRequestException { DataFillContext ctx = makeDataContext(null, null, null, context); ctx.setRequest(req); checkConditions(req, ctx); WsRequest fReq = req.clone(); if (req.isDynamic()) { ctx.setRequest(fReq); if (context.isFillingParams()) { for (Map.Entry reqParam : fReq.getParameters().entrySet()) { reqParam.getValue() .setValue(fillInRequestData(ctx.setData(reqParam.getValue().getStringValue()) .setFormat(reqParam.getValue().getAttribute(WsRequest.Parameter.ATTR_FORMAT)) .setType(reqParam.getValue().getAttribute(WsRequest.Parameter.ATTR_TYPE)) .setTimeZone(reqParam.getValue().getAttribute(WsRequest.Parameter.ATTR_TIMEZONE)))); } } fReq.setId((String) fillInRequestData(ctx.setData(fReq.getId()).reset())); fReq.setData((String) fillInRequestData(ctx.setData(fReq.getData()))); } return fReq; } /** * Makes request data fill-in context instance for provided request entity data string, value format and request * fill-in context. * * @param reqDataStr * request entity data string * @param format * format of value to fill * @param tz * date-time value format timezone * @param reqCtx * request fill-in context to use * @return data fill-in context instance */ protected DataFillContext makeDataContext(String reqDataStr, String format, String tz, RequestFillContext reqCtx) { DataFillContext dataCtx = new DataFillContext(reqDataStr); dataCtx.setFormat(format); dataCtx.setTimeZone(tz); if (reqCtx != null) { for (Map.Entry rcme : reqCtx.entrySet()) { if (rcme.getKey().startsWith(DataFillContext.KEY_PREFIX)) { dataCtx.put(rcme.getKey(), rcme.getValue()); } } } return dataCtx; } /** * Formats provided value as a string using defined format pattern. * * @param cValue * value to format * @param format * format pattern of the value * @param tz * timezone to format date-time values * @return formatted value string */ protected static String formattedValue(Object cValue, String format, String tz) { if (StringUtils.isNotEmpty(format)) { if (cValue instanceof UsecTimestamp) { return ((UsecTimestamp) cValue).toString(format, tz); } else if (cValue instanceof Date) { FastDateFormat df = FastDateFormat.getInstance(format, StringUtils.isEmpty(tz) ? null : TimeZone.getTimeZone(tz)); return df.format(cValue); } else if (cValue instanceof Number) { DecimalFormat df = new DecimalFormat(format); return df.format(cValue); } else { return Utils.toString(cValue); } } return Utils.toString(cValue); } /** * Checks if request matches any of defined conditions. * * @param req * request instance to check conditions against * @param context * variable values resolution context to use * * @throws VoidRequestException * if request matched any of conditions and resolution is to skip or to stop */ protected void checkConditions(WsRequest req, DataFillContext context) throws VoidRequestException { List reqConditions = req.getConditions(); if (CollectionUtils.isNotEmpty(reqConditions)) { for (Condition condition : reqConditions) { if (!condition.isEmpty()) { for (String matchExp : condition.getMatchExpressions()) { boolean match = false; try { match = matchExpression(matchExp, context); } catch (Exception exc) { Utils.logThrowable(logger(), OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.condition.match.failed", req.getId(), condition.getId(), exc); } if (match) { if (condition.getResolution() == Condition.Resolution.STOP) { offerDieMarker(); } throw new VoidRequestException(StreamsResources.getStringFormatted( WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.condition.match", condition.getId(), condition.getResolution())); } } } } } } /** * Evaluates provided match expressions {@code matchExp} against {@code context} provided data. * * @param matchExp * match expression to evaluate * @param context * variable values resolution context to use * @return {@code true} if expressions matches context provided data, {@code false} - otherwise * * @throws Exception * if evaluation expression is empty or evaluation of match expression fails */ protected boolean matchExpression(String matchExp, DataFillContext context) throws Exception { Set vars = new HashSet<>(); Utils.resolveExpressionVariables(vars, matchExp); // Utils.resolveCfgVariables(vars, reqData); Map valBindings = new HashMap<>(vars.size()); if (CollectionUtils.isNotEmpty(vars)) { for (String rdVar : vars) { Object vValue = getVariableValue(Utils.getVarName(rdVar), context); valBindings.put(rdVar, vValue); } } return Matchers.evaluateBindings(matchExp, valBindings); } /** * Acquires semaphore to be used for requests synchronization. Semaphore can be bound to stream or scenario step. * Stream semaphore has preference against scenario step semaphore. *

* To synchronize stream or scenario step requests use flag ({@code true}/{@code false}) type stream/scenario step * configuration property * {@value com.jkoolcloud.tnt4j.streams.configure.WsStreamProperties#PROP_SYNCHRONIZE_REQUESTS}. * * @param request * request semaphore is obtained for * @return acquired semaphore instance or {@code null} if no semaphore was acquired * * @throws InterruptedException * if current thread got interrupted when acquiring semaphore */ protected Semaphore acquireSemaphore(WsRequest request) throws InterruptedException { if (semaphore != null) { while (!semaphore.tryAcquire()) { Thread.sleep(50); } logger().log(OpLevel.DEBUG, StreamsResources.getString(WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.semaphore.acquired.stream"), getName(), request.getId()); return semaphore; } WsScenarioStep scenarioStep = request.getScenarioStep(); Semaphore stepSemaphore = scenarioStep.getSemaphore(); if (stepSemaphore != null) { while (!stepSemaphore.tryAcquire()) { Thread.sleep(50); } logger().log(OpLevel.DEBUG, StreamsResources.getString(WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.semaphore.acquired.step"), scenarioStep.getName(), request.getId()); return stepSemaphore; } return null; } /** * Releases provided semaphore to continue next request execution. * * @param acquiredSemaphore * requests semaphore to release * @param lockName * locker object name * @param request * request semaphore was obtained for */ protected void releaseSemaphore(Semaphore acquiredSemaphore, String lockName, WsRequest request) { if (acquiredSemaphore != null && acquiredSemaphore.availablePermits() < 1) { logger().log(OpLevel.DEBUG, StreamsResources.getString(WsStreamConstants.RESOURCE_BUNDLE_NAME, "AbstractWsStream.semaphore.release"), lockName, request == null ? "UNKNOWN" : request.getId()); // NON-NLS acquiredSemaphore.release(); } } /** * Invokes actions to be done when request fails or response has no meaningful payload. * * @param request * failed request */ protected void requestFailed(WsRequest request) { } /** * Performs actions on response item before parsing. * * @param rItem * parsed response item */ protected void requestParsingStarted(WsResponse rItem) { String reqName = rItem.getOriginalRequest().fqn(); if (reqName != null) { synchronized (parsedRequests) { parsedRequests.add(reqName); } } } /** * Performs actions on consumed response item. * * @param rItem * consumed response item */ protected void responseConsumed(WsResponse rItem) { String qName = rItem.getOriginalRequest().fqn(); if (qName != null) { synchronized (parsedRequests) { parsedRequests.remove(qName); } } } /** * Checks if request has any response currently processed or pending on input buffer. * * @param req * request instance to check * @return {@code true} if request has any response currently processed or pending on input buffer, {@code false} - * otherwise */ @SuppressWarnings("unchecked") protected boolean isRequestOngoing(WsRequest req) { String reqName = req.fqn(); synchronized (parsedRequests) { for (String pqName : parsedRequests) { if (reqName.equals(pqName)) { logger().log(OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.response.in.progress", reqName); return true; } } } for (Object item : inputBuffer) { if (item instanceof WsResponse) { WsResponse respItem = (WsResponse) item; if (reqName.equals(respItem.getOriginalRequest().fqn())) { logger().log(OpLevel.WARNING, StreamsResources.getBundle(WsStreamConstants.RESOURCE_BUNDLE_NAME), "AbstractWsStream.response.pending", reqName); return true; } } } return false; } /** * Returns recurrent response for currently parsed response. * * @param cItem * currently parsed response * @param buffer * input buffer instance * @return recurrent response for currently processed response, or {@code null} if no recurring responses available * in input buffer */ @SuppressWarnings("unchecked") protected WsResponse getRecurrentResponse(WsResponse cItem, Queue buffer) { for (Object item : buffer) { if (item instanceof WsResponse) { WsResponse respItem = (WsResponse) item; if (respItem.getOriginalRequest().fqn().equals(cItem.getOriginalRequest().fqn())) { return respItem; } } } return null; } /** * Closes response instance. * * @param resp * response to close */ protected void closeResponse(RS resp) { } /** * Checks if stream is configured to drop recurring requests and if request is recurring: currently processed by * parser or pending in input buffer. * * @param req * request to check * @return {@code true} is stream shall drop recurring requests and request is processed or pending in input buffer, * {@code false} - otherwise * * @see #isRequestOngoing(com.jkoolcloud.tnt4j.streams.scenario.WsRequest) */ protected boolean isDropRecurring(WsRequest req) { return dropRecurrentRequests && isRequestOngoing(req); } /** * Base scheduler job class to be executing implementing stream calls. */ protected static abstract class CallJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); AbstractWsStream stream = (AbstractWsStream) dataMap.get(JOB_PROP_STREAM_KEY); if (stream.isShotDown()) { return; } stream.startProcessingTask(); try { executeCalls(dataMap); } finally { stream.endProcessingTask(); } } /** * Executes dedicated endpoint (e.g. JAX-RS, JAX-WS, JDBC, etc.) calls. * * @param dataMap * job data map instance */ protected abstract void executeCalls(JobDataMap dataMap); } /** * Request entity data fill-in context. */ protected static class DataFillContext extends HashMap { private static final long serialVersionUID = 925353711026880141L; /** * Constant for context key prefix {@value}. */ public static final String KEY_PREFIX = "REQ_DATA_CTX_"; // NON-NLS private static final String DATA = KEY_PREFIX + "DATA"; // NON-NLS private static final String FORMAT = KEY_PREFIX + "FORMAT"; // NON-NLS private static final String TYPE = KEY_PREFIX + "TYPE"; // NON-NLS private static final String REQUEST = KEY_PREFIX + "REQUEST"; // NON-NLS private static final String TIMEZONE = KEY_PREFIX + "TIMEZONE"; // NON-NLS /** * Constructs a new instance of DataFillContext. * * @param reqData * request entity data string */ public DataFillContext(String reqData) { super(); put(DATA, reqData); } /** * Sets request entity data string. * * @param data * request entity data string * @return instance of this context */ public DataFillContext setData(String data) { put(DATA, data); return this; } /** * Returns request entity data string. * * @return request entity data string */ public String getData() { return (String) get(DATA); } /** * Sets value format. * * @param format * value format * @return instance of this context */ public DataFillContext setFormat(String format) { put(FORMAT, format); return this; } /** * Returns value format. * * @return value format */ public String getFormat() { return (String) get(FORMAT); } /** * Sets date-time value format timezone. * * @param tz * timezone identifier string * @return instance of this context */ public DataFillContext setTimeZone(String tz) { put(TIMEZONE, tz); return this; } /** * Returns date-time value format timezone. * * @return date-time value format timezone */ public String getTimeZone() { return (String) get(TIMEZONE); } /** * Sets value type. * * @param type * value type * @return instance of this context */ public DataFillContext setType(String type) { put(TYPE, type); return this; } /** * Returns value type. * * @return value type */ public String getType() { return (String) get(TYPE); } /** * Removes context additional properties: format, timezone and type. * * @return instance of this context */ public DataFillContext reset() { remove(FORMAT); remove(TIMEZONE); remove(TYPE); return this; } /** * Sets request instance bound to this context. * * @param request * request instance to bind * @return instance of this context */ public DataFillContext setRequest(WsRequest request) { put(REQUEST, request); return this; } /** * Returns this context bound request instance. * * @return bound request instance */ @SuppressWarnings("unchecked") public WsRequest getRequest() { return (WsRequest) get(REQUEST); } /** * Returns request parameter value. * * @param pKey * request parameter key * @return request parameter value, or {@code null} if request has no such property */ public Object getReqParameter(String pKey) { WsRequest request = getRequest(); return request == null ? null : request.getParameterValue(pKey); } /** * Returns boolean context property value. * * @param key * context property key * @param defValue * default property value * @return resolved context property value or default value if context has no defined property */ public boolean getBoolean(String key, boolean defValue) { Boolean val = (Boolean) get(key); return val == null ? defValue : val; } } /** * Request fill-in context. */ protected static class RequestFillContext extends HashMap { private static final long serialVersionUID = 181407282124290094L; /** * Constant for context key prefix {@value}. */ public static final String KEY_PREFIX = "REQ_CTX_"; // NON-NLS private static final String FILL_PARAMS = KEY_PREFIX + "FILL_PARAMS"; // NON-NLS /** * Constructs a new instance of RequestFillContext. * * @param fillParams * flag indicating whether to fill-in request parameters */ public RequestFillContext(boolean fillParams) { super(); put(FILL_PARAMS, fillParams); } /** * Returns flag indicating whether to fill-in request parameters. * * @return {@code true} if request parameters values shall be filled-in, {@code false} - otherwise */ public boolean isFillingParams() { Boolean fp = (Boolean) get(FILL_PARAMS); return fp == null ? true : fp; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy