blob: d576aaced4c0641bd434e34bc76361d2cbf64ed6 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.eis.interactions;
import java.io.*;
import java.util.*;
import jakarta.resource.*;
import jakarta.resource.cci.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.eis.*;
/**
* Defines the specification for a call to a JCA interaction.
* Builds the input and output records from the arguments.
*
* @author James
* @since OracleAS TopLink 10<i>g</i> (10.0.3)
*/
public abstract class EISInteraction extends DatasourceCall {
/** Adapter specific interaction spec. */
protected InteractionSpec interactionSpec;
/** Name of the function the interaction describes. */
protected String functionName;
/** Name to pass to the input record creation. */
protected String inputRecordName;
/** Adapter specific properties may be added. */
protected Map properties;
/** Holds database row of input values. */
protected AbstractRecord inputRow;
/** Defines the arguments to the interaction, these can be the values or argument names/fields the values come from. */
protected Vector arguments;
/**
* Defines the output argument names as defined in the output record for the interaction.
* This is shared as indexed interaction may still have mapped results.
*/
protected Vector outputArgumentNames;
/** Defines the field values the output arguments of the interaction map to. These are order dependent with the names. */
protected Vector outputArguments;
/** Path to the desired output record if nested. */
protected String outputResultPath;
/**
* PUBLIC:
* Default constructor.
*/
protected EISInteraction() {
super();
this.functionName = "";
this.inputRecordName = "";
this.outputResultPath = "";
}
/**
* PUBLIC:
* Define the output argument to the interaction and the field/argument name to be substitute for it.
* This is only required if an output row is not used.
* The parameterAndArgumentFieldName is the name of the output record argument expected,
* and is the field or argument name to be used to be used for it.
* These names are assumed to be the same, if not this method can be called with two arguments.
*/
public void addOutputArgument(String parameterAndArgumentFieldName) {
addOutputArgument(parameterAndArgumentFieldName, parameterAndArgumentFieldName);
}
/**
* PUBLIC:
* Define the output argument to the interaction and the field/argument name to be substitute for it.
* This is only required if an output row is not used.
* The parameterName is the name of the output record argument expected.
* The argumentFieldName is the field or argument name to be used to be used for it.
* If these names are the same (as they normally are) this method can be called with a single argument.
*/
public void addOutputArgument(String parameterName, String argumentFieldName) {
getOutputArgumentNames().addElement(parameterName);
getOutputArguments().addElement(new DatabaseField(argumentFieldName));
}
/**
* The argument fields or values to the interaction that map into the input record.
*/
public Vector getArguments() {
// This is lazy initialized to conserv space on calls that have no parameters.
if (arguments == null) {
arguments = new Vector();
}
return arguments;
}
/**
* INTERNAL:
* The argument fields or values to the interaction that map into the input record.
*/
public void setArguments(Vector arguments) {
this.arguments = arguments;
}
/**
* Return if argumented.
*/
public boolean hasArguments() {
return (arguments != null) && (!arguments.isEmpty());
}
@Override
public boolean isEISInteraction() {
return true;
}
/**
* PUBLIC:
* The output result path defines the root key for the MappedRecord that
* the desired interaction result is nested into.
* This is required for read interactions that need a nested record to build from the mapped object.
*/
public String getOutputResultPath() {
return outputResultPath;
}
/**
* PUBLIC:
* The output result path defines the root key for the MappedRecord that
* the desired interaction result is nested into.
* This is required for read interactions that need a nested record to build from the mapped object.
*/
public void setOutputResultPath(String outputResultPath) {
this.outputResultPath = outputResultPath;
}
/**
* The argument names for the output record.
*/
public Vector getOutputArgumentNames() {
// This is lazy initialized to conserv space on calls that have no parameters.
if (outputArgumentNames == null) {
outputArgumentNames = new Vector();
}
return outputArgumentNames;
}
/**
* The argument fields to the interaction that map into the output record.
*/
public Vector getOutputArguments() {
// This is lazy initialized to conserv space on calls that have no parameters.
if (outputArguments == null) {
outputArguments = new Vector();
}
return outputArguments;
}
/**
* The output arguments.
*/
public void setOutputArguments(Vector outputArguments) {
this.outputArguments = outputArguments;
}
/**
* Set the output argument names.
*/
public void setOutputArgumentNames(Vector outputArgumentNames) {
this.outputArgumentNames = outputArgumentNames;
}
/**
* Return if argumented.
*/
public boolean hasOutputArguments() {
return (outputArguments != null) && (!outputArguments.isEmpty());
}
/**
* Set the default record name from the descriptor.
*/
@Override
public void prepare(AbstractSession session) {
if (getInputRecordName().length() == 0) {
if ((getQuery() != null) && (getQuery().getDescriptor() instanceof EISDescriptor)) {
EISDescriptor descriptor = (EISDescriptor)getQuery().getDescriptor();
setInputRecordName(descriptor.getDataTypeName());
} else {
setInputRecordName("input");
}
}
super.prepare(session);
}
/**
* Create the appropriate record element for the data value.
* If the value is a collection, create a collection of elements,
* if the value is a map, create a nested map,
* otherwise just return the value (primitive data).
*/
public Object createRecordElement(String elementName, Object value, EISAccessor accessor) {
try {
Object element = value;
// Handle nested collections.
if (element instanceof List) {
// Convert each element in the list.
List values = (List)element;
List elements = new Vector(values.size());
for (int index = 0; index < values.size(); index++) {
elements.add(createRecordElement(elementName, values.get(index), accessor));
}
element = elements;
// Handle nested rows.
} else if (value instanceof AbstractRecord) {
AbstractRecord valuesRow = (AbstractRecord)value;
// The record name for the row must be determined,
// currently the SDK uses the table name of the row's field for this,
// ideally this would be a property on the row.
String recordName = elementName;
if (valuesRow.size() > 0) {
recordName = valuesRow.getFields().get(0).getTableName();
}
MappedRecord record = accessor.getRecordFactory().createMappedRecord(recordName);
for (Iterator<DatabaseField> keysIterator = valuesRow.getFields().iterator();
keysIterator.hasNext();) {
DatabaseField field = keysIterator.next();
Object elementValue = createRecordElement(field.getName(), valuesRow.get(field), accessor);
accessor.getEISPlatform().setValueInRecord(field.getName(), elementValue, record, accessor);
}
element = record;
}
return element;
} catch (ResourceException exception) {
throw EISException.resourceException(exception, accessor, null);
}
}
/**
* PUBLIC:
* Return the JCA InteractionSpec that defines this EIS interaction.
* The InteractionSpec is JCA adapter specific and typically
* defines the function name.
*/
public InteractionSpec getInteractionSpec() {
return interactionSpec;
}
/**
* PUBLIC:
* Set the JCA InteractionSpec that defines this EIS interaction.
* The InteractionSpec is JCA adapter specific and typically
* defines the function name.
*/
public void setInteractionSpec(InteractionSpec interactionSpec) {
this.interactionSpec = interactionSpec;
}
/**
* Return the string for logging purposes.
*/
@Override
public String getLogString(Accessor accessor) {
StringWriter writer = new StringWriter();
writer.write("Executing ");
writer.write(toString());
writer.write(Helper.cr());
writer.write("\tspec => ");
writer.write(String.valueOf(getInteractionSpec()));
writer.write(Helper.cr());
writer.write("\tproperties => ");
writer.write(String.valueOf(getProperties()));
writer.write(Helper.cr());
writer.write("\tinput => [");
if (!getParameters().isEmpty()) {
for (Iterator iterator = getParameters().iterator(); iterator.hasNext();) {
Object parameter = iterator.next();
writer.write(String.valueOf(parameter));
if (iterator.hasNext()) {
writer.write(", ");
} else {
writer.write("]");
}
}
} else {
writer.write(String.valueOf(getInputRow()));
writer.write("]");
}
return writer.toString();
}
/**
* Arguments to the interaction can be passed two ways.
* The entire argument row can be converted to the input record,
* or the arguments from the row can be translated into the interaction parameters.
*/
@Override
public void translate(AbstractRecord translationRow, AbstractRecord modifyRow, AbstractSession session) {
if (modifyRow != null) {
// Avoid clearing a prepared row.
setInputRow(modifyRow);
}
if (hasArguments()) {
List parametersValues = new ArrayList(getArguments().size());
for (int index = 0; index < getArguments().size(); index++) {
Object argument = getArguments().elementAt(index);
// The argument is either a value or a databasefield that needs to be translated.
if ((argument != null) && (argument instanceof DatabaseField)) {
DatabaseField field = (DatabaseField)argument;
Object value = translationRow.get(field);
//BUG#12345:modifyRow is null for reads
if ((value == null) && (modifyRow != null)) {
value = modifyRow.get(field);
}
parametersValues.add(value);
} else {
parametersValues.add(argument);
}
}
setParameters(parametersValues);
}
}
/**
* Create the appropriate input record for this interaction.
* Populate the data into the record from this interaction's arguments.
*/
public abstract jakarta.resource.cci.Record createInputRecord(EISAccessor accessor);
/**
* INTERNAL:
* Build a database row from the record returned from the interaction.
*/
public abstract AbstractRecord buildRow(jakarta.resource.cci.Record record, EISAccessor accessor);
/**
* Build a collection of database rows from the Record returned from the interaction.
* This handles IndexedRecords used as sets of result records,
* and a single MappedRecord with a list of result records.
*/
public Vector buildRows(jakarta.resource.cci.Record record, EISAccessor accessor) {
Vector rows = null;
if (record instanceof IndexedRecord) {
IndexedRecord indexedRecord = (IndexedRecord)record;
rows = new Vector(indexedRecord.size());
for (int index = 0; index < indexedRecord.size(); index++) {
Object element = indexedRecord.get(index);
if (element instanceof jakarta.resource.cci.Record) {
rows.addElement(buildRow((jakarta.resource.cci.Record)element, accessor));
} else {
// It is a single row record.
rows.add(buildRow(record, accessor));
return rows;
}
}
} else if (record instanceof MappedRecord) {
MappedRecord mappedRecord = (MappedRecord)record;
// Handle the case of a single output argument of the entire row contained within the return record.
if (getOutputResultPath().length() > 0) {
Object element = mappedRecord.get(getOutputResultPath());
// Handles nested rows inside an IndexedRecord, MappedRecord or List.
if (element instanceof IndexedRecord) {
return buildRows((IndexedRecord)element, accessor);
} else if (element instanceof MappedRecord) {
mappedRecord = (MappedRecord)element;
} else if (element instanceof List) {
List elements = (List)element;
rows = new Vector(elements.size());
for (int index = 0; index < elements.size(); index++) {
Object elementValue = elements.get(index);
if (elementValue instanceof jakarta.resource.cci.Record) {
rows.addElement(buildRow((jakarta.resource.cci.Record)elementValue, accessor));
} else {
rows.add(elementValue);
}
}
return rows;
}
}
rows = new Vector(1);
AbstractRecord row = new EISMappedRecord(mappedRecord, accessor);
rows.add(row);
} else {
rows = new Vector(1);
}
return rows;
}
/**
* PUBLIC:
* Return the function name of the interaction.
* A function name is a common properties for a CCI InteractionSpec.
* This cannot be generically set on the spec as it is not in the interface
* however the EIS platform may make use of this in build the interaction spec.
*/
public String getFunctionName() {
return functionName;
}
/**
* PUBLIC:
* Set the function name of the interaction.
* A function name is a common properties for a CCI InteractionSpec.
* This cannot be generically set on the spec as it is not in the interface
* however the EIS platform may make use of this in build the interaction spec.
*/
public void setFunctionName(String functionName) {
this.functionName = functionName;
}
/**
* PUBLIC:
* Return the input record name.
* This is used as the name passed to the adapter when creating the input record.
*/
public String getInputRecordName() {
return inputRecordName;
}
/**
* PUBLIC:
* Set the input record name.
* This is used as the name passed to the adapter when creating the input record.
*/
public void setInputRecordName(String recordName) {
this.inputRecordName = recordName;
}
/**
* Returns the adapter specific properties.
*/
public Map getProperties() {
if (properties == null) {
properties = new HashMap(5);
}
return properties;
}
/**
* PUBLIC:
* Returns the adapter property associated the given name.
* If the interaction is associated with a query that has a descriptor,
* The descriptor's properties are automatically inherited.
*/
public Object getProperty(String name) {
Object value = getProperties().get(name);
// Check descriptor property if available.
if ((value == null) && (getQuery() != null) && (getQuery().getDescriptor() != null)) {
value = getQuery().getDescriptor().getProperty(name);
}
return value;
}
/**
* Set the adapter specific properties.
*/
public void setProperties(Map properties) {
this.properties = properties;
}
/**
* PUBLIC:
* Set the adapter specific property.
*/
public void setProperty(String name, Object value) {
getProperties().put(name, value);
}
/**
* INTERNAL:
* Return the input database row.
*/
public AbstractRecord getInputRow() {
return inputRow;
}
/**
* PUBLIC:
* Set the input database row.
*/
public void setInputRow(AbstractRecord inputRow) {
this.inputRow = inputRow;
}
@Override
public String toString() {
return Helper.getShortClassName(getClass()) + "(" + getFunctionName() + ")";
}
}