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