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