Apex による共有管理の再適用
組織のデフォルトアクセスレベルが変更されると、オブジェクトの全レコードの共有が自動的に再適用されます。再適用により、適切な場合は Force.com 共有管理が追加されます。また、付与されたアクセス権が冗長である場合は、すべてのタイプの共有が削除されます。たとえば、オブジェクトの共有モデルが「非公開」から「公開/参照のみ」に変更されると、ユーザに「参照のみ」アクセス権を付与する共有の直接設定が削除されます。
Apex 共有管理を再適用するには、Salesforce が提供する再適用を行うインターフェースを実装する、Apex クラスを記述する必要があります。その後、[Apex 共有の再適用] 関連リストのカスタムオブジェクトの詳細ページで、クラスとカスタムオブジェクトを関連付ける必要があります。
Apex 共有の理由を指定するカスタムオブジェクトの詳細ページからこのクラスを実行します。ロックの問題により、アプリケーションのロジックに定義されたユーザへのアクセス権限の付与が Apex コードで実行されない場合、管理者はオブジェクトの Apex 共有管理を再適用する必要があることがあります。Database.executeBatch メソッドを使用して、Apex 共有管理の再適用をプログラムで呼び出すこともできます。
Apex 再適用の実行を監視または停止するには、[設定] から、[クイック検索] ボックスに「Apex ジョブ」と入力し、[Apex ジョブ] を選択します。
共有の再適用のための Apex クラスの作成
Apex 共有管理を再適用するには、再適用を行う Apex クラスを記述する必要があります。このクラスは、Salesforce が提供する Database.Batchable インターフェースを実装している必要があります。
Database.Batchable インターフェースは、Apex 共有管理の再適用など、すべての Apex の一括処理プロセスに使用されます。このインターフェースは、組織で複数回実装できます。実装する必要があるメソッドの詳細は、「Apex の一括処理の使用」を参照してください。
Apex 共有管理の再適用を作成する前に、ベストプラクティスについても検討してください。
Apex による共有管理の再適用の例
この例では、人事採用アプリケーションの構築中で、Job というオブジェクトが存在すると仮定しています。ジョブにリストされた採用担当者および採用担当マネージャにレコードへのアクセス権が付与されていることを確認したいと考えています。次の Apex クラスでこの検証を実行できます。この例では、User レコードと関連付けられた、Hiring_Manager および Recruiter という 2 つの参照項目を持つ Job というカスタムオブジェクトが必要です。また、Job カスタムオブジェクトには、Hiring_Manager と Recruiter という 2 つの共有の理由を追加する必要があります。このサンプルを実行する前に、メールアドレスを、エラー通知とジョブ完了通知を送信する有効なメールアドレスに置き換えます。
1global class JobSharingRecalc implements Database.Batchable<sObject> {
2
3 // String to hold email address that emails will be sent to.
4 // Replace its value with a valid email address.
5 static String emailAddress = 'admin@yourcompany.com';
6
7 // The start method is called at the beginning of a sharing recalculation.
8 // This method returns a SOQL query locator containing the records
9 // to be recalculated.
10 global Database.QueryLocator start(Database.BatchableContext BC){
11 return Database.getQueryLocator([SELECT Id, Hiring_Manager__c, Recruiter__c
12 FROM Job__c]);
13 }
14
15 // The executeBatch method is called for each chunk of records returned from start.
16 global void execute(Database.BatchableContext BC, List<sObject> scope){
17 // Create a map for the chunk of records passed into method.
18 Map<ID, Job__c> jobMap = new Map<ID, Job__c>((List<Job__c>)scope);
19
20 // Create a list of Job__Share objects to be inserted.
21 List<Job__Share> newJobShrs = new List<Job__Share>();
22
23 // Locate all existing sharing records for the Job records in the batch.
24 // Only records using an Apex sharing reason for this app should be returned.
25 List<Job__Share> oldJobShrs = [SELECT Id FROM Job__Share WHERE ParentId IN
26 :jobMap.keySet() AND
27 (RowCause = :Schema.Job__Share.rowCause.Recruiter__c OR
28 RowCause = :Schema.Job__Share.rowCause.Hiring_Manager__c)];
29
30 // Construct new sharing records for the hiring manager and recruiter
31 // on each Job record.
32 for(Job__c job : jobMap.values()){
33 Job__Share jobHMShr = new Job__Share();
34 Job__Share jobRecShr = new Job__Share();
35
36 // Set the ID of user (hiring manager) on the Job record being granted access.
37 jobHMShr.UserOrGroupId = job.Hiring_Manager__c;
38
39 // The hiring manager on the job should always have 'Read Only' access.
40 jobHMShr.AccessLevel = 'Read';
41
42 // The ID of the record being shared
43 jobHMShr.ParentId = job.Id;
44
45 // Set the rowCause to the Apex sharing reason for hiring manager.
46 // This establishes the sharing record as Apex managed sharing.
47 jobHMShr.RowCause = Schema.Job__Share.RowCause.Hiring_Manager__c;
48
49 // Add sharing record to list for insertion.
50 newJobShrs.add(jobHMShr);
51
52 // Set the ID of user (recruiter) on the Job record being granted access.
53 jobRecShr.UserOrGroupId = job.Recruiter__c;
54
55 // The recruiter on the job should always have 'Read/Write' access.
56 jobRecShr.AccessLevel = 'Edit';
57
58 // The ID of the record being shared
59 jobRecShr.ParentId = job.Id;
60
61 // Set the rowCause to the Apex sharing reason for recruiter.
62 // This establishes the sharing record as Apex managed sharing.
63 jobRecShr.RowCause = Schema.Job__Share.RowCause.Recruiter__c;
64
65 // Add the sharing record to the list for insertion.
66 newJobShrs.add(jobRecShr);
67 }
68
69 try {
70 // Delete the existing sharing records.
71 // This allows new sharing records to be written from scratch.
72 Delete oldJobShrs;
73
74 // Insert the new sharing records and capture the save result.
75 // The false parameter allows for partial processing if multiple records are
76 // passed into operation.
77 Database.SaveResult[] lsr = Database.insert(newJobShrs,false);
78
79 // Process the save results for insert.
80 for(Database.SaveResult sr : lsr){
81 if(!sr.isSuccess()){
82 // Get the first save result error.
83 Database.Error err = sr.getErrors()[0];
84
85 // Check if the error is related to trivial access level.
86 // Access levels equal or more permissive than the object's default
87 // access level are not allowed.
88 // These sharing records are not required and thus an insert exception
89 // is acceptable.
90 if(!(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION
91 && err.getMessage().contains('AccessLevel'))){
92 // Error is not related to trivial access level.
93 // Send an email to the Apex job's submitter.
94 Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
95 String[] toAddresses = new String[] {emailAddress};
96 mail.setToAddresses(toAddresses);
97 mail.setSubject('Apex Sharing Recalculation Exception');
98 mail.setPlainTextBody(
99 'The Apex sharing recalculation threw the following exception: ' +
100 err.getMessage());
101 Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
102 }
103 }
104 }
105 } catch(DmlException e) {
106 // Send an email to the Apex job's submitter on failure.
107 Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
108 String[] toAddresses = new String[] {emailAddress};
109 mail.setToAddresses(toAddresses);
110 mail.setSubject('Apex Sharing Recalculation Exception');
111 mail.setPlainTextBody(
112 'The Apex sharing recalculation threw the following exception: ' +
113 e.getMessage());
114 Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
115 }
116 }
117
118 // The finish method is called at the end of a sharing recalculation.
119 global void finish(Database.BatchableContext BC){
120 // Send an email to the Apex job's submitter notifying of job completion.
121 Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
122 String[] toAddresses = new String[] {emailAddress};
123 mail.setToAddresses(toAddresses);
124 mail.setSubject('Apex Sharing Recalculation Completed.');
125 mail.setPlainTextBody
126 ('The Apex sharing recalculation finished processing');
127 Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
128 }
129
130}Apex による共有管理の再適用のテスト
この例では、5 つの Job レコードを挿入し、前の例で使用した一括処理クラスに実装される一括処理ジョブを呼び出します。この例では、User レコードと関連付けられた、Hiring_Manager および Recruiter という 2 つの参照項目を持つ Job というカスタムオブジェクトが必要です。また、Job カスタムオブジェクトには、Hiring_Manager と Recruiter という 2 つの共有の理由を追加する必要があります。このテストを実行する前に、組織全体の Job のデフォルト共有設定を [非公開] に設定します。テストからはメールメッセージは送信されないため、また、一括処理クラスはテストメソッドによって呼び出されるため、この場合、メール通知は送信されません。
1@isTest
2private class JobSharingTester {
3
4 // Test for the JobSharingRecalc class
5 static testMethod void testApexSharing(){
6 // Instantiate the class implementing the Database.Batchable interface.
7 JobSharingRecalc recalc = new JobSharingRecalc();
8
9 // Select users for the test.
10 List<User> users = [SELECT Id FROM User WHERE IsActive = true LIMIT 2];
11 ID User1Id = users[0].Id;
12 ID User2Id = users[1].Id;
13
14 // Insert some test job records.
15 List<Job__c> testJobs = new List<Job__c>();
16 for (Integer i=0;i<5;i++) {
17 Job__c j = new Job__c();
18 j.Name = 'Test Job ' + i;
19 j.Recruiter__c = User1Id;
20 j.Hiring_Manager__c = User2Id;
21 testJobs.add(j);
22 }
23 insert testJobs;
24
25 Test.startTest();
26
27 // Invoke the Batch class.
28 String jobId = Database.executeBatch(recalc);
29
30 Test.stopTest();
31
32 // Get the Apex job and verify there are no errors.
33 AsyncApexJob aaj = [Select JobType, TotalJobItems, JobItemsProcessed, Status,
34 CompletedDate, CreatedDate, NumberOfErrors
35 from AsyncApexJob where Id = :jobId];
36 System.assertEquals(0, aaj.NumberOfErrors);
37
38 // This query returns jobs and related sharing records that were inserted
39 // by the batch job's execute method.
40 List<Job__c> jobs = [SELECT Id, Hiring_Manager__c, Recruiter__c,
41 (SELECT Id, ParentId, UserOrGroupId, AccessLevel, RowCause FROM Shares
42 WHERE (RowCause = :Schema.Job__Share.rowCause.Recruiter__c OR
43 RowCause = :Schema.Job__Share.rowCause.Hiring_Manager__c))
44 FROM Job__c];
45
46 // Validate that Apex managed sharing exists on jobs.
47 for(Job__c job : jobs){
48 // Two Apex managed sharing records should exist for each job
49 // when using the Private org-wide default.
50 System.assert(job.Shares.size() == 2);
51
52 for(Job__Share jobShr : job.Shares){
53 // Test the sharing record for hiring manager on job.
54 if(jobShr.RowCause == Schema.Job__Share.RowCause.Hiring_Manager__c){
55 System.assertEquals(jobShr.UserOrGroupId,job.Hiring_Manager__c);
56 System.assertEquals(jobShr.AccessLevel,'Read');
57 }
58 // Test the sharing record for recruiter on job.
59 else if(jobShr.RowCause == Schema.Job__Share.RowCause.Recruiter__c){
60 System.assertEquals(jobShr.UserOrGroupId,job.Recruiter__c);
61 System.assertEquals(jobShr.AccessLevel,'Edit');
62 }
63 }
64 }
65 }
66}再適用に使用される Apex クラスの関連付け
再適用に使用される Apex クラスはカスタムオブジェクトと関連付けられている必要があります。
- カスタムオブジェクトの管理設定から [Apex 共有再適用] に移動します。
- このオブジェクトの Apex 共有を再適用する Apex クラスを選択します。選択するクラスは、Database.Batchable インターフェースを実装している必要があります。同じ Apex クラスを、同じカスタムオブジェクトと複数関連付けることはできません。
- [保存] をクリックします。