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-04-24 19:34:36 -0700 (Wed, 24 Apr 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.HashMap;
29

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

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

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

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

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

    
227
	/**
228
	 * Generate a DOI using the EZID service as configured
229
	 * @return
230
	 * @throws EZIDException 
231
	 * @throws InvalidRequest 
232
	 */
233
	public Identifier generateDOI() throws EZIDException, InvalidRequest {
234

    
235
		Identifier identifier = new Identifier();
236

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

    
265
		// call the EZID service
266
		String ezidServiceBaseUrl = null;
267
		try {
268
			ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
269
		} catch (PropertyNotFoundException e) {
270
			logMetacat.warn("Using default EZID baseUrl");
271
		}
272
		EZIDService ezid = new EZIDService(ezidServiceBaseUrl);
273
		ezid.login(ezidUsername, ezidPassword);
274
		String doi = ezid.mintIdentifier(shoulder, metadata);
275
		identifier.setValue(doi);
276
		ezid.logout();
277
		
278
		return identifier;
279
	}
280
	
281
	/**
282
	 * Locates an appropriate title for the object identified by the given SystemMetadata.
283
	 * Different types of objects will be handled differently for titles:
284
	 * 1. EML formats - parsed by the Datamanager library to find dataset title 
285
	 * 2. Data objects - TODO: use title from EML file that describes that data
286
	 * 3. ORE objects - TODO: use title from EML file contained in that package
287
	 * @param sysMeta
288
	 * @return appropriate title if known, or the missing value code
289
	 * @throws Exception
290
	 */
291
	private String lookupTitle(SystemMetadata sysMeta) throws Exception {
292
		String title = ErcMissingValueCode.UNKNOWN.toString();
293
		if (sysMeta.getFormatId().getValue().startsWith("eml://")) {
294
			DataPackageParserInterface parser = new Eml200DataPackageParser();
295
			// for using the MN API as the MN itself
296
			MockHttpServletRequest request = new MockHttpServletRequest(null, null, null);
297
			Session session = new Session();
298
	        Subject subject = MNodeService.getInstance(request).getCapabilities().getSubject(0);
299
	        session.setSubject(subject);
300
			InputStream emlStream = MNodeService.getInstance(request).get(session, sysMeta.getIdentifier());
301
			parser.parse(emlStream);
302
			DataPackage dataPackage = parser.getDataPackage();
303
			title = dataPackage.getTitle();
304
		}
305
		return title;
306
	}
307
	
308
	private String lookupResourceType(SystemMetadata sysMeta) {
309
		String resourceType = DataCiteProfileResourceTypeValues.DATASET.toString();
310
		try {
311
			ObjectFormat objectFormat = D1Client.getCN().getFormat(sysMeta.getFormatId());
312
			resourceType += "/" + objectFormat.getFormatType().toLowerCase();
313
		} catch (Exception e) {
314
			// ignore
315
			logMetacat.warn("Could not lookup resource type for formatId" + e.getMessage());
316
		}
317
		
318
		return resourceType;
319
	}
320

    
321
	/**
322
	 * Lookup the citable name for the given Subject
323
	 * Calls the configured CN to determine this information.
324
	 * If the person is not registered with the CN identity service, 
325
	 * a NotFound exception will be raised as expected from the service.
326
	 * @param subject
327
	 * @return fullName if found
328
	 * @throws ServiceFailure
329
	 * @throws NotAuthorized
330
	 * @throws NotImplemented
331
	 * @throws NotFound
332
	 * @throws InvalidToken
333
	 */
334
	private String lookupCreator(Subject subject) throws ServiceFailure, NotAuthorized, NotImplemented, NotFound, InvalidToken {
335
		// default to given DN
336
		String fullName = subject.getValue();
337
		
338
		SubjectInfo subjectInfo = D1Client.getCN().getSubjectInfo(subject);
339
		if (subjectInfo != null && subjectInfo.getPersonList() != null) {
340
			for (Person p: subjectInfo.getPersonList()) {
341
				if (p.getSubject().equals(subject)) {
342
					fullName = p.getFamilyName();
343
					if (p.getGivenNameList() != null && p.getGivenNameList().size() > 0) {
344
						fullName = fullName + ", " + p.getGivenName(0);
345
					}
346
					break;
347
				}
348
			}
349
		}
350
		
351
		return fullName;
352
		
353
	}
354
	
355
}
(3-3/6)