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: walker $'
7
 *     '$Date: 2013-10-11 16:35:37 -0700 (Fri, 11 Oct 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.metacat.util.SystemUtil;
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 = lookupResourceType(sysMeta);
181
				
182
				// format
183
				String format = sysMeta.getFormatId().getValue();
184
				
185
				//size
186
				String size = sysMeta.getSize().toString();
187
				
188
				// target (URL)
189
				String target = node.getBaseURL() + "/v1/object/" + identifier;
190
				String uriTemplate = null;
191
				String uriTemplateKey = "guid.ezid.uritemplate.data";
192
				ObjectFormat objectFormat = null;
193
				try {
194
					objectFormat = D1Client.getCN().getFormat(sysMeta.getFormatId());
195
				} catch (NotFound e1) {
196
					logMetacat.warn("Could not check format type for: " + sysMeta.getFormatId());
197
				}
198
				if (objectFormat != null && objectFormat.getFormatType().equals("METADATA")) {
199
					uriTemplateKey = "guid.ezid.uritemplate.metadata";
200
				}
201
				try {
202
					uriTemplate = PropertyService.getProperty(uriTemplateKey);
203
					target =  SystemUtil.getSecureServerURL() + uriTemplate.replaceAll("<IDENTIFIER>", identifier);
204
				} catch (PropertyNotFoundException e) {
205
					logMetacat.warn("No target URI template found in the configuration for: " + uriTemplateKey);
206
				}
207
				
208
				// status and export fields for public/protected data
209
				String status = InternalProfileValues.UNAVAILABLE.toString();
210
				String export = InternalProfileValues.NO.toString();
211
				Subject publicSubject = new Subject();
212
				publicSubject.setValue(Constants.SUBJECT_PUBLIC);
213
				if (AuthUtils.isAuthorized(Arrays.asList(new Subject[] {publicSubject}), Permission.READ, sysMeta)) {
214
					status = InternalProfileValues.PUBLIC.toString();
215
					export = InternalProfileValues.YES.toString();
216
				}
217
				
218
				// set the datacite metadata fields
219
				metadata.put(DataCiteProfile.TITLE.toString(), title);
220
				metadata.put(DataCiteProfile.CREATOR.toString(), creator);
221
				metadata.put(DataCiteProfile.PUBLISHER.toString(), publisher);
222
				metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), year);
223
				metadata.put(DataCiteProfile.RESOURCE_TYPE.toString(), resourceType);
224
				metadata.put(DataCiteProfile.FORMAT.toString(), format);
225
				metadata.put(DataCiteProfile.SIZE.toString(), size);
226
				metadata.put(InternalProfile.TARGET.toString(), target);
227
				metadata.put(InternalProfile.STATUS.toString(), status);
228
				metadata.put(InternalProfile.EXPORT.toString(), export);
229
	
230
				// set using the API
231
				if (create) {
232
					ezid.createIdentifier(identifier, metadata);
233
				} else {
234
					ezid.setMetadata(identifier, metadata);
235
				}
236
				
237
				ezid.logout();
238
			}
239
			
240
		}
241
		
242
		return true;
243
	}
244

    
245
	/**
246
	 * Generate a DOI using the EZID service as configured
247
	 * @return
248
	 * @throws EZIDException 
249
	 * @throws InvalidRequest 
250
	 */
251
	public Identifier generateDOI() throws EZIDException, InvalidRequest {
252

    
253
		Identifier identifier = new Identifier();
254

    
255
		// look up configuration values
256
		String shoulder = null;
257
		String ezidUsername = null;
258
		String ezidPassword = null;
259
		boolean doiEnabled = false;
260
		try {
261
            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
262
			shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
263
			ezidUsername = PropertyService.getProperty("guid.ezid.username");
264
			ezidPassword = PropertyService.getProperty("guid.ezid.password");
265
		} catch (PropertyNotFoundException e1) {
266
			throw new InvalidRequest("2193", "DOI shoulder is not configured at this node.");
267
		}
268
		
269
		// only continue if we have the feature turned on
270
		if (!doiEnabled) {
271
			throw new InvalidRequest("2193", "DOI scheme is not enabled at this node.");
272
		}
273
		
274
		// add only the minimal metadata required for this DOI
275
		HashMap<String, String> metadata = new HashMap<String, String>();
276
		metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString());
277
		metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString());
278
		metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString());
279
		metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString());
280
		metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString());
281
		metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString());
282

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

    
339
	/**
340
	 * Lookup the citable name for the given Subject
341
	 * Calls the configured CN to determine this information.
342
	 * If the person is not registered with the CN identity service, 
343
	 * a NotFound exception will be raised as expected from the service.
344
	 * @param subject
345
	 * @return fullName if found
346
	 * @throws ServiceFailure
347
	 * @throws NotAuthorized
348
	 * @throws NotImplemented
349
	 * @throws NotFound
350
	 * @throws InvalidToken
351
	 */
352
	private String lookupCreator(Subject subject) throws ServiceFailure, NotAuthorized, NotImplemented, NotFound, InvalidToken {
353
		// default to given DN
354
		String fullName = subject.getValue();
355
		
356
		SubjectInfo subjectInfo = D1Client.getCN().getSubjectInfo(subject);
357
		if (subjectInfo != null && subjectInfo.getPersonList() != null) {
358
			for (Person p: subjectInfo.getPersonList()) {
359
				if (p.getSubject().equals(subject)) {
360
					fullName = p.getFamilyName();
361
					if (p.getGivenNameList() != null && p.getGivenNameList().size() > 0) {
362
						fullName = fullName + ", " + p.getGivenName(0);
363
					}
364
					break;
365
				}
366
			}
367
		}
368
		
369
		return fullName;
370
		
371
	}
372
	
373
}
(3-3/6)