Project

General

Profile

1 7512 leinfelder
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author$'
7
 *     '$Date$'
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 7516 leinfelder
import java.io.InputStream;
26 7512 leinfelder
import java.text.SimpleDateFormat;
27 7516 leinfelder
import java.util.Arrays;
28 8794 leinfelder
import java.util.Calendar;
29
import java.util.Date;
30 7512 leinfelder
import java.util.HashMap;
31
32
import org.apache.log4j.Logger;
33 7622 leinfelder
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
34 8810 leinfelder
import org.dataone.client.v2.itk.D1Client;
35
import org.dataone.service.exceptions.BaseException;
36 7512 leinfelder
import org.dataone.service.exceptions.InvalidRequest;
37 7516 leinfelder
import org.dataone.service.exceptions.InvalidToken;
38
import org.dataone.service.exceptions.NotAuthorized;
39
import org.dataone.service.exceptions.NotFound;
40 7512 leinfelder
import org.dataone.service.exceptions.NotImplemented;
41
import org.dataone.service.exceptions.ServiceFailure;
42
import org.dataone.service.types.v1.Identifier;
43 8810 leinfelder
import org.dataone.service.types.v2.Node;
44
import org.dataone.service.types.v2.ObjectFormat;
45 7516 leinfelder
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 8810 leinfelder
import org.dataone.service.types.v2.SystemMetadata;
51 7516 leinfelder
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 7512 leinfelder
57 8795 leinfelder
import edu.ucsb.nceas.ezid.EZIDClient;
58 7512 leinfelder
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 8202 leinfelder
import edu.ucsb.nceas.metacat.util.SystemUtil;
66 7512 leinfelder
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 8794 leinfelder
	private boolean doiEnabled = false;
81
82
	private String shoulder = null;
83
84
	private String ezidUsername = null;
85
86
	private String ezidPassword = null;
87
88 8795 leinfelder
	private EZIDClient ezid = null;
89 8794 leinfelder
90
	private Date lastLogin = null;
91
92
	private long loginPeriod = 1 * 24 * 60 * 60 * 1000;
93
94 7512 leinfelder
	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 8794 leinfelder
		// 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 8795 leinfelder
		ezid = new EZIDClient(ezidServiceBaseUrl);
123 8794 leinfelder
124
125
126 7512 leinfelder
	}
127
128
	/**
129 8794 leinfelder
	 * 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 7512 leinfelder
	 * submits DOI metadata information about the object to EZID
142
	 * @param sysMeta
143
	 * @return
144
	 * @throws EZIDException
145
	 * @throws ServiceFailure
146
	 * @throws NotImplemented
147 8795 leinfelder
	 * @throws InterruptedException
148 7512 leinfelder
	 */
149 8795 leinfelder
	public boolean registerDOI(SystemMetadata sysMeta) throws EZIDException, NotImplemented, ServiceFailure, InterruptedException {
150 8794 leinfelder
151 7512 leinfelder
		// 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 8795 leinfelder
				HashMap<String, String> metadata = new HashMap<String, String>();
161 7512 leinfelder
162 7516 leinfelder
				// 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 7512 leinfelder
				Node node = MNodeService.getInstance(null).getCapabilities();
182 7516 leinfelder
				publisher = node.getName();
183
184
				// publication year
185 7512 leinfelder
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
186 7517 leinfelder
				String year = sdf.format(sysMeta.getDateUploaded());
187 7516 leinfelder
188
				// type
189 7517 leinfelder
				String resourceType = lookupResourceType(sysMeta);
190 7516 leinfelder
191 7517 leinfelder
				// format
192
				String format = sysMeta.getFormatId().getValue();
193
194
				//size
195
				String size = sysMeta.getSize().toString();
196
197 7516 leinfelder
				// target (URL)
198
				String target = node.getBaseURL() + "/v1/object/" + identifier;
199 8202 leinfelder
				String uriTemplate = null;
200
				String uriTemplateKey = "guid.ezid.uritemplate.data";
201
				ObjectFormat objectFormat = null;
202
				try {
203
					objectFormat = D1Client.getCN().getFormat(sysMeta.getFormatId());
204 8810 leinfelder
				} catch (BaseException e1) {
205 8202 leinfelder
					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 8313 walker
					target =  SystemUtil.getSecureServerURL() + uriTemplate.replaceAll("<IDENTIFIER>", identifier);
213 8202 leinfelder
				} catch (PropertyNotFoundException e) {
214
					logMetacat.warn("No target URI template found in the configuration for: " + uriTemplateKey);
215
				}
216 7516 leinfelder
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 7517 leinfelder
				// set the datacite metadata fields
228 7516 leinfelder
				metadata.put(DataCiteProfile.TITLE.toString(), title);
229
				metadata.put(DataCiteProfile.CREATOR.toString(), creator);
230
				metadata.put(DataCiteProfile.PUBLISHER.toString(), publisher);
231 7512 leinfelder
				metadata.put(DataCiteProfile.PUBLICATION_YEAR.toString(), year);
232 7516 leinfelder
				metadata.put(DataCiteProfile.RESOURCE_TYPE.toString(), resourceType);
233 7517 leinfelder
				metadata.put(DataCiteProfile.FORMAT.toString(), format);
234
				metadata.put(DataCiteProfile.SIZE.toString(), size);
235 7516 leinfelder
				metadata.put(InternalProfile.TARGET.toString(), target);
236
				metadata.put(InternalProfile.STATUS.toString(), status);
237
				metadata.put(InternalProfile.EXPORT.toString(), export);
238 7512 leinfelder
239 8795 leinfelder
				// make sure we have a current login
240
				this.refreshLogin();
241
242 7512 leinfelder
				// set using the API
243 8795 leinfelder
				ezid.createOrUpdate(identifier, metadata);
244 7512 leinfelder
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 8794 leinfelder
		// make sure we have a current login
276
		this.refreshLogin();
277
278 7512 leinfelder
		// call the EZID service
279
		String doi = ezid.mintIdentifier(shoulder, metadata);
280 8794 leinfelder
		Identifier identifier = new Identifier();
281 7512 leinfelder
		identifier.setValue(doi);
282
283
		return identifier;
284
	}
285 7516 leinfelder
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 7517 leinfelder
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 7512 leinfelder
326 7516 leinfelder
	/**
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 8810 leinfelder
		SubjectInfo subjectInfo = D1Client.getCN().getSubjectInfo(null, subject);
344 7516 leinfelder
		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 7512 leinfelder
360
}