Sample Solution
This section presents a sample use case and one possible solution addressing the use case using a plug-in driven batch process.
Note: If the plug-in creation requirement is for a Production environment, always develop the code in your Development environment first, perform initial testing in that environment with sample data, then migrate the code to your Test environment using Content Migration Assistant (CMA) and perform the testing again on actual customer data, and then repeat the same steps to migrate the code to the Production environment.
Warning: Once data has been deleted and or updated using a plug-in driven batch process, the data cannot be restored. As such, Oracle strongly recommends thorough testing of your plug-in driven batch job.
Use Case
A large number of devices in Customer Cloud Service or Meter Solution Cloud Service have been created with an incorrect head end system configured directly on the device itself. Manually updating the devices would take too long so an automatic fix is required. The devices in question can be identified by the device type code of ROM-E-SMART-MTR and a head end system of MV90. The solution is to update each of these devices with a new head end value of L+G (Landis+Gyr).
Step One: Create the Batch Control
Creating the batch control begins by duplicating the OUAF delivered batch control F1-PDBG - Plug-in Driven Generic Template. This batch control provides the appropriate Java class as well as a set of default batch parameters.
For this solution, we will provide flexibility through a set of three batch job parameters:
• Device Type Code
• Current Head End
• Target Head End.
The first two will be used to identify the set of devices that need to be fixed and the last parameter will identify the new head end value that will be applied to each of the devices.
Here are the additional parameters that are to be defined:
Step Two: Create a Select Records Algorithm
This algorithm should be created by duplicating the sample OUAF algorithm type F1-PDB-SR - Select Records by Pre-defined Query and the plug-in script F1-PDBSelRec - Select Records by Pre-defined Query.
No changes are required to the algorithm type other than directing it to the new plug-in script that we have made.
The plug-in script logic will set the batch strategy and key field (code from the OUAF sample) with the addition of logic for taking the batch parameters for device type code and current head end system and setting them to the bind parameters from the query we provide.
There are two key collections in the hard parameters of this plug-in spot:
1. parm/hard/batchParms/BatchParm: this contains each of the batch parameters as a name/value pair. You will access this list by using the "Parameter Name" value defined on the batch control. For example, to retrieve the device type code you would retrieve the entry in this list with a name of "deviceTypeCode".
2. parm/hard/bindVariables/Bind: populating this list is the key purpose of this plug-in script. The field name is used to ensure the value is bound with proper data typing, so provide the field name that corresponds with the field the bind variable is being compared with. The name should be the name of the bind in the SQL you are providing. The value will be a value you extracted either from the batch parameters or from an algorithm soft parameter (in this example we are not using soft parameters for bind values).
Here is the sample logic we are using for our solution:
move "parm/soft[2]/value" to "parm/hard/batchStrategy";
move "parm/soft[3]/value" to "parm/hard/keyField";
//push the batch parameter for device type code to the bind variable
//the field name reflects the database column that this bind will be compared against
//the batch parameter name is based on the parameter name defined on the batch control
//the bind variable name should match the name in the query. in this instance we used "F1"
//because an OUAF zone was used to test the query and this way the query didn't need to change
move 'DEVICE_TYPE_CD' to "parm/hard/bindVariables/+Bind/fieldName";
move 'F1' to "parm/hard/bindVariables/Bind[last()]/name";
move "parm/hard/batchParms/BatchParm[name='deviceTypeCode']/value" to "parm/hard/bindVariables/Bind[last()]/value";
//push the batch parameter for the current head end to the bind variable "F2"
move 'D1_SPR_CD' to "parm/hard/bindVariables/+Bind/fieldName";
move 'F2' to "parm/hard/bindVariables/Bind[last()]/name";
move "parm/hard/batchParms/BatchParm[name='currentHeadEnd']/value" to "parm/hard/bindVariables/Bind[last()]/value";
Here is a sample of the hard parameter data area from an execution of this logic to give a better idea of the inputs to this script:
<root>
<parm>
<soft>
<value>select d1_device_id from d1_dvc
where device_type_cd = :F1
and d1_spr_cd = :F2
and d1_device_id between :f1.lowId AND :f1.highId
order by d1_device_id
</value>
</soft>
<soft>
<value>THDS</value>
</soft>
<soft>
<value>D1_DEVICE_ID</value>
</soft>
<hard>
<batchControl>
<id>ZZ-TSTDU</id>
</batchControl>
<batchParms>
<BatchParm>
<name>deviceTypeCode</name>
<value>ROM-E-SMART-MTR</value>
</BatchParm>
<BatchParm>
<name>targetHeadEnd</name>
<value>L+G</value>
</BatchParm>
<BatchParm>
<name>currentHeadEnd</name>
<value>MV90</value>
</BatchParm>
</batchParms>
<batchRunNumber>2</batchRunNumber>
<businessDate>2019-08-16</businessDate>
<isNewRun>false</isNewRun>
<numOfThreads>5</numOfThreads>
<batchStrategy>THDS</batchStrategy>
</hard>
</parm>
</root>
Lastly an algorithm should be created for the algorithm type with the following parameter values:
Step Three: Create a Process Records Algorithm
For this plug-in spot we will create an entirely new plug-in script that will perform the following high level steps:
1. Retrieve the device ID from the SQL results in the hard parameters
2. Use the device ID and a lite business object to read the device information
3. Retrieve the new head end from the batch parameters
4. Set the new head end to the lite business object and perform an update
There are two key collections within the hard parameters of this plug-in spot:
1. parm/hard/batchParms/BatchParm: this contains each of the batch parameters as a name/value pair. You will access this list by using the "Parameter Name" value defined on the batch control. For example, to retrieve the target head end you would retrieve the entry in this list with a name of "targetHeadEnd".
2. parm/hard/selectedFields: this contains the results of the query. Each entry here has a name that corresponds to a column in the select clause of your query and a value that contains the data selected.
Here is some sample logic for this solution:
//retrieve the device ID from the query results. this logic will receive exactly one device at a time.
//use that device ID to perform a read of the device using a lite BO
//a lite BO is being used to make sure this is performing as quickly as possible by eliminating unnecessary data collections
move "parm/hard/selectedFields/Field[name='D1_DEVICE_ID']/value" to "D1-DeviceDetailsLITE/meterId";
invokeBO 'D1-DeviceDetailsLITE' using "D1-DeviceDetailsLITE" for read;
//retrieve the value for the new head end from the batch parameter list
//use that value to perform an update with the lite BO.
//fastUpdate is being used because there is no further processing and a subsequent read after the update is not
//required
move "parm/hard/batchParms/BatchParm[name='targetHeadEnd']/value" to "D1-DeviceDetailsLITE/headEndSystem";
invokeBO 'D1-DeviceDetailsLITE' using "D1-DeviceDetailsLITE" for fastUpdate;
Here is a sample of the hard parameter data area from an execution of this logic to give a better idea of the inputs you have to this script:
<root>
<parm>
<hard>
<batchParms>
<BatchParm>
<name>currentHeadEnd</name>
<value>MV90</value>
</BatchParm>
<BatchParm>
<name>deviceTypeCode</name>
<value>ROM-E-SMART-MTR</value>
</BatchParm>
<BatchParm>
<name>targetHeadEnd</name>
<value>L+G</value>
</BatchParm>
</batchParms>
<batchParmsHelper>
<batchControlId>ZZ-TSTDU</batchControlId>
</batchParmsHelper>
<isFirst>true</isFirst>
<isLast>false</isLast>
<jobParms>
<batchCode>ZZ-TSTDU</batchCode>
<batchNumber>2</batchNumber>
<businessDate>2019-08-16</businessDate>
<numOfThreads>5</numOfThreads>
<threadNumber>1</threadNumber>
</jobParms>
<selectedFields>
<Field>
<name>D1_DEVICE_ID</name>
<value>114747438643</value>
</Field>
</selectedFields>
</hard>
</parm>
</root>
Update via DTO
In cases that an update needs to be on a maintenance object that is not business object maintained or an update is specific to a table field and not exposed on any business object schema, the object can be maintained via entity/DTO using Groovy Scripting.
10: Step Type: Edit Data
move "xs:date(parm/hard/batchParms/BatchParm[name='billDate']/value)" to $billDate;
if ("string($billDate) = $BLANK")
terminate with error(6, 16504);
end-if;
move "string(parm/hard/selectedFields/Field[name='BILL_ID']/value)" to $billId;
if ("string($billId) = $BLANK")
terminate;
end-if;
invokeGroovy 'updateUsageDTO';
invokeGroovy 'updateBillEntity';
20: Step Type: Groovy Members
void updateUsageDTO(){
Bill_Id billId = new Bill_Id(getStringScriptVariable('billId'))
Bill bill = billId.getEntity()
Account account = bill.getAccount();
String accountIdStr = account.getId().getTrimmedValue();
move accountIdStr, "ZZGetBSUsage/input/accountId";
PreparedStatementQuery query = createPreparedStatement("""
SELECT FT_ID, SIBLING_ID AS BSEG_ID, USAGE_ID
FROM CI_FT FT, C1_USAGE USG
WHERE FT.SA_ID IN (SELECT SA.SA_ID
FROM CI_SA SA WHERE ACCT_ID = :accountId
AND EXISTS (SELECT 'X' FROM CI_SA_TYPE SATYPE
WHERE SATYPE.SA_TYPE_CD = SA.SA_TYPE_CD
AND SATYPE.CIS_DIVISION = SA.CIS_DIVISION
AND SATYPE.SPECIAL_ROLE_FLG = 'BD')
)
AND FT.BILL_ID = ' '
AND FREEZE_SW = 'Y'
AND ft.sibling_id = usg.bseg_id
AND ft.ft_type_flg IN ( 'BS', 'BX')
""", "Get Usage Bill Segment")
query.bindString("accountId", accountIdStr, "ACCT_ID");
List<SQLResultRow> usageQueryList = query.list();
if (usageQueryList == null) return
BillSegment_Id nonBdBillSegment = new BillSegment_Id(getStringScriptVariable('nonBdBillSegmentId'));
for(SQLResultRow iter : usageQueryList){
String usageIdStr = iter.get("USAGE_ID")
Usage usage = new Usage_Id(usageIdStr).getEntity()
Usage_DTO usageDTO = usage.getDTO()
// update Bill Segment on Usage via DTO
usageDTO.setBillSegmentId(nonBdBillSegment)
usage.setDTO(usageDTO)
}
30: Step Type: Groovy Members
public void updateBillEntity() {
Bill_Id billId = new Bill_Id(getStringScriptVariable('billId'))
Bill bill = billId.getEntity()
Date accountingDate = getProcessDateTime().getDate()
BillCompletionInputData billCompletionInputData = BillCompletionInputData.Factory.newInstance()
billCompletionInputData.setAccountingDate(accountingDate)
billCompletionInputData.setBillDate( getDateScriptVariable('billDate'))
billCompletionInputData
.setUnableToCompleteBillAction(UnableToCompleteBillActionLookup.constants.SHOW_ERROR)
bill.complete(billCompletionInputData)
}
Step Four: Submitting the Batch Job
The final step of the process is to submit a batch job to perform the update. At this point there is no difference between this style of batch and any other.