Project

General

Profile

1 8452 slaughter
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 8510 slaughter
 *   '$Author$'
11 8511 slaughter
 *     '$Date$'
12
 * '$Revision$'
13 8452 slaughter
 *
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 8590 slaughter
import java.util.concurrent.ExecutorService;
40
import java.util.concurrent.Executors;
41 8452 slaughter
42
import org.apache.log4j.Logger;
43
import org.dataone.client.CNode;
44
import org.dataone.client.D1Client;
45
import org.dataone.service.exceptions.InvalidRequest;
46
import org.dataone.service.exceptions.InvalidToken;
47
import org.dataone.service.exceptions.NotAuthorized;
48
import org.dataone.service.exceptions.NotFound;
49
import org.dataone.service.exceptions.NotImplemented;
50
import org.dataone.service.exceptions.ServiceFailure;
51
import org.dataone.service.exceptions.VersionMismatch;
52
import org.dataone.service.types.v1.AccessPolicy;
53
import org.dataone.service.types.v1.Identifier;
54
import org.dataone.service.types.v1.ObjectFormatIdentifier;
55
import org.dataone.service.types.v1.ObjectInfo;
56
import org.dataone.service.types.v1.ObjectList;
57
import org.dataone.service.types.v1.Permission;
58
import org.dataone.service.types.v1.Session;
59
import org.dataone.service.types.v1.Subject;
60
import org.dataone.service.types.v1.SystemMetadata;
61
62
import edu.ucsb.nceas.metacat.AccessionNumberException;
63
import edu.ucsb.nceas.metacat.IdentifierManager;
64
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
65 8561 slaughter
import edu.ucsb.nceas.metacat.accesscontrol.AccessControlException;
66 8590 slaughter
import edu.ucsb.nceas.metacat.admin.AdminException;
67 8452 slaughter
import edu.ucsb.nceas.metacat.properties.PropertyService;
68
import edu.ucsb.nceas.metacat.shared.ServiceException;
69
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
70
import edu.ucsb.nceas.utilities.SortedProperties;
71
72
public class SyncAccessPolicy {
73
74
	private static Logger logMetacat = Logger.getLogger(SyncAccessPolicy.class);
75 8590 slaughter
76 8452 slaughter
	/**
77
	 * Synchronize access policy (from system metadata) of d1 member node with
78
	 * the corresponding controlling node.
79
	 *
80
	 * @param objList
81
	 *            list of d1 objects to be synced
82
	 * @return syncedIds a list of pids that were synced with the CN
83
	 * @throws ServiceFailure
84
	 * @throws InvalidToken
85
	 * @throws NotAuthorized
86
	 * @throws NotFound
87
	 * @throws NotImplemented
88
	 * @throws McdbDocNotFoundException
89
	 * @throws InvalidRequest
90
	 * @throws VersionMismatch
91
	 * @throws SQLException
92
	 * @throws AccessionNumberException
93
	 * @throws NumberFormatException
94
	 */
95 8585 leinfelder
	private List<Identifier> sync(ObjectList objList) throws ServiceFailure,
96 8452 slaughter
			InvalidToken, NotAuthorized, NotFound, NotImplemented,
97
			McdbDocNotFoundException, InvalidRequest, VersionMismatch,
98 8590 slaughter
			NumberFormatException, AccessionNumberException, SQLException,
99
			Exception {
100 8452 slaughter
101
		AccessPolicy cnAccessPolicy = null;
102
		AccessPolicy mnAccessPolicy = null;
103 8510 slaughter
		Identifier pid = new Identifier();
104 8452 slaughter
		ObjectInfo objInfo = null;
105
		Session session = null;
106
		List<Identifier> syncedIds = new ArrayList<Identifier>();
107
		SystemMetadata cnSysMeta = null;
108
		SystemMetadata mnSysMeta = null;
109
110 8561 slaughter
		CNode cn = null;
111 8590 slaughter
112 8561 slaughter
		try {
113
			cn = D1Client.getCN();
114 8592 slaughter
			logMetacat.debug("Will sync access policies to CN id: "
115
					+ cn.getNodeId() + " with info: " + cn.toString());
116 8561 slaughter
		} catch (ServiceFailure sf) {
117 8590 slaughter
			logMetacat
118
					.error("Unable to get Coordinating node name for this MN");
119
			throw new AccessControlException(
120
					"Unable to get Coordinating node name for this MN");
121 8561 slaughter
		}
122 8452 slaughter
123 8491 slaughter
		for (int i = objList.getStart(); i < objList.getCount(); i++) {
124 8452 slaughter
125
			objInfo = objList.getObjectInfo(i);
126
			pid = objInfo.getIdentifier();
127 8491 slaughter
128 8590 slaughter
			logMetacat.debug("Getting SM for pid: " + pid.getValue() + " i: "
129
					+ i);
130 8452 slaughter
			try {
131
				// Get sm, access policy for requested localId
132
				mnSysMeta = IdentifierManager.getInstance().getSystemMetadata(
133
						pid.getValue());
134
			} catch (McdbDocNotFoundException e) {
135
				logMetacat.error("Error syncing access policy of pid: "
136
						+ pid.getValue() + " pid not found: " + e.getMessage());
137 8585 leinfelder
				continue;
138 8452 slaughter
			} catch (Exception e) {
139
				logMetacat.error("Error syncing access policy of pid: "
140 8585 leinfelder
						+ pid.getValue() + ". Message: " + e.getMessage());
141
				continue;
142 8452 slaughter
			}
143
144 8590 slaughter
			logMetacat
145
					.debug("Getting access policy for pid: " + pid.getValue());
146 8491 slaughter
147 8452 slaughter
			mnAccessPolicy = mnSysMeta.getAccessPolicy();
148 8590 slaughter
149 8452 slaughter
			// Get sm, access policy for requested pid from the CN
150
			try {
151
				cnSysMeta = cn.getSystemMetadata(pid);
152
			} catch (Exception e) {
153
				logMetacat.error("Error getting system metadata for pid: "
154
						+ pid.getValue() + " from cn: " + e.getMessage());
155 8510 slaughter
				continue;
156 8452 slaughter
			}
157 8491 slaughter
			logMetacat.debug("Getting access policy from CN for pid: "
158
					+ pid.getValue());
159 8452 slaughter
			cnAccessPolicy = cnSysMeta.getAccessPolicy();
160 8491 slaughter
			logMetacat.debug("Diffing access policies (MN,CN) for pid: "
161
					+ pid.getValue());
162 8452 slaughter
163 8491 slaughter
			// Compare access policies of MN and CN, and update if different.
164 8452 slaughter
			if (!isEqual(mnAccessPolicy, cnAccessPolicy)) {
165
				try {
166
					BigInteger serialVersion = cnSysMeta.getSerialVersion();
167 8590 slaughter
					logMetacat
168
							.debug("Requesting CN to set access policy for pid: "
169
									+ pid.getValue()
170
									+ ", serial version: "
171
									+ serialVersion.toString());
172 8491 slaughter
					cn.setAccessPolicy(session, pid, mnAccessPolicy,
173 8452 slaughter
							serialVersion.longValue());
174 8491 slaughter
					logMetacat.debug("Successfully set access policy");
175
					// Add this pid to the list of pids that were successfully
176
					// synced
177 8452 slaughter
					syncedIds.add(pid);
178 8510 slaughter
				} catch (NotAuthorized na) {
179
					logMetacat
180
							.error("Error syncing CN with access policy of pid: "
181
									+ pid.getValue()
182
									+ " user not authorized: "
183
									+ na.getMessage());
184 8590 slaughter
					// throw na;
185 8585 leinfelder
					continue;
186 8510 slaughter
				} catch (ServiceFailure sf) {
187
					logMetacat
188
							.error("Error syncing CN with access policy of pid: "
189
									+ pid.getValue()
190
									+ " Service failure: "
191
									+ sf.getMessage());
192 8592 slaughter
					sf.printStackTrace();
193
					logMetacat.debug("Cause: " + sf.getCause());
194 8590 slaughter
					// throw sf;
195 8585 leinfelder
					continue;
196 8452 slaughter
				} catch (Exception e) {
197 8510 slaughter
					logMetacat
198
							.error("Error syncing CN with access policy of pid: "
199
									+ pid.getValue() + e.getMessage());
200 8590 slaughter
					// throw e;
201 8585 leinfelder
					continue;
202 8452 slaughter
				}
203 8585 leinfelder
			} else {
204
				logMetacat.warn("Skipping pid: " + pid.getValue());
205 8452 slaughter
			}
206 8590 slaughter
			logMetacat.debug("Done syncing access policy for pid: "
207
					+ pid.getValue());
208 8452 slaughter
		}
209
210
		return syncedIds;
211
	}
212
213
	/**
214
	 * Convenience function that accepts a list of guids to sync
215
	 *
216
	 * @param guidsToSync
217
	 *            list of guids to have access policy synced for
218
	 * @return syncedPids - list of pids that were actually synced with the CN
219
	 * @throws NumberFormatException
220
	 * @throws ServiceFailure
221
	 * @throws InvalidToken
222
	 * @throws NotAuthorized
223
	 * @throws NotFound
224
	 * @throws NotImplemented
225
	 * @throws McdbDocNotFoundException
226
	 * @throws InvalidRequest
227
	 * @throws VersionMismatch
228
	 * @throws AccessionNumberException
229
	 * @throws SQLException
230
	 */
231
	public List<Identifier> sync(List<String> guidsToSync)
232
			throws NumberFormatException, ServiceFailure, InvalidToken,
233
			NotAuthorized, NotFound, NotImplemented, McdbDocNotFoundException,
234
			InvalidRequest, VersionMismatch, AccessionNumberException,
235 8511 slaughter
			SQLException, Exception {
236 8452 slaughter
		List<Identifier> syncedPids = null;
237
		ObjectList objList = new ObjectList();
238
		SystemMetadata sm = new SystemMetadata();
239
240
		int start = 0;
241 8590 slaughter
		int count = 0; // guidsToSync.size();
242 8452 slaughter
243
		objList.setStart(start);
244
245
		// Convert the guids to d1 objects, as this is what
246
		// IdentifierManager.getInstance().querySystemMetadata returns in
247
		// syncAll, and
248
		// what sync(ObjectList...) expects
249
		for (String guid : guidsToSync) {
250
			try {
251
				sm = IdentifierManager.getInstance().getSystemMetadata(guid);
252 8585 leinfelder
				count++;
253 8452 slaughter
			} catch (Exception e) {
254
				logMetacat.error("Error syncing access policy of pid: " + guid
255 8585 leinfelder
						+ ". Message: " + e.getMessage());
256
				continue;
257 8452 slaughter
			}
258
259
			ObjectInfo oi = new ObjectInfo();
260
			Identifier id = new Identifier();
261
			id.setValue(guid);
262
			oi.setIdentifier(id);
263
			oi.setDateSysMetadataModified(sm.getDateSysMetadataModified());
264
			oi.setChecksum(sm.getChecksum());
265
			oi.setFormatId(sm.getFormatId());
266
			oi.setSize(sm.getSize());
267
			objList.addObjectInfo(oi);
268
		}
269 8590 slaughter
270 8585 leinfelder
		int total = count;
271
		objList.setCount(count);
272
		objList.setTotal(total);
273 8452 slaughter
274
		syncedPids = sync(objList);
275
		return syncedPids;
276
	}
277
278 8590 slaughter
	/**
279
	 * For all guids for which current MN is authoritative, check that access
280
	 * policy is synced with CN.
281
	 *
282
	 * @return void
283
	 */
284
	public void syncAll() throws ServiceFailure, InvalidToken, NotAuthorized,
285
			NotFound, NotImplemented, McdbDocNotFoundException, InvalidRequest,
286
			VersionMismatch, NumberFormatException, AccessionNumberException,
287
			SQLException, PropertyNotFoundException, ServiceException,
288
			Exception {
289 8452 slaughter
290 8590 slaughter
		SyncTask st = new SyncTask();
291
		// Create a single thread to run the sync of all guids in
292
		ExecutorService executor = Executors.newSingleThreadExecutor();
293
		logMetacat.debug("syncAll starting thread");
294
		executor.execute(st);
295
		// Only one task will run on this thread
296
		executor.shutdown();
297 8452 slaughter
298 8590 slaughter
		// return syncedIds;
299
	}
300 8452 slaughter
301 8590 slaughter
	/**
302
	 * Perform syncAll in a single thread.
303
	 *
304
	 * @return void
305
	 */
306
	private class SyncTask implements Runnable {
307 8452 slaughter
308 8590 slaughter
		@Override
309
		public void run() {
310
			// For the following query parameters - null indicates that the
311
			// query
312
			// will not be
313
			// constrained by the parameter.
314
			Date startTime = null;
315
			Date endTime = null;
316
			ObjectFormatIdentifier objectFormatId = null;
317
			Boolean replicaStatus = false; // return only pids for which this mn
318
											// is
319
320
			ObjectList objsToSync = null;
321
			Integer count = 0;
322
			Integer start = 0;
323
			Integer total = 0;
324
			List<Identifier> tmpIds = null;
325
			List<Identifier> syncedIds = new ArrayList<Identifier>();
326
327
			try {
328
				count = Integer.valueOf(PropertyService
329
						.getProperty("database.webResultsetSize"));
330
			} catch (NumberFormatException e1) {
331
				logMetacat
332
						.error("Error in  propery file for format of database.webResultsetSize, will use 1000");
333
				e1.printStackTrace();
334
				count = 1000;
335
			} catch (PropertyNotFoundException e1) {
336
				logMetacat
337
						.error("Error reading propery file for database.webResultsetSize, will use 1000");
338
				e1.printStackTrace();
339
				count = 1000;
340
			}
341
342
			// Get the total count of guids before we start syncing
343
			try {
344
				objsToSync = IdentifierManager.getInstance()
345
						.querySystemMetadata(startTime, endTime,
346
								objectFormatId, replicaStatus, start, count);
347
348
				logMetacat.debug("syncTask total # of guids: "
349 8592 slaughter
						+ objsToSync.getTotal() + ", count for this page: "
350
						+ objsToSync.getCount());
351 8590 slaughter
			} catch (Exception e) {
352
				logMetacat.error("Error syncing ids");
353
			}
354 8592 slaughter
355 8590 slaughter
			total = objsToSync.getTotal();
356
357 8592 slaughter
			// The first loop might have fewer results than the requested count
358
			// value from the properties file,
359
			// so in this case use count returned from IdentiferManger for the
360
			// loop count/increment (loop will only execute once).
361
			if (objsToSync.getCount() < count)
362
				count = objsToSync.getCount();
363
364
			for (int i = 0; (i + count - 1) < total; i += count) {
365 8590 slaughter
				try {
366
					logMetacat.debug("syncTask # requested: " + count
367
							+ ", start: " + start + ", total: " + total
368
							+ ", count: " + objsToSync.getCount());
369
					tmpIds = sync(objsToSync);
370
					syncedIds.addAll(tmpIds);
371 8592 slaughter
372 8590 slaughter
					// Set start for the next db retrieval, loop interation
373
					start += objsToSync.getCount();
374 8592 slaughter
					if (start >= total)
375
						break;
376 8590 slaughter
					objsToSync = IdentifierManager
377
							.getInstance()
378
							.querySystemMetadata(startTime, endTime,
379
									objectFormatId, replicaStatus, start, count);
380
				} catch (Exception e) {
381
					logMetacat.error("Error syncing ids");
382
					break;
383
				}
384
			}
385
			logMetacat
386
					.debug("syncTask thread completed. Number of guids synced: "
387
							+ syncedIds.size());
388
		}
389 8452 slaughter
	}
390
391
	/**
392
	 * Compare two d1 system metadata access policies for equivalence.
393
	 *
394
	 * @param ap1
395
	 *            - first access policy in the comparison
396
	 * @param ap2
397
	 *            - second access policy in the comparison
398 8590 slaughter
	 * @return boolean - true if access policies are equivalent
399 8452 slaughter
	 */
400
	private boolean isEqual(AccessPolicy ap1, AccessPolicy ap2) {
401
402 8491 slaughter
		// Access Policy -> Access Rule -> (Subject, Permission)
403
		// i.e. Subject="slaughter", Permission="read,write,changePermission"
404 8452 slaughter
		// Get the list of access rules for each access policy
405
		List<org.dataone.service.types.v1.AccessRule> allowList1 = ap1
406
				.getAllowList();
407
		List<org.dataone.service.types.v1.AccessRule> allowList2 = ap2
408
				.getAllowList();
409
410
		HashMap<Subject, Set<Permission>> userPerms1 = new HashMap<Subject, Set<Permission>>();
411
		HashMap<Subject, Set<Permission>> userPerms2 = new HashMap<Subject, Set<Permission>>();
412
413
		// Load the permissions from the access rules into a hash of sets, i.e.,
414
		// so that we end up with this:
415 8491 slaughter
		// hash key: set of permissions, i.e.
416 8452 slaughter
		// ----------------------------
417
		// user1: read, write
418
		// user2: read
419
		// user3: read, write, change permissions
420
		// With the permissions in this structure, they can be easily compared
421
		Set<Permission> perms = null;
422
		// Process first access policy
423 8491 slaughter
		// Loop through access rules of this allowList
424 8452 slaughter
		for (org.dataone.service.types.v1.AccessRule accessRule : allowList1) {
425
			for (Subject s : accessRule.getSubjectList()) {
426
				if (userPerms1.containsKey(s)) {
427
					perms = userPerms1.get(s);
428
				} else {
429
					perms = new HashSet<Permission>();
430
				}
431
				for (Permission p : accessRule.getPermissionList()) {
432
					perms.add(p);
433
				}
434 8491 slaughter
				userPerms1.put(s, perms);
435 8452 slaughter
			}
436
		}
437
438
		// Process second access policy
439
		for (org.dataone.service.types.v1.AccessRule accessRule : allowList2) {
440
			for (Subject s : accessRule.getSubjectList()) {
441
				if (userPerms2.containsKey(s)) {
442
					perms = userPerms2.get(s);
443
				} else {
444
					perms = new HashSet<Permission>();
445
				}
446
				for (Permission p : accessRule.getPermissionList()) {
447
					perms.add(p);
448
				}
449 8491 slaughter
				userPerms2.put(s, perms);
450 8452 slaughter
			}
451
		}
452
453 8561 slaughter
		// Check if the number of access rules is the same for mn and cn. If not
454 8590 slaughter
		// then consider them not equal, without performing diff of each access
455
		// rule.
456 8561 slaughter
		if (userPerms1.entrySet().size() != userPerms2.entrySet().size())
457
			return false;
458 8590 slaughter
459
		// Now perform the comparison of each access rule of access policy 1 to
460
		// ap 2.
461
		// This test assumes that the mn perms are more complete than the cn
462
		// perms.
463 8491 slaughter
		logMetacat.debug("Performing comparison of access policies");
464 8452 slaughter
		for (Map.Entry<Subject, Set<Permission>> entry : userPerms1.entrySet()) {
465
			// User name
466
			Subject s1 = entry.getKey();
467
			// Perms that the user holds
468 8491 slaughter
			Set<Permission> p1 = entry.getValue();
469 8590 slaughter
			logMetacat
470
					.debug("Checking access policy of user: " + s1.getValue());
471 8452 slaughter
472 8491 slaughter
			// Does this user exist in both access policies?
473 8452 slaughter
			if (userPerms2.containsKey(s1)) {
474 8491 slaughter
				if (!p1.equals(userPerms2.get(s1))) {
475
					logMetacat.debug("User access policies not equal");
476 8452 slaughter
					return false;
477
				}
478
			} else {
479 8491 slaughter
				logMetacat.debug("User access policy not found on CN");
480 8452 slaughter
				return false;
481
			}
482
		}
483
484
		// All comparisons have been passed, so the two access policies are
485
		// equivalent
486 8491 slaughter
		logMetacat.debug("Access policies are the same");
487 8452 slaughter
		return true;
488
	}
489
490 8585 leinfelder
	/**
491 8590 slaughter
	 * Run pid synch script on the given pids Each argument is an individual pid
492
	 * because pids cannot contain whitespace.
493
	 *
494 8585 leinfelder
	 * @param args
495
	 * @throws Exception
496
	 */
497 8452 slaughter
	public static void main(String[] args) throws Exception {
498
499
		// set up the properties based on the test/deployed configuration of the
500
		// workspace
501 8590 slaughter
		SortedProperties testProperties = new SortedProperties(
502
				"test/test.properties");
503 8452 slaughter
		testProperties.load();
504 8590 slaughter
		String metacatContextDir = testProperties
505
				.getProperty("metacat.contextDir");
506 8452 slaughter
		PropertyService.getInstance(metacatContextDir + "/WEB-INF");
507 8590 slaughter
508 8585 leinfelder
		ArrayList<String> guids = null;
509
		SyncAccessPolicy syncAP = new SyncAccessPolicy();
510 8452 slaughter
511
		if (args.length > 0) {
512
			try {
513 8585 leinfelder
				guids = new ArrayList<String>(Arrays.asList(args));
514 8590 slaughter
				logMetacat.warn("Trying to syncing access policy for "
515
						+ args.length + " pids");
516 8585 leinfelder
				List<Identifier> synchedPids = syncAP.sync(guids);
517 8590 slaughter
				logMetacat.warn("Sunk access policies for "
518
						+ synchedPids.size() + " pids");
519 8452 slaughter
			} catch (Exception e) {
520 8590 slaughter
				logMetacat.error(
521
						"Error syncing pids, message: " + e.getMessage(), e);
522 8452 slaughter
				System.exit(1);
523
			}
524
		}
525
	}
526
}