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: 2014-07-23 16:19:48 -0700 (Wed, 23 Jul 2014) $'
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.Calendar;
29
import java.util.Date;
30
import java.util.HashMap;
31

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

    
57
import edu.ucsb.nceas.ezid.EZIDClient;
58
import edu.ucsb.nceas.ezid.EZIDException;
59
import edu.ucsb.nceas.ezid.profile.DataCiteProfile;
60
import edu.ucsb.nceas.ezid.profile.DataCiteProfileResourceTypeValues;
61
import edu.ucsb.nceas.ezid.profile.ErcMissingValueCode;
62
import edu.ucsb.nceas.ezid.profile.InternalProfile;
63
import edu.ucsb.nceas.ezid.profile.InternalProfileValues;
64
import edu.ucsb.nceas.metacat.properties.PropertyService;
65
import edu.ucsb.nceas.metacat.util.SystemUtil;
66
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
67

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

    
78
	private Logger logMetacat = Logger.getLogger(DOIService.class);
79

    
80
	private boolean doiEnabled = false;
81
	
82
	private String shoulder = null;
83
	
84
	private String ezidUsername = null;
85
	
86
	private String ezidPassword = null;
87
	
88
	private EZIDClient ezid = null;
89
	
90
	private Date lastLogin = null;
91
	
92
	private long loginPeriod = 1 * 24 * 60 * 60 * 1000;
93

    
94
	private static DOIService instance = null;
95
	
96
	public static DOIService getInstance() {
97
		if (instance == null) {
98
			instance = new DOIService();
99
		}
100
		return instance;
101
	}
102
	
103
	/**
104
	 * Constructor, private for singleton access
105
	 */
106
	private DOIService() {
107
		
108
		// for DOIs
109
		String ezidServiceBaseUrl = null;
110
		
111
		try {
112
            doiEnabled = new Boolean(PropertyService.getProperty("guid.ezid.enabled")).booleanValue();
113
			shoulder = PropertyService.getProperty("guid.ezid.doishoulder.1");
114
			ezidServiceBaseUrl = PropertyService.getProperty("guid.ezid.baseurl");
115
			ezidUsername = PropertyService.getProperty("guid.ezid.username");
116
			ezidPassword = PropertyService.getProperty("guid.ezid.password");
117
		} catch (PropertyNotFoundException e) {
118
			logMetacat.warn("DOI support is not configured at this node.", e);
119
			return;
120
		}
121
		
122
		ezid = new EZIDClient(ezidServiceBaseUrl);
123

    
124
		
125
		
126
	}
127
	
128
	/**
129
	 * Make sure we have a current login before making any calls
130
	 * @throws EZIDException
131
	 */
132
	private void refreshLogin() throws EZIDException {
133
		Date now = Calendar.getInstance().getTime();
134
		if (lastLogin == null || now.getTime() - lastLogin.getTime() > loginPeriod) {
135
			ezid.login(ezidUsername, ezidPassword);
136
			lastLogin = now;	
137
		}
138
	}
139
	
140
	/**
141
	 * submits DOI metadata information about the object to EZID
142
	 * @param sysMeta
143
	 * @return
144
	 * @throws EZIDException 
145
	 * @throws ServiceFailure 
146
	 * @throws NotImplemented 
147
	 * @throws InterruptedException 
148
	 */
149
	public boolean registerDOI(SystemMetadata sysMeta) throws EZIDException, NotImplemented, ServiceFailure, InterruptedException {
150
				
151
		// only continue if we have the feature turned on
152
		if (doiEnabled) {
153
			
154
			String identifier = sysMeta.getIdentifier().getValue();
155
			
156
			// only continue if this DOI is in our configured shoulder
157
			if (identifier.startsWith(shoulder)) {
158
				
159
				// enter metadata about this identifier
160
				HashMap<String, String> metadata = new HashMap<String, String>();
161
				
162
				// title 
163
				String title = ErcMissingValueCode.UNKNOWN.toString();
164
				try {
165
					title = lookupTitle(sysMeta);
166
				} catch (Exception e) {
167
					e.printStackTrace();
168
					// ignore
169
				}
170
				
171
				// creator
172
				String creator = sysMeta.getRightsHolder().getValue();
173
				try {
174
					creator = lookupCreator(sysMeta.getRightsHolder());
175
				} catch (Exception e) {
176
					// ignore and use default
177
				}
178
				
179
				// publisher
180
				String publisher = ErcMissingValueCode.UNKNOWN.toString();
181
				Node node = MNodeService.getInstance(null).getCapabilities();
182
				publisher = node.getName();
183
				
184
				// publication year
185
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
186
				String year = sdf.format(sysMeta.getDateUploaded());
187
				
188
				// type
189
				String resourceType = lookupResourceType(sysMeta);
190
				
191
				// format
192
				String format = sysMeta.getFormatId().getValue();
193
				
194
				//size
195
				String size = sysMeta.getSize().toString();
196
				
197
				// target (URL)
198
				String target = node.getBaseURL() + "/v1/object/" + identifier;
199
				String uriTemplate = null;
200
				String uriTemplateKey = "guid.ezid.uritemplate.data";
201
				ObjectFormat objectFormat = null;
202
				try {
203
					objectFormat = D1Client.getCN().getFormat(sysMeta.getFormatId());
204
				} catch (BaseException e1) {
205
					logMetacat.warn("Could not check format type for: " + sysMeta.getFormatId());
206
				}
207
				if (objectFormat != null && objectFormat.getFormatType().equals("METADATA")) {
208
					uriTemplateKey = "guid.ezid.uritemplate.metadata";
209
				}
210
				try {
211
					uriTemplate = PropertyService.getProperty(uriTemplateKey);
212
					target =  SystemUtil.getSecureServerURL() + uriTemplate.replaceAll("<IDENTIFIER>", identifier);
213
				} catch (PropertyNotFoundException e) {
214
					logMetacat.warn("No target URI template found in the configuration for: " + uriTemplateKey);
215
				}
216
				
217
				// status and export fields for public/protected data
218
				String status = InternalProfileValues.UNAVAILABLE.toString();
219
				String export = InternalProfileValues.NO.toString();
220
				Subject publicSubject = new Subject();
221
				publicSubject.setValue(Constants.SUBJECT_PUBLIC);
222
				if (AuthUtils.isAuthorized(Arrays.asList(new Subject[] {publicSubject}), Permission.READ, sysMeta)) {
223
					status = InternalProfileValues.PUBLIC.toString();
224
					export = InternalProfileValues.YES.toString();
225
				}
226
				
227
				// set the datacite metadata fields
228
				metadata.put(DataCiteProfile.TITLE.toString(), title);
229
				metadata.put(DataCiteProfile.CREATOR.toString(), creator);
230
				metadata.put(DataCiteProfile.PUBLISHER.toString(), publisher);
231
				metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), year);
232
				metadata.put(DataCiteProfile.RESOURCE_TYPE.toString(), resourceType);
233
				metadata.put(DataCiteProfile.FORMAT.toString(), format);
234
				metadata.put(DataCiteProfile.SIZE.toString(), size);
235
				metadata.put(InternalProfile.TARGET.toString(), target);
236
				metadata.put(InternalProfile.STATUS.toString(), status);
237
				metadata.put(InternalProfile.EXPORT.toString(), export);
238
	
239
				// make sure we have a current login
240
				this.refreshLogin();
241
				
242
				// set using the API
243
				ezid.createOrUpdate(identifier, metadata);
244
				
245
			}
246
			
247
		}
248
		
249
		return true;
250
	}
251

    
252
	/**
253
	 * Generate a DOI using the EZID service as configured
254
	 * @return
255
	 * @throws EZIDException 
256
	 * @throws InvalidRequest 
257
	 */
258
	public Identifier generateDOI() throws EZIDException, InvalidRequest {
259

    
260
		
261
		// only continue if we have the feature turned on
262
		if (!doiEnabled) {
263
			throw new InvalidRequest("2193", "DOI scheme is not enabled at this node.");
264
		}
265
		
266
		// add only the minimal metadata required for this DOI
267
		HashMap<String, String> metadata = new HashMap<String, String>();
268
		metadata.put(DataCiteProfile.TITLE.toString(), ErcMissingValueCode.UNKNOWN.toString());
269
		metadata.put(DataCiteProfile.CREATOR.toString(), ErcMissingValueCode.UNKNOWN.toString());
270
		metadata.put(DataCiteProfile.PUBLISHER.toString(), ErcMissingValueCode.UNKNOWN.toString());
271
		metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), ErcMissingValueCode.UNKNOWN.toString());
272
		metadata.put(InternalProfile.STATUS.toString(), InternalProfileValues.RESERVED.toString());
273
		metadata.put(InternalProfile.EXPORT.toString(), InternalProfileValues.NO.toString());
274

    
275
		// make sure we have a current login
276
		this.refreshLogin();
277

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

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