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:
Sequence
Parameter Name
Description
Detail Description
Required
10
deviceTypeCode
Device Type Code
Device Type Code of the devices to be updated
Yes
20
currentHeadEnd
Current Head End
Current Head End of the devices to be updated
Yes
30
targetHeadEnd
Target Head End
The new Head End that will be applied to the devices selected
Yes
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:
 
Parameter
Sequence
Value
Comments
SQL
1
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
This is a simple SQL that retrieves all the device IDs within a given ID range that have the input device type code and head end system.
The SQL sets up 4 bind variables:
1. :F1 is the device type code that will be provided in the batch parameters
2. :F2 is the head end that will be provided in the batch parameters
3. :f1.lowId is a special parameter that will be injected by the batch control with the thread specific low ID. The plug-in script does not need to worry about this bind variable.
4. :f1.highId is similar to :f1.lowId except it represents the high ID for the thread
The low and high ID are part of the query because of our choice for the next parameter.
NOTE: there is a 2000 character limit on algorithm parameters so SQL provided must be succinct.
 
Batch Category
2
THDS
To take advantage of multi-threading we have set the batch strategy to THDS which means it will thread based on a range of a table key. In this instance we will use the device ID and the batch program will evenly divide the possible range of device across the available threads and the selecting of records will be done within each thread.
This is no different than our standard batch threading mechanism.
If for some reason the SQL to identify the items to fix didn't fall nicely into a master data key you can use the 'JOBS' strategy which will first select the records and then evenly divide them up across the available threads.
Key Field
3
D1_DEVICE_ID
This is identifying which of the returned fields by the query is being used in the threading strategy.
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.