Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: leinfelder $'
7
 *     '$Date: 2013-03-13 17:11:53 -0700 (Wed, 13 Mar 2013) $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
package edu.ucsb.nceas.metacat.dataone;
24

    
25
import java.io.InputStream;
26
import java.text.SimpleDateFormat;
27
import java.util.Arrays;
28
import java.util.Collections;
29
import java.util.HashMap;
30

    
31
import org.apache.log4j.Logger;
32
import org.apache.wicket.protocol.http.MockHttpServletRequest;
33
import org.dataone.client.D1Client;
34
import org.dataone.service.exceptions.InsufficientResources;
35
import org.dataone.service.exceptions.InvalidRequest;
36
import org.dataone.service.exceptions.InvalidToken;
37
import org.dataone.service.exceptions.NotAuthorized;
38
import org.dataone.service.exceptions.NotFound;
39
import org.dataone.service.exceptions.NotImplemented;
40
import org.dataone.service.exceptions.ServiceFailure;
41
import org.dataone.service.types.v1.Identifier;
42
import org.dataone.service.types.v1.Node;
43
import org.dataone.service.types.v1.Permission;
44
import org.dataone.service.types.v1.Person;
45
import org.dataone.service.types.v1.Session;
46
import org.dataone.service.types.v1.Subject;
47
import org.dataone.service.types.v1.SubjectInfo;
48
import org.dataone.service.types.v1.SystemMetadata;
49
import org.dataone.service.types.v1.util.AuthUtils;
50
import org.dataone.service.util.Constants;
51
import org.ecoinformatics.datamanager.parser.DataPackage;
52
import org.ecoinformatics.datamanager.parser.generic.DataPackageParserInterface;
53
import org.ecoinformatics.datamanager.parser.generic.Eml200DataPackageParser;
54

    
55
import edu.ucsb.nceas.ezid.EZIDException;
56
import edu.ucsb.nceas.ezid.EZIDService;
57
import edu.ucsb.nceas.ezid.profile.DataCiteProfile;
58
import edu.ucsb.nceas.ezid.profile.DataCiteProfileResourceTypeValues;
59
import edu.ucsb.nceas.ezid.profile.ErcMissingValueCode;
60
import edu.ucsb.nceas.ezid.profile.InternalProfile;
61
import edu.ucsb.nceas.ezid.profile.InternalProfileValues;
62
import edu.ucsb.nceas.metacat.properties.PropertyService;
63
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
64

    
65
/**
66
 * 
67
 * Singleton for interacting with the EZID DOI library.
68
 * Allows DOI minting/initial registration, creating and updating 
69
 * existing DOI registrations.
70
 * 
71
 * @author leinfelder
72
 */
73
public class DOIService {
74

    
75
	private Logger logMetacat = Logger.getLogger(DOIService.class);
76

    
77
	private static DOIService instance = null;
78
	
79
	public static DOIService getInstance() {
80
		if (instance == null) {
81
			instance = new DOIService();
82
		}
83
		return instance;
84
	}
85
	
86
	/**
87
	 * Constructor, private for singleton access
88
	 */
89
	private DOIService() {
90
		
91
	}
92
	
93
	/**
94
	 * submits DOI metadata information about the object to EZID
95
	 * @param sysMeta
96
	 * @return
97
	 * @throws EZIDException 
98
	 * @throws ServiceFailure 
99
	 * @throws NotImplemented 
100
	 */
101
	public boolean registerDOI(SystemMetadata sysMeta) throws EZIDException, NotImplemented, ServiceFailure {
102
		
103
		// for DOIs
104
		String ezidUsername = null;
105
		String ezidPassword = null;
106
		String shoulder = null;
107
		boolean doiEnabled = false;
108
		try {
109
            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
110
			shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
111
			ezidUsername = PropertyService.getProperty("guid.ezid.username");
112
			ezidPassword = PropertyService.getProperty("guid.ezid.password");
113
		} catch (PropertyNotFoundException e) {
114
			logMetacat.warn("DOI support is not configured at this node.", e);
115
			return false;
116
		}
117
		
118
		// only continue if we have the feature turned on
119
		if (doiEnabled) {
120
			
121
			String identifier = sysMeta.getIdentifier().getValue();
122
			
123
			// only continue if this DOI is in our configured shoulder
124
			if (identifier.startsWith(shoulder)) {
125
				
126
				// enter metadata about this identifier
127
				HashMap<String, String> metadata = null;
128
				
129
				// login to EZID service
130
				String ezidServiceBaseUrl = null;
131
				try {
132
					ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
133
				} catch (PropertyNotFoundException e) {
134
					logMetacat.warn("Using default EZID baseUrl");
135
				}
136
				EZIDService ezid = new EZIDService(ezidServiceBaseUrl);
137
				ezid.login(ezidUsername, ezidPassword);
138
				
139
				// check for existing metadata
140
				boolean create = false;
141
				try {
142
					metadata = ezid.getMetadata(identifier);
143
				} catch (EZIDException e) {
144
					// expected much of the time
145
					logMetacat.warn("No metadata found for given identifier: " + e.getMessage());
146
				}
147
				if (metadata == null) {
148
					create = true;
149
				}
150
				// confuses the API if we send the original metadata that it gave us, so start from scratch
151
				metadata = new HashMap<String, String>();
152
				
153
				// title 
154
				String title = ErcMissingValueCode.UNKNOWN.toString();
155
				try {
156
					title = lookupTitle(sysMeta);
157
				} catch (Exception e) {
158
					e.printStackTrace();
159
					// ignore
160
				}
161
				
162
				// creator
163
				String creator = sysMeta.getRightsHolder().getValue();
164
				try {
165
					creator = lookupCreator(sysMeta.getRightsHolder());
166
				} catch (Exception e) {
167
					// ignore and use default
168
				}
169
				
170
				// publisher
171
				String publisher = ErcMissingValueCode.UNKNOWN.toString();
172
				Node node = MNodeService.getInstance(null).getCapabilities();
173
				publisher = node.getName();
174
				
175
				// publication year
176
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
177
				String year = sdf .format(sysMeta.getDateUploaded());
178
				
179
				// type
180
				String resourceType = DataCiteProfileResourceTypeValues.DATASET.toString() + "/" + sysMeta.getFormatId().getValue();
181
				
182
				// target (URL)
183
				String target = node.getBaseURL() + "/v1/object/" + identifier;
184
				
185
				// status and export fields for public/protected data
186
				String status = InternalProfileValues.UNAVAILABLE.toString();
187
				String export = InternalProfileValues.NO.toString();
188
				Subject publicSubject = new Subject();
189
				publicSubject.setValue(Constants.SUBJECT_PUBLIC);
190
				if (AuthUtils.isAuthorized(Arrays.asList(new Subject[] {publicSubject}), Permission.READ, sysMeta)) {
191
					status = InternalProfileValues.PUBLIC.toString();
192
					export = InternalProfileValues.YES.toString();
193
				}
194
				
195
				// set the metadata fields
196
				metadata.put(DataCiteProfile.TITLE.toString(), title);
197
				metadata.put(DataCiteProfile.CREATOR.toString(), creator);
198
				metadata.put(DataCiteProfile.PUBLISHER.toString(), publisher);
199
				metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), year);
200
				metadata.put(DataCiteProfile.RESOURCE_TYPE.toString(), resourceType);
201
				metadata.put(InternalProfile.TARGET.toString(), target);
202
				metadata.put(InternalProfile.STATUS.toString(), status);
203
				metadata.put(InternalProfile.EXPORT.toString(), export);
204
	
205
				// set using the API
206
				if (create) {
207
					ezid.createIdentifier(identifier, metadata);
208
				} else {
209
					ezid.setMetadata(identifier, metadata);
210
				}
211
				
212
				ezid.logout();
213
			}
214
			
215
		}
216
		
217
		return true;
218
	}
219

    
220
	/**
221
	 * Generate a DOI using the EZID service as configured
222
	 * @return
223
	 * @throws EZIDException 
224
	 * @throws InvalidRequest 
225
	 */
226
	public Identifier generateDOI() throws EZIDException, InvalidRequest {
227

    
228
		Identifier identifier = new Identifier();
229

    
230
		// look up configuration values
231
		String shoulder = null;
232
		String ezidUsername = null;
233
		String ezidPassword = null;
234
		boolean doiEnabled = false;
235
		try {
236
            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
237
			shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
238
			ezidUsername = PropertyService.getProperty("guid.ezid.username");
239
			ezidPassword = PropertyService.getProperty("guid.ezid.password");
240
		} catch (PropertyNotFoundException e1) {
241
			throw new InvalidRequest("2193", "DOI shoulder is not configured at this node.");
242
		}
243
		
244
		// only continue if we have the feature turned on
245
		if (!doiEnabled) {
246
			throw new InvalidRequest("2193", "DOI scheme is not enabled at this node.");
247
		}
248
		
249
		// add only the minimal metadata required for this DOI
250
		HashMap<String, String> metadata = new HashMap<String, String>();
251
		metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString());
252
		metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString());
253
		metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString());
254
		metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString());
255
		metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString());
256
		metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString());
257

    
258
		// call the EZID service
259
		String ezidServiceBaseUrl = null;
260
		try {
261
			ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
262
		} catch (PropertyNotFoundException e) {
263
			logMetacat.warn("Using default EZID baseUrl");
264
		}
265
		EZIDService ezid = new EZIDService(ezidServiceBaseUrl);
266
		ezid.login(ezidUsername, ezidPassword);
267
		String doi = ezid.mintIdentifier(shoulder, metadata);
268
		identifier.setValue(doi);
269
		ezid.logout();
270
		
271
		return identifier;
272
	}
273
	
274
	/**
275
	 * Locates an appropriate title for the object identified by the given SystemMetadata.
276
	 * Different types of objects will be handled differently for titles:
277
	 * 1. EML formats - parsed by the Datamanager library to find dataset title 
278
	 * 2. Data objects - TODO: use title from EML file that describes that data
279
	 * 3. ORE objects - TODO: use title from EML file contained in that package
280
	 * @param sysMeta
281
	 * @return appropriate title if known, or the missing value code
282
	 * @throws Exception
283
	 */
284
	private String lookupTitle(SystemMetadata sysMeta) throws Exception {
285
		String title = ErcMissingValueCode.UNKNOWN.toString();
286
		if (sysMeta.getFormatId().getValue().startsWith("eml://")) {
287
			DataPackageParserInterface parser = new Eml200DataPackageParser();
288
			// for using the MN API as the MN itself
289
			MockHttpServletRequest request = new MockHttpServletRequest(null, null, null);
290
			Session session = new Session();
291
	        Subject subject = MNodeService.getInstance(request).getCapabilities().getSubject(0);
292
	        session.setSubject(subject);
293
			InputStream emlStream = MNodeService.getInstance(request).get(session, sysMeta.getIdentifier());
294
			parser.parse(emlStream);
295
			DataPackage dataPackage = parser.getDataPackage();
296
			title = dataPackage.getTitle();
297
		}
298
		return title;
299
	}
300

    
301
	/**
302
	 * Lookup the citable name for the given Subject
303
	 * Calls the configured CN to determine this information.
304
	 * If the person is not registered with the CN identity service, 
305
	 * a NotFound exception will be raised as expected from the service.
306
	 * @param subject
307
	 * @return fullName if found
308
	 * @throws ServiceFailure
309
	 * @throws NotAuthorized
310
	 * @throws NotImplemented
311
	 * @throws NotFound
312
	 * @throws InvalidToken
313
	 */
314
	private String lookupCreator(Subject subject) throws ServiceFailure, NotAuthorized, NotImplemented, NotFound, InvalidToken {
315
		// default to given DN
316
		String fullName = subject.getValue();
317
		
318
		SubjectInfo subjectInfo = D1Client.getCN().getSubjectInfo(subject);
319
		if (subjectInfo != null && subjectInfo.getPersonList() != null) {
320
			for (Person p: subjectInfo.getPersonList()) {
321
				if (p.getSubject().equals(subject)) {
322
					fullName = p.getFamilyName();
323
					if (p.getGivenNameList() != null && p.getGivenNameList().size() > 0) {
324
						fullName = fullName + ", " + p.getGivenName(0);
325
					}
326
					break;
327
				}
328
			}
329
		}
330
		
331
		return fullName;
332
		
333
	}
334
	
335
}
(3-3/6)