The Reactor framework is a cornerstone of SEMOSS's Java backend, providing a flexible and extensible way to define and execute specific operations or commands, often as part of a Pixel script. Each "reactor" is a Java class that encapsulates a particular piece of logic.
-
prerna.reactor.IReactor.java: This is the primary interface that all reactors must implement (typically by extendingAbstractReactor). It defines the contract for reactor behavior, including methods for:- Execution:
NounMetadata execute()is the main method where the reactor performs its logic. - Input/Output Definition:
getInputs()andgetOutputs()(though often managed byAbstractReactorconventions). - Lifecycle and Chaining:
setParentReactor(),getChildReactors(),mergeUp()for integrating into a larger execution flow. - Interaction with Pixel Execution:
setPixelPlanner(),setNounStore(). - Metadata:
getName(),getSignature(),getHelp().
- Execution:
-
prerna.reactor.AbstractReactor.java: This abstract class provides a robust base implementation ofIReactor. Most concrete reactors in SEMOSS extendAbstractReactor. Key functionalities it provides include:- Noun Management:
NounStore store: Each reactor instance has aNounStoreto hold its input parameters (nouns).keysToGet: Concrete reactors define an array of strings (keysToGet) specifying the names of the input nouns they expect (e.g.,"value","column","expression"). These often correspond to keys fromReactorKeysEnum.java.organizeKeys(): A crucial method called typically at the start ofexecute(). It populates thestore(and a convenience mapkeyValue) from the actual inputs provided in the Pixel script, matching them againstkeysToGet.curRow: AGenRowStructrepresenting the current data row or context, especially when reactors are chained or process input streams.
- Planner and Insight: Access to the
PixelPlannerand the currentInsightobject. - Signature and Naming: Storing the reactor's operation name and Pixel signature.
- Error Handling and Logging: Utility methods for standardized error reporting and logging.
- Input Retrieval: Helper methods like
getNounAsStringList(String key)to easily access input values from theNounStore.
- Noun Management:
Reactors are used for a vast array of tasks in SEMOSS. The behavior and integration of a reactor can vary:
-
General Purpose Reactors: These perform a distinct operation and their results are typically used by subsequent reactors or returned to the user.
- Example:
EchoReactor.java- Purpose: A simple reactor that returns its primary input.
- Inputs: Expects a single noun, typically specified by the key
ReactorKeysEnum.VALUE.getKey()(e.g.,Pixel: Echo(value=["Hello World"]);). - Execution: Calls
organizeKeys()to load its inputs. Retrieves the specified noun from itsNounStoreand returns it as aNounMetadataobject. - Role: Useful for debugging, simple assignments, or as a basic building block.
- Example:
-
Data Structure Providers / Configuration Reactors: Some reactors don't perform a final action themselves but rather construct an object or configure a state that is then used by a parent or subsequent reactor in the execution chain.
- Example:
FilterReactor.java- Purpose: To define a filter condition based on a left operand (column), a comparator, and a right operand (value or another column).
- Inputs: Expects nouns named "LCOL", "COMPARATOR", and "RCOL".
- Execution: Its
execute()method creates aprerna.sablecc2.om.Filterobject using these inputs. - Integration:
- It overrides
mergeUp()to add the createdFilterobject (asNounMetadata) directly into its parent reactor's current processing row (parentReactor.getCurRow().add(filterNoun)). - Its
getInputs()method returnsnull, indicating it's not treated as a standalone step in thePixelPlannerbut rather contributes to a consuming reactor (e.g., a data query reactor likeSelectReactorwould consume thisFilterobject).
- It overrides
- Role: Defines a filter that will be applied by another reactor that actually performs data retrieval or manipulation.
- Example:
-
Control Flow Reactors:
- Example:
IfReactor.java- Purpose: Implements conditional logic (if-then-else).
- Inputs: Typically takes a condition, a then-expression (reactor or value), and an optional else-expression.
- Execution: Evaluates the condition. Based on the result, it then executes either the "then" part or the "else" part.
- Role: Allows for branching logic within Pixel scripts.
- Example:
-
Data Operation Reactors (e.g., in
src/prerna/reactor/frame/orsrc/prerna/reactor/qs/):- Many reactors are dedicated to specific data operations like selecting columns, joining data, grouping, pivoting, etc. These often interact heavily with the
ITableDataFrame(viaPixelPlanneror directly) or build up components of a query structure (QueryStruct). For example, a hypotheticalSelectReactorwould take column names as input and modify the current frame or query to only include those columns.
- Many reactors are dedicated to specific data operations like selecting columns, joining data, grouping, pivoting, etc. These often interact heavily with the
The Reactor pattern, combined with the Pixel language, gives SEMOSS a highly modular and powerful way to define and execute a wide variety of operations. Developers can add new functionality by creating new reactor classes.
Reactors are designed to be configurable and reusable components. Their inputs are dynamically provided at runtime, typically defined in a Pixel script. Understanding how these inputs are passed and processed is crucial.
-
Defining Inputs in Pixel Scripts:
- When invoking a Reactor in Pixel, inputs are passed as key-value pairs within the parentheses. The key is the parameter name expected by the Reactor, and the value is the data to be passed.
- Pixel syntax generally requires values to be enclosed in square brackets
[], even if it's a single item. This allows for consistent passing of single values or lists of values. - Examples of Passing Different Data Types:
// Passing literal strings, numbers, booleans CreateFile(fileName=["myDocument.txt"], content=["Hello SEMOSS!"], overwrite=[true]); Calculate(operation=["ADD"], values=[10, 20, 30]); // Passing a list of strings SelectColumns(columns=["ProductID", "ProductName", "Price", "Category"]); // Referencing a previously defined variable (e.g., a frame or a value) productFilter = "Electronics"; FilterData(frame=[$currentFrame], column=["Category"], comparator=["=="], value=[$productFilter]);
-
Reactor Input Handling via
AbstractReactor:keysToGet(String Array): Each concrete Reactor (extendingAbstractReactor) declares aString[] keysToGetarray. This array lists the expected input parameter names (keys) that the Reactor can accept. For example,EchoReactordefinesthis.keysToGet = new String[] {ReactorKeysEnum.VALUE.getKey()};.NounStore: WhenPixelRunner(viaGreedyTranslation) prepares to execute a Reactor, it populates the Reactor'sNounStore(an instance ofprerna.sablecc2.om.NounStore). TheNounStoreis essentially a map where keys are the parameter names (from the Pixel script, e.g., "fileName", "content") and values areGenRowStructobjects. AGenRowStructcan hold one or moreNounMetadataobjects, accommodating single values or lists passed from Pixel.organizeKeys()Method: Inside the Reactor'sexecute()method (or often in its constructor or an initialization block),organizeKeys()(a method fromAbstractReactor) is typically called. This method:- Iterates through the Reactor's declared
keysToGet. - For each key, it retrieves the corresponding
GenRowStructfrom theNounStore. - It populates a convenience map
this.keyValue(aHashtable<String, String>) with the first value for each key, converted to a String. This is useful for quickly accessing single-value parameters. - It also ensures that required parameters (as defined by
keyRequiredin the Reactor) are present, throwing an error if a required key is missing.
- Iterates through the Reactor's declared
- Accessing Full Input Data:
- While
organizeKeys()populateskeyValuewith string representations of the first item for each input, Reactors often need to access the fullNounMetadata(to get the actual data type and full value, especially for lists or complex objects). - This is done by directly accessing the
NounStoreusingthis.store.getNoun(String key)which returns theGenRowStruct. - Example:
GenRowStruct columnsGrs = this.store.getNoun("columns"); - The Reactor can then iterate through the
NounMetadataobjects in theGenRowStructor get specific ones by index.
- While
-
Input Data Representation (
NounMetadata):- All inputs, whether literals or variables from Pixel, are wrapped as
NounMetadataobjects before being placed in theNounStore. - A
NounMetadataobject contains:- The actual value (e.g., a String, Integer, List,
ITableDataFrameinstance if a frame variable was passed). - A
PixelDataTypeenum indicating the type of the data (e.g.,CONST_STRING,CONST_INT,FRAME). PixelOperationType(less critical for inputs, more for outputs).
- The actual value (e.g., a String, Integer, List,
- This consistent wrapping allows Reactors to inspect the type of input they have received and process it accordingly. For instance, a Reactor expecting a frame can check if
noun.getNounType() == PixelDataType.FRAMEand then castnoun.getValue()toITableDataFrame.
- All inputs, whether literals or variables from Pixel, are wrapped as
-
Conceptual Example of a Reactor Processing Inputs:
// Inside a hypothetical "ProcessItemsReactor" // public class ProcessItemsReactor extends AbstractReactor { // public ProcessItemsReactor() { // this.keysToGet = new String[] {"items", "processingMode", "threshold"}; // this.keyRequired = new int[] {1, 0, 0}; // items is required // } // @Override // public NounMetadata execute() { // organizeKeys(); // Populates this.store and this.keyValue // // Get the list of items // List<Object> itemsList = new ArrayList<>(); // GenRowStruct itemsGrs = this.store.getNoun("items"); // if (itemsGrs != null) { // for (NounMetadata itemNoun : itemsGrs.vector) { // itemsList.add(itemNoun.getValue()); // } // } // // Get optional processingMode (defaults if not present) // String mode = this.keyValue.get("processingMode"); // if (mode == null) { // mode = "default"; // } // // Get optional threshold // double threshold = 0.5; // NounMetadata thresholdNoun = this.store.getNoun("threshold") != null ? this.store.getNoun("threshold").getNoun(0) : null; // if (thresholdNoun != null && (thresholdNoun.getNounType() == PixelDataType.CONST_INT || thresholdNoun.getNounType() == PixelDataType.CONST_DECIMAL)) { // threshold = ((Number) thresholdNoun.getValue()).doubleValue(); // } // // ... perform processing with itemsList, mode, threshold ... // // return new NounMetadata(...); // } // }
This system allows for flexible parameter passing from Pixel to Java Reactors, supporting various data types and optional/required parameters.
A reactor's execute() method returns a NounMetadata object. It wraps the result with type and operation information used by the execution pipeline and API consumers.
| Accessor | Purpose |
|---|---|
getValue() |
The returned value, such as a string, number, list, map, or ITableDataFrame |
getNounType() |
A PixelDataType describing the value |
getOpType() |
A list of PixelOperationType values describing the operation or outcome |
PixelDataType includes CONST_STRING, CONST_INT, CONST_DECIMAL, BOOLEAN, MAP, VECTOR, and FRAME. Use the type that matches the result; VECTOR represents list values and FRAME represents an ITableDataFrame.
PixelOperationType describes what the reactor did. Common examples are OPERATION, FRAME, FRAME_DATA_CHANGE, FRAME_HEADERS_CHANGE, SUCCESS, WARNING, and ERROR. A result can carry multiple operation types.
These snippets illustrate return values inside a reactor's execute() method:
// Return a calculated scalar.
return new NounMetadata(sumResult, PixelDataType.CONST_DECIMAL);// Return a frame for subsequent Pixel operations.
return new NounMetadata(resultFrame, PixelDataType.FRAME, PixelOperationType.FRAME);// Return structured data.
return new NounMetadata(resultMap, PixelDataType.MAP, PixelOperationType.OPERATION);For feedback, use NounMetadata.getSuccessNounMessage(...), getWarningNounMessage(...), or getErrorNounMessage(...). These helpers set the appropriate data and operation types.
- Variable assignment: a result assigned in Pixel becomes available through the current Insight's
VarStorefor subsequent operations. - Pipelines: typed results can become input to the next reactor in a piped expression. The receiving reactor interprets the value according to its input contract.
- API responses: the execution pipeline collects results for serialization and return to the caller, including their data and operation types.
See the Insight object for execution state and Monolith integration for the HTTP request and response path.