Project

General

Profile

1
package edu.ucsb.nceas.metacat.dataone;
2

    
3
/**
4
 *  '$RCSfile$'
5
 *    Purpose: A Class for upgrading the database to version 1.5
6
 *  Copyright: 2000 Regents of the University of California and the
7
 *             National Center for Ecological Analysis and Synthesis
8
 *    Authors: Peter Slaughter
9
 *
10
 *   '$Author: leinfelder $'
11
 *     '$Date: 2014-02-10 11:52:33 -0800 (Mon, 10 Feb 2014) $'
12
 * '$Revision: 8585 $'
13
 *
14
 * This program is free software; you can redistribute it and/or modify
15
 * it under the terms of the GNU General Public License as published by
16
 * the Free Software Foundation; either version 2 of the License, or
17
 * (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU General Public License
25
 * along with this program; if not, write to the Free Software
26
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
 */
28

    
29
import java.math.BigInteger;
30
import java.sql.SQLException;
31
import java.util.ArrayList;
32
import java.util.Arrays;
33
import java.util.Date;
34
import java.util.HashMap;
35
import java.util.HashSet;
36
import java.util.List;
37
import java.util.Map;
38
import java.util.Set;
39

    
40
import org.apache.log4j.Logger;
41
import org.dataone.client.CNode;
42
import org.dataone.client.D1Client;
43
import org.dataone.service.exceptions.InvalidRequest;
44
import org.dataone.service.exceptions.InvalidToken;
45
import org.dataone.service.exceptions.NotAuthorized;
46
import org.dataone.service.exceptions.NotFound;
47
import org.dataone.service.exceptions.NotImplemented;
48
import org.dataone.service.exceptions.ServiceFailure;
49
import org.dataone.service.exceptions.VersionMismatch;
50
import org.dataone.service.types.v1.AccessPolicy;
51
import org.dataone.service.types.v1.Identifier;
52
import org.dataone.service.types.v1.ObjectFormatIdentifier;
53
import org.dataone.service.types.v1.ObjectInfo;
54
import org.dataone.service.types.v1.ObjectList;
55
import org.dataone.service.types.v1.Permission;
56
import org.dataone.service.types.v1.Session;
57
import org.dataone.service.types.v1.Subject;
58
import org.dataone.service.types.v1.SystemMetadata;
59

    
60
import edu.ucsb.nceas.metacat.AccessionNumberException;
61
import edu.ucsb.nceas.metacat.IdentifierManager;
62
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
63
import edu.ucsb.nceas.metacat.accesscontrol.AccessControlException;
64
import edu.ucsb.nceas.metacat.properties.PropertyService;
65
import edu.ucsb.nceas.metacat.shared.ServiceException;
66
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
67
import edu.ucsb.nceas.utilities.SortedProperties;
68

    
69
public class SyncAccessPolicy {
70

    
71
	private static Logger logMetacat = Logger.getLogger(SyncAccessPolicy.class);
72
	
73
	/**
74
	 * Synchronize access policy (from system metadata) of d1 member node with
75
	 * the corresponding controlling node.
76
	 * 
77
	 * @param objList
78
	 *            list of d1 objects to be synced
79
	 * @return syncedIds a list of pids that were synced with the CN
80
	 * @throws ServiceFailure
81
	 * @throws InvalidToken
82
	 * @throws NotAuthorized
83
	 * @throws NotFound
84
	 * @throws NotImplemented
85
	 * @throws McdbDocNotFoundException
86
	 * @throws InvalidRequest
87
	 * @throws VersionMismatch
88
	 * @throws SQLException
89
	 * @throws AccessionNumberException
90
	 * @throws NumberFormatException
91
	 */
92
	private List<Identifier> sync(ObjectList objList) throws ServiceFailure,
93
			InvalidToken, NotAuthorized, NotFound, NotImplemented,
94
			McdbDocNotFoundException, InvalidRequest, VersionMismatch,
95
			NumberFormatException, AccessionNumberException, SQLException, Exception {
96

    
97
		AccessPolicy cnAccessPolicy = null;
98
		AccessPolicy mnAccessPolicy = null;
99
		Identifier pid = new Identifier();
100
		ObjectInfo objInfo = null;
101
		Session session = null;
102
		List<Identifier> syncedIds = new ArrayList<Identifier>();
103
		SystemMetadata cnSysMeta = null;
104
		SystemMetadata mnSysMeta = null;
105

    
106
		CNode cn = null;
107
		
108
		try {
109
			cn = D1Client.getCN();
110
		} catch (ServiceFailure sf) {
111
			logMetacat.error("Unable to get Coordinating node name for this MN");
112
			throw new AccessControlException ("Unable to get Coordinating node name for this MN");
113
		}
114

    
115
		for (int i = objList.getStart(); i < objList.getCount(); i++) {
116

    
117
			objInfo = objList.getObjectInfo(i);
118
			pid = objInfo.getIdentifier();
119

    
120
			logMetacat.debug("Getting SM for pid: " + pid.getValue() + " i: " + i);
121
			try {
122
				// Get sm, access policy for requested localId
123
				mnSysMeta = IdentifierManager.getInstance().getSystemMetadata(
124
						pid.getValue());
125
			} catch (McdbDocNotFoundException e) {
126
				logMetacat.error("Error syncing access policy of pid: "
127
						+ pid.getValue() + " pid not found: " + e.getMessage());
128
				continue;
129
			} catch (Exception e) {
130
				logMetacat.error("Error syncing access policy of pid: "
131
						+ pid.getValue() + ". Message: " + e.getMessage());
132
				continue;
133
			}
134

    
135
			logMetacat.debug("Getting access policy for pid: " + pid.getValue());
136

    
137
			mnAccessPolicy = mnSysMeta.getAccessPolicy();
138
			
139
			// Get sm, access policy for requested pid from the CN
140
			try {
141
				cnSysMeta = cn.getSystemMetadata(pid);
142
			} catch (Exception e) {
143
				logMetacat.error("Error getting system metadata for pid: "
144
						+ pid.getValue() + " from cn: " + e.getMessage());
145
				continue;
146
			}
147
			logMetacat.debug("Getting access policy from CN for pid: "
148
					+ pid.getValue());
149
			cnAccessPolicy = cnSysMeta.getAccessPolicy();
150
			logMetacat.debug("Diffing access policies (MN,CN) for pid: "
151
					+ pid.getValue());
152

    
153
			// Compare access policies of MN and CN, and update if different.
154
			if (!isEqual(mnAccessPolicy, cnAccessPolicy)) {
155
				try {
156
					BigInteger serialVersion = cnSysMeta.getSerialVersion();
157
					logMetacat.debug("Requesting CN to set access policy for pid: "
158
							+ pid.getValue() + ", serial version: "
159
							+ serialVersion.toString());
160
					cn.setAccessPolicy(session, pid, mnAccessPolicy,
161
							serialVersion.longValue());
162
					logMetacat.debug("Successfully set access policy");
163
					// Add this pid to the list of pids that were successfully
164
					// synced
165
					syncedIds.add(pid);
166
				} catch (NotAuthorized na) {
167
					logMetacat
168
							.error("Error syncing CN with access policy of pid: "
169
									+ pid.getValue()
170
									+ " user not authorized: "
171
									+ na.getMessage());
172
					//throw na;
173
					continue;
174
				} catch (ServiceFailure sf) {
175
					logMetacat
176
							.error("Error syncing CN with access policy of pid: "
177
									+ pid.getValue()
178
									+ " Service failure: "
179
									+ sf.getMessage());
180
					//throw sf;
181
					continue;
182
				} catch (Exception e) {
183
					logMetacat
184
							.error("Error syncing CN with access policy of pid: "
185
									+ pid.getValue() + e.getMessage());
186
					//throw e;
187
					continue;
188
				}
189
			} else {
190
				logMetacat.warn("Skipping pid: " + pid.getValue());
191
			}
192
			logMetacat.debug("Done syncing access policy for pid: " + pid.getValue());
193
		}
194

    
195
		return syncedIds;
196
	}
197

    
198
	/**
199
	 * Convenience function that accepts a list of guids to sync
200
	 * 
201
	 * @param guidsToSync
202
	 *            list of guids to have access policy synced for
203
	 * @return syncedPids - list of pids that were actually synced with the CN
204
	 * @throws NumberFormatException
205
	 * @throws ServiceFailure
206
	 * @throws InvalidToken
207
	 * @throws NotAuthorized
208
	 * @throws NotFound
209
	 * @throws NotImplemented
210
	 * @throws McdbDocNotFoundException
211
	 * @throws InvalidRequest
212
	 * @throws VersionMismatch
213
	 * @throws AccessionNumberException
214
	 * @throws SQLException
215
	 */
216
	public List<Identifier> sync(List<String> guidsToSync)
217
			throws NumberFormatException, ServiceFailure, InvalidToken,
218
			NotAuthorized, NotFound, NotImplemented, McdbDocNotFoundException,
219
			InvalidRequest, VersionMismatch, AccessionNumberException,
220
			SQLException, Exception {
221
		List<Identifier> syncedPids = null;
222
		ObjectList objList = new ObjectList();
223
		SystemMetadata sm = new SystemMetadata();
224

    
225
		int start = 0;
226
		int count = 0; //guidsToSync.size();
227

    
228
		objList.setStart(start);
229

    
230
		// Convert the guids to d1 objects, as this is what
231
		// IdentifierManager.getInstance().querySystemMetadata returns in
232
		// syncAll, and
233
		// what sync(ObjectList...) expects
234
		for (String guid : guidsToSync) {
235
			try {
236
				sm = IdentifierManager.getInstance().getSystemMetadata(guid);
237
				count++;
238
			} catch (Exception e) {
239
				logMetacat.error("Error syncing access policy of pid: " + guid
240
						+ ". Message: " + e.getMessage());
241
				continue;
242
			}
243

    
244
			ObjectInfo oi = new ObjectInfo();
245
			Identifier id = new Identifier();
246
			id.setValue(guid);
247
			oi.setIdentifier(id);
248
			oi.setDateSysMetadataModified(sm.getDateSysMetadataModified());
249
			oi.setChecksum(sm.getChecksum());
250
			oi.setFormatId(sm.getFormatId());
251
			oi.setSize(sm.getSize());
252
			objList.addObjectInfo(oi);
253
		}
254
		
255
		int total = count;
256
		objList.setCount(count);
257
		objList.setTotal(total);
258

    
259
		syncedPids = sync(objList);
260
		return syncedPids;
261
	}
262

    
263
	public List<Identifier> syncAll() throws ServiceFailure, InvalidToken,
264
			NotAuthorized, NotFound, NotImplemented, McdbDocNotFoundException,
265
			InvalidRequest, VersionMismatch, NumberFormatException,
266
			AccessionNumberException, SQLException, PropertyNotFoundException,
267
			ServiceException, Exception {
268

    
269
		// For the following query parameters - null indicates that the query
270
		// will not be
271
		// constrained by the parameter.
272
		Date startTime = null;
273
		Date endTime = null;
274
		ObjectFormatIdentifier objectFormatId = null;
275
		Boolean replicaStatus = false; // return only pids for which this mn is
276
										// authoritative
277
		Integer start = 0;
278
		Integer count = Integer.valueOf(PropertyService.getProperty("database.webResultsetSize"));
279

    
280
		ObjectList objsToSync = IdentifierManager.getInstance()
281
				.querySystemMetadata(startTime, endTime, objectFormatId,
282
						replicaStatus, start, count);
283

    
284
		List<Identifier> syncedIds = sync(objsToSync);
285

    
286
		return syncedIds;
287
	}
288

    
289
	/**
290
	 * Compare two d1 system metadata access policies for equivalence.
291
	 * 
292
	 * @param ap1
293
	 *            - first access policy in the comparison
294
	 * @param ap2
295
	 *            - second access policy in the comparison
296
	 * @return	boolean - true if access policies are equivalent
297
	 */
298
	private boolean isEqual(AccessPolicy ap1, AccessPolicy ap2) {
299

    
300
		// Access Policy -> Access Rule -> (Subject, Permission)
301
		// i.e. Subject="slaughter", Permission="read,write,changePermission"
302
		// Get the list of access rules for each access policy
303
		List<org.dataone.service.types.v1.AccessRule> allowList1 = ap1
304
				.getAllowList();
305
		List<org.dataone.service.types.v1.AccessRule> allowList2 = ap2
306
				.getAllowList();
307

    
308
		HashMap<Subject, Set<Permission>> userPerms1 = new HashMap<Subject, Set<Permission>>();
309
		HashMap<Subject, Set<Permission>> userPerms2 = new HashMap<Subject, Set<Permission>>();
310

    
311
		// Load the permissions from the access rules into a hash of sets, i.e.,
312
		// so that we end up with this:
313
		// hash key: set of permissions, i.e.
314
		// ----------------------------
315
		// user1: read, write
316
		// user2: read
317
		// user3: read, write, change permissions
318
		// With the permissions in this structure, they can be easily compared
319
		Set<Permission> perms = null;
320
		// Process first access policy
321
		// Loop through access rules of this allowList
322
		for (org.dataone.service.types.v1.AccessRule accessRule : allowList1) {
323
			for (Subject s : accessRule.getSubjectList()) {
324
				if (userPerms1.containsKey(s)) {
325
					perms = userPerms1.get(s);
326
				} else {
327
					perms = new HashSet<Permission>();
328
				}
329
				for (Permission p : accessRule.getPermissionList()) {
330
					perms.add(p);
331
				}
332
				userPerms1.put(s, perms);
333
			}
334
		}
335

    
336
		// Process second access policy
337
		for (org.dataone.service.types.v1.AccessRule accessRule : allowList2) {
338
			for (Subject s : accessRule.getSubjectList()) {
339
				if (userPerms2.containsKey(s)) {
340
					perms = userPerms2.get(s);
341
				} else {
342
					perms = new HashSet<Permission>();
343
				}
344
				for (Permission p : accessRule.getPermissionList()) {
345
					perms.add(p);
346
				}
347
				userPerms2.put(s, perms);
348
			}
349
		}
350

    
351
		// Check if the number of access rules is the same for mn and cn. If not
352
		// then consider them not equal, without performing diff of each access rule.
353
		if (userPerms1.entrySet().size() != userPerms2.entrySet().size())
354
			return false;
355
		
356
		// Now perform the comparison of each access rule of access policy 1 to ap 2.
357
		// This test assumes that the mn perms are more complete than the cn perms.
358
		logMetacat.debug("Performing comparison of access policies");
359
		for (Map.Entry<Subject, Set<Permission>> entry : userPerms1.entrySet()) {
360
			// User name
361
			Subject s1 = entry.getKey();
362
			// Perms that the user holds
363
			Set<Permission> p1 = entry.getValue();
364
			logMetacat.debug("Checking access policy of user: " + s1.getValue());
365

    
366
			// Does this user exist in both access policies?
367
			if (userPerms2.containsKey(s1)) {
368
				if (!p1.equals(userPerms2.get(s1))) {
369
					logMetacat.debug("User access policies not equal");
370
					return false;
371
				}
372
			} else {
373
				logMetacat.debug("User access policy not found on CN");
374
				return false;
375
			}
376
		}
377

    
378
		// All comparisons have been passed, so the two access policies are
379
		// equivalent
380
		logMetacat.debug("Access policies are the same");
381
		return true;
382
	}
383

    
384
	/**
385
	 * Run pid synch script on the given pids
386
	 * Each argument is an individual pid 
387
	 * because pids cannot contain whitespace. 
388
	 * @param args
389
	 * @throws Exception
390
	 */
391
	public static void main(String[] args) throws Exception {
392

    
393
		// set up the properties based on the test/deployed configuration of the
394
		// workspace
395
		SortedProperties testProperties = new SortedProperties("test/test.properties");
396
		testProperties.load();
397
		String metacatContextDir = testProperties.getProperty("metacat.contextDir");
398
		PropertyService.getInstance(metacatContextDir + "/WEB-INF");
399
		
400
		ArrayList<String> guids = null;
401
		SyncAccessPolicy syncAP = new SyncAccessPolicy();
402

    
403
		if (args.length > 0) {
404
			try {
405
				guids = new ArrayList<String>(Arrays.asList(args));
406
				logMetacat.warn("Trying to syncing access policy for " + args.length + " pids");
407
				List<Identifier> synchedPids = syncAP.sync(guids);
408
				logMetacat.warn("Sunk access policies for " + synchedPids.size() + " pids");
409
			} catch (Exception e) {
410
				logMetacat.error("Error syncing pids, message: " + e.getMessage(), e);
411
				System.exit(1);
412
			}
413
		}
414
	}
415
}
(6-6/7)