Record Lock - nun, die Umsetzung.Wir haben bereits mehrere VisualForce Pages im Einsatz. Aus dem Grund ist das zuvor beschriebene Konzept darauf zugeschnitten. Selbstverständlich lässt sich der Code auch für Standard und Custom Objects verwenden, ohne den Einsatz von VisualForce Pages. Na ja, mindestens eine VF Page brauchen wir eigentlich schon - diejenige, die von dem dem "Edit" Button aufgerufen wird.
Ich zeige jetzt die wichtigsten Bausteine aus der Umsetzung.
1) Diese VF Page wird aufgerufen sobald man den "Edit" Button betätigt
Folgendes passiert dabei:
ein Objekt der Klasse "LockController" wird beim Aufruf der Page erzeugt. Dieses Objekt "entscheidet", ob der Datensatz im Lese- oder Bearbeitungsmodus geöffnet wird. Dafür sorgt die Aktion SwitchToPage aus der Klasse LockController.
Ich zeige jetzt die wichtigsten Bausteine aus der Umsetzung.
1) Diese VF Page wird aufgerufen sobald man den "Edit" Button betätigt
<apex:page
standardController="myObject"
title="{!myObject.Name}"
extensions="LockController"
action="{!SwitchToPage}">
<!--
READ MODE -->
<apex:include
pageName="myObjectRead"
rendered="{!isEditMode
== false}"/>
<!--
EDIT MODE -->
<apex:include
pageName="myObjectEdit"
rendered="{!isEditMode
== true}"/>
</apex:page>
Folgendes passiert dabei:
ein Objekt der Klasse "LockController" wird beim Aufruf der Page erzeugt. Dieses Objekt "entscheidet", ob der Datensatz im Lese- oder Bearbeitungsmodus geöffnet wird. Dafür sorgt die Aktion SwitchToPage aus der Klasse LockController.
public
void
SwitchToPage() {
//
remove old locks from the system
if(!lstLockToRemove.isEmpty())
delete
lstLockToRemove;
//no
locks for current object -> edit is allowed
if(isEditMode){
Lock__c
lock = new
Lock__c();
lock.Object_Id__c
= recordId;
lock.Object_Name__c
= ObjectName;
lock.User_Id__c
= CurrentUser;
lock.User_Name__c
= CurrentUserName;
lock.Start_Time__c
= datetime.now();
insert
lock;
}else{
//
Lock records found -> display message
if(isEditMode
== false){
String
LockUser = lock.User_Name__c;
DateTime
LockStartDateTime = lock.Start_Time__c;
DateTime
LockEndDateTime = computeEndDataTime(LockStartDateTime);
//build
the error message
String
msg = ErrorMessage;
msg
= msg.replace('%USER%',
LockUser);
msg
= msg.replace('%STARTDATETIME%',
LockStartDateTime.format());
msg
= msg.replace('%ENDDATETIME%',
LockEndDateTime.format());
ApexPages.addMessage(new
ApexPages.message(ApexPages.severity.ERROR, msg));
}
}
}
Im Edit-Modus wird ein neuer Lock-Datensatz erzeugt.
Im Read-Modus wird eine Fehlermeldung generiert. Diese Meldung ist als Custom Label mit Platzhaltern definiert. Platzhalter werden z.B. durch den Benutzernamen (Benutzer, der den aktuellen Datensatz im Zugriff hat), Lock-Start- und End-Zeitpunkt ersetzt.
2) Constructor der Klasse LockController erledigt die ganze Arbeit.
/************
CONSTRUCTOR ************/
public
LockController(ApexPages.StandardController controller) {
this.controller
= controller;
ObjectName
= 'My Object';
DTnow
= DateTime.now();
CurrentUser
= UserInfo.getUserId();
CurrentUserName
= UserInfo.getName();
ErrorMessage
= LABEL.Lock_Error_Message;
MaxLockPeriod
= 1;
//get
URL parameters
if(ApexPages.currentPage()
!= null){
recordId
= ApexPages.currentPage().getParameters().get('id');
retURL
= ApexPages.currentPage().getParameters().get('retURL');
}
//
List with old locks for current record
lstLockToRemove
= new
List<Lock__c>();
//get
lock for current object and fill list with old locks
lock
= getLockRecord(lstLockToRemove);
if(lock
== null) isEditMode
= true;
else isEditMode
= false;
}
Die getLockRecord() Funktion sucht nach einem Lock Record im System:
private
Lock__c
getLockRecord(List<Lock__c>
lstLockToRemove){
Lock__c
lock;
if(recordId
!= ''){
//
got through the list and compare current user name with the name from
the lock object
for(Lock__c
lr : [Select
Id, Object_Id__c, Object_Name__c, Start_Time__c, User_Id__c,
User_Name__c From Lock__c
where
Object_Id__c =: recordId]) {
if(lr.User_Id__c
== CurrentUser){
//
put this lock object for deletion (a new one will be created)
lstLockToRemove.add(lr);
}else{
//
we have a lock for the same object but another user -> compare
record's end date with the max allowed locking end date
DateTime
LockEndDateTime = computeEndDataTime(lr.Start_Time__c);
if(DTnow.getTime()
- LockEndDateTime.getTime() > 0)
lstLockToRemove.add(lr);
// put this lock object for
deletion (a new one will be created)
else
lock
= lr; // Lock is
valid. Use it for return
}
}
}
return
lock;
}
3) Beim Speichern bzw. Abbrechen soll der zuvor generierte Lock-Datensatz gelöscht werden.
Da wir VF Pages mit einer etwas komplexeren Logik im Einsatz haben, wird das Löschen über eine JS Funktion initiiert.
welches die DeleteMyLock Methode aufruft.
4) Die Löschung von Locks soll ebenfalls im Hintergrund durchgeführt werden. Dazu wird die Löschfunktion in regelmäßigen Abständen von dem sogenannten Apex-Scheduler aufgerufen.
Die Löschung habe ich als Batch geschrieben, damit auch große Mengen an Locks gelöscht werden können.
Es gibt eine Feinheit, die dafür sorgt, dass Locks nicht wie in meinem Fall 1 x pro Stunde, sondern 1 x alle 24 Stunden gelöscht werden.
Der Query String wird zusammengebaut. Dabei wird der Erstellungszeitpunkt der Locks mit dem von mir formatierten Datum verglichen: LockStartDateTime.format('yyyy-MM-dd\'T\'HH:mm:ss.000\'Z\'');
Da wir VF Pages mit einer etwas komplexeren Logik im Einsatz haben, wird das Löschen über eine JS Funktion initiiert.
<apex:commandButton
onclick="javascript:cancelPage();"
value="Cancel"
action="{!null}"
reRender="''"
/>
</apex:pageBlockButtons>
Die cancelPage() Methode ruft unter anderem die Löschfunktion für Locks auf:
<script
type="text/javascript">
function
cancelPage() {
machwas1();
machwas2();
deleteCurrentLock();
}
</script>
Jetzt kommt die Zauberei zum Einsatz. Für die JavaScript Methode deleteCurrentLock() gibt es ein gleichnamiges Apex-Tag,
<apex:form
>
<apex:actionFunction
name="machwas1"
action="{!machwas1}"
/>
<apex:actionFunction
name="deleteCurrentLock"
action="{!DeleteMyLock}"
/>
....
welches die DeleteMyLock Methode aufruft.
public
static
void
deleteMyLock(String recordId, String CurrentUser){
List<Lock__c>
lstLockToRemove = new
List<Lock__c>();
String
FeatureId = ApexPages.currentPage().getParameters().get('id');
String
UserId = UserInfo.getUserId();
if(
!(recordId == ''
|| CurrentUser == '')
){
//find and delete the
corresponding lock(s)
lstLockToRemove = [Select
Object_Id__c, User_Id__c From
Lock__c
where
Object_Id__c =: recordId AND
User_Id__c =: CurrentUser];
if(!lstLockToRemove.isEmpty())
delete
lstLockToRemove;
}
}
4) Die Löschung von Locks soll ebenfalls im Hintergrund durchgeführt werden. Dazu wird die Löschfunktion in regelmäßigen Abständen von dem sogenannten Apex-Scheduler aufgerufen.
global
class
BatchLockDeletion implements
Schedulable{
global
void
execute(SchedulableContext sc) {
//
Delete ol locks ----------------------------------
DeleteOldLocks
batch = new
DeleteOldLocks();
ID
jobId = database.executeBatch(batch);
Integer
iLockPeriod =
integer.valueOf(LockControllerHelper.getMaxLockPeriod());
//compute
the next Batch Job Start Time -------------
If(iLockPeriod
> 0){
DateTime
dtNextRun = DateTime.now();
dtNextRun
= dtNextRun.addHours(iLockPeriod);
String
ss = String.valueOf(dtNextRun.second());
String
mm = String.valueOf(dtNextRun.minute());
String
hh = String.valueOf(dtNextRun.hour());
String
d = String.valueOf(dtNextRun.day());
String
m = String.valueOf(dtNextRun.month());
String
y = String.valueOf(dtNextRun.year());
String
jobName = 'Delete locks: '
+ dtNextRun.formatLong();
String ScheduleParam = '00'
+ ' ' + mm
+ ' ' + hh
+ ' ' + d +
' ' + m + '
? ' + y;
//Create
a Job
if(
!(ScheduleParam == ''
|| ScheduleParam == null)
){
System.schedule(jobName, ScheduleParam, new
BatchLockDeletion());
}
}
}
}
Die Löschung habe ich als Batch geschrieben, damit auch große Mengen an Locks gelöscht werden können.
global
class
DeleteOldLocks implements
Database.Batchable<sObject> {
global
String query;
Integer
iLockPeriod;
/****
Constructor - build search string ************/
global
DeleteOldLocks(){
//get
the max lock duration. already computed by lock controller
iLockPeriod
= integer.valueOf(LockControllerHelper.getMaxLockPeriod());
//compute
the earliest Lock Start Time
DateTime
LockEndDateTime = DateTime.now();
DateTime
LockStartDateTime = LockEndDateTime.addHours(-iLockPeriod);
query
= 'Select Start_Time__c From Lock__c
where Start_Time__c < ' +
LockStartDateTime.format('yyyy-MM-dd\'T\'HH:mm:ss.000\'Z\'');
}
/***************
START() ************/
global
Database.QueryLocator start(Database.BatchableContext BC){
return
Database.getQueryLocator(query);
}
/***************
EXECUTE() ************/
global
void
execute(Database.BatchableContext BC,List<sObject> scope){
//
remove old locks from the system
if(!scope.isEmpty())
delete
scope;
//Database.emptyRecycleBin(scope);
}
/***************
FINISH() - without any logic ************/
global
void
finish(Database.BatchableContext BC){
}
}Es gibt eine Feinheit, die dafür sorgt, dass Locks nicht wie in meinem Fall 1 x pro Stunde, sondern 1 x alle 24 Stunden gelöscht werden.
Der Query String wird zusammengebaut. Dabei wird der Erstellungszeitpunkt der Locks mit dem von mir formatierten Datum verglichen: LockStartDateTime.format('yyyy-MM-dd\'T\'HH:mm:ss.000\'Z\'');
Setzt man dabei ..-hh-.. statt ..-HH-.. für Stunden ein, bekommt man die Zeit in 12 Stunden-Format zurück. Im System ist der Zeitpunkt dagegen im 24 Stunden-Format abgelegt.
Kommentare
Kommentar veröffentlichen