Skip to content

Commit 2b06646

Browse files
author
John Pinto
committed
Added functionality to add citation to items. If the the citation is
created with a doi, then it is updated with a doi once registered. One needs run Cron job like this periodically to apply this functionality 15 8-19 * * * $DSPACE/bin/dspace era-citation-updater -c > $DSPACE/log/era-citation-updater.log 2>&1 (Note the call requires flag -c.)
1 parent ed6af4a commit 2b06646

2 files changed

Lines changed: 361 additions & 0 deletions

File tree

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
package uk.ac.ed.era.commands;
2+
3+
import java.sql.SQLException;
4+
import java.util.Calendar;
5+
import java.util.Date;
6+
import java.util.GregorianCalendar;
7+
import java.util.List;
8+
import java.util.Spliterator;
9+
import java.util.Spliterators;
10+
import java.util.StringTokenizer;
11+
import java.util.stream.StreamSupport;
12+
13+
import org.apache.commons.cli.CommandLine;
14+
import org.apache.commons.cli.CommandLineParser;
15+
import org.apache.commons.cli.HelpFormatter;
16+
import org.apache.commons.cli.Options;
17+
import org.apache.commons.cli.ParseException;
18+
import org.apache.commons.cli.PosixParser;
19+
import org.apache.logging.log4j.LogManager;
20+
import org.apache.logging.log4j.Logger;
21+
import org.dspace.authorize.AuthorizeException;
22+
import org.dspace.content.Item;
23+
import org.dspace.content.MetadataValue;
24+
import org.dspace.content.factory.ContentServiceFactory;
25+
import org.dspace.content.service.ItemService;
26+
import org.dspace.core.Context;
27+
28+
/**
29+
* Functionality to create or update citations for items using
30+
* the Dspace CLI. When run it creates citations for items with missing
31+
* citations
32+
* stored in the metdata field dc.identifier.citation. Further, if intially the
33+
* DOI was missing
34+
* and subsequently registered. Then the citation is updated with the doi.
35+
* Once the doi is added to citation it won't be updated by running this class.
36+
*
37+
* This is configured in dspace/config/launcher.xml.
38+
* Then we run the script in a Cron job periodically., e.g,
39+
* 15 8-19 * * * $DSPACE/bin/dspace citation-updater -c >
40+
* $DSPACE/log/era-citation-updater.log 2>&1
41+
* (Note the call requires flag -c.)
42+
*
43+
* @author John Pinto
44+
*
45+
*
46+
*/
47+
public class EraCitationUpdaterCLI {
48+
49+
private static final Logger log = LogManager.getLogger(EraCitationUpdaterCLI.class);
50+
51+
private Context context;
52+
53+
public EraCitationUpdaterCLI(Context context) {
54+
this.context = context;
55+
}
56+
57+
/**
58+
*
59+
* @param argv
60+
*/
61+
public static void main(String[] argv) {
62+
// create an options object and populate it
63+
CommandLineParser parser = new PosixParser();
64+
65+
Options options = new Options();
66+
67+
options.addOption("c", "create citations", false, "Create or update citation for items.");
68+
69+
EraCitationUpdaterCLI du = new EraCitationUpdaterCLI(new Context());
70+
HelpFormatter helpformater = new HelpFormatter();
71+
try {
72+
CommandLine line = parser.parse(options, argv);
73+
if (line.hasOption('c')) {
74+
log.info("Started Creating citations");
75+
System.out.println("Started Creating citations");
76+
du.createCitations();
77+
log.info("Completed Creating citations");
78+
System.out.println("Completed Creating citations");
79+
} else {
80+
helpformater.printHelp("\nEra DOI\n", options);
81+
}
82+
} catch (ParseException ex) {
83+
log.info(ex);
84+
System.out.println(ex.getMessage());
85+
helpformater.printHelp("\nEra DOI\n", options);
86+
}
87+
}
88+
89+
/**
90+
* Create a citation for all items that have a new doi.
91+
*/
92+
private void createCitations() {
93+
context.turnOffAuthorisationSystem();
94+
95+
try {
96+
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
97+
// Convert iterator to stream and process items functionally
98+
StreamSupport.stream(
99+
Spliterators.spliteratorUnknownSize(
100+
itemService.findAll(context),
101+
Spliterator.ORDERED),
102+
false)
103+
.filter(item -> needsCitationUpdate(item))
104+
.forEach(item -> processItemCitation(item));
105+
106+
context.complete();
107+
} catch (SQLException ex) {
108+
throw new RuntimeException(ex);
109+
} finally {
110+
context.restoreAuthSystemState();
111+
}
112+
113+
}
114+
115+
/**
116+
* Checks if item needs a citation or needs updating.
117+
*
118+
* @param item
119+
* @return boolean
120+
*/
121+
private boolean needsCitationUpdate(Item item) {
122+
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
123+
124+
// Get citation directly using DSpace API
125+
List<MetadataValue> citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY, false);
126+
String citation = citations.isEmpty() ? null : citations.get(0).getValue();
127+
128+
// Check if item has DOI directly using DSpace API
129+
List<MetadataValue> identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
130+
boolean hasDoi = identifiers.stream()
131+
.anyMatch(identifier -> identifier.getValue().contains("doi.org"));
132+
133+
log.info("Item {} citation: '{}' hasDoi: {}", item.getID(), citation, hasDoi);
134+
135+
// Case 1: Has no citation
136+
boolean needsNewCitation = citation == null;
137+
138+
// Case 2: Has doi, but citation doesn't contain the DOI URL
139+
boolean needsUpdatedCitation = hasDoi && citation != null && !citation.contains("doi.org");
140+
141+
log.info("Item {} needsNewCitation: {} needsUpdatedCitation: {}",
142+
item.getID(), needsNewCitation, needsUpdatedCitation);
143+
144+
return needsNewCitation || needsUpdatedCitation;
145+
}
146+
147+
/**
148+
* Process that creates or updates item's citation.
149+
*
150+
* @param item
151+
*/
152+
private void processItemCitation(Item item) {
153+
try {
154+
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
155+
156+
// Get current citation
157+
List<MetadataValue> citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY,
158+
false);
159+
String citation = citations.isEmpty() ? null : citations.get(0).getValue();
160+
161+
// Check if item has DOI
162+
List<MetadataValue> identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
163+
boolean hasDoi = identifiers.stream()
164+
.anyMatch(identifier -> identifier.getValue().contains("doi.org"));
165+
166+
log.info("Item " + item.getID() + " citation: " + citation);
167+
168+
if (citation == null && hasDoi) {
169+
// Create new citation
170+
String newCitation = createCitation(item);
171+
if (newCitation != null) {
172+
itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
173+
}
174+
} else if (citation != null && !citation.contains("doi.org") && hasDoi) {
175+
// Clear existing citation and create new one
176+
itemService.clearMetadata(context, item, "dc", "identifier", "citation", Item.ANY);
177+
String newCitation = createCitation(item);
178+
if (newCitation != null) {
179+
itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
180+
log.info("Item " + item.getID() + " has new citation: " + newCitation);
181+
}
182+
}
183+
184+
itemService.update(context, item);
185+
} catch (AuthorizeException | SQLException ex) {
186+
log.info("Error updating citation for item " + item.getID() + ": " + ex.getMessage());
187+
}
188+
}
189+
190+
/**
191+
* Create a citation for a given DSpace item using DSpace core APIs
192+
*/
193+
private String createCitation(Item item) {
194+
try {
195+
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
196+
StringBuilder buffer = new StringBuilder(200);
197+
198+
// Get authors
199+
List<MetadataValue> authors = itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY, false);
200+
boolean authorGiven = !authors.isEmpty();
201+
202+
if (authorGiven) {
203+
// Add authors
204+
for (int i = 0; i < authors.size(); i++) {
205+
if (i > 0) {
206+
buffer.append("; ");
207+
}
208+
buffer.append(authors.get(i).getValue());
209+
}
210+
buffer.append(". ");
211+
} else {
212+
// Add publisher if no authors
213+
List<MetadataValue> publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
214+
false);
215+
if (!publishers.isEmpty()) {
216+
buffer.append(" ");
217+
buffer.append(publishers.get(0).getValue());
218+
buffer.append(".");
219+
}
220+
buffer.append(" ");
221+
}
222+
223+
// Add date available year if available
224+
buffer.append("(");
225+
List<MetadataValue> dateAvailable = itemService.getMetadata(item, "dc", "date", "available", Item.ANY,
226+
false);
227+
if (!dateAvailable.isEmpty()) {
228+
String dateStr = dateAvailable.get(0).getValue();
229+
// Extract year from date string (assuming format like "2023-01-01" or "2023")
230+
String year = dateStr.length() >= 4 ? dateStr.substring(0, 4) : dateStr;
231+
buffer.append(year);
232+
} else {
233+
// No date available, use current year
234+
Calendar calendar = new GregorianCalendar();
235+
calendar.setTime(new Date());
236+
buffer.append(calendar.get(Calendar.YEAR));
237+
}
238+
buffer.append("). ");
239+
240+
// Add title
241+
List<MetadataValue> titles = itemService.getMetadata(item, "dc", "title", Item.ANY, Item.ANY, false);
242+
if (!titles.isEmpty()) {
243+
buffer.append(titles.get(0).getValue());
244+
}
245+
buffer.append(", ");
246+
247+
// Add time period if available
248+
List<MetadataValue> temporal = itemService.getMetadata(item, "dc", "coverage", "temporal", Item.ANY, false);
249+
if (!temporal.isEmpty()) {
250+
String timePeriod = temporal.get(0).getValue();
251+
String[] dates = decodeTimePeriod(timePeriod);
252+
253+
if (dates != null && dates.length == 2) {
254+
String from = dates[0].length() >= 4 ? dates[0].substring(0, 4) : dates[0];
255+
String to = dates[1].length() >= 4 ? dates[1].substring(0, 4) : dates[1];
256+
257+
if (from.equals(to)) {
258+
timePeriod = from;
259+
} else {
260+
timePeriod = from + "-" + to;
261+
}
262+
263+
buffer.append(timePeriod);
264+
buffer.append(" ");
265+
}
266+
}
267+
268+
// Add item type
269+
List<MetadataValue> types = itemService.getMetadata(item, "dc", "type", Item.ANY, Item.ANY, false);
270+
buffer.append("[");
271+
if (!types.isEmpty()) {
272+
buffer.append(types.get(0).getValue());
273+
}
274+
buffer.append("].");
275+
276+
// Append publisher if author is specified
277+
if (authorGiven) {
278+
List<MetadataValue> publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
279+
false);
280+
if (!publishers.isEmpty()) {
281+
buffer.append(" ");
282+
buffer.append(publishers.get(0).getValue());
283+
buffer.append(".");
284+
}
285+
}
286+
287+
// Add DOI if available
288+
List<MetadataValue> identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
289+
for (MetadataValue identifier : identifiers) {
290+
if (identifier.getValue().contains("doi.org")) {
291+
buffer.append(" ");
292+
buffer.append(identifier.getValue());
293+
buffer.append(".");
294+
break;
295+
}
296+
}
297+
298+
return buffer.toString();
299+
300+
} catch (Exception e) {
301+
log.info("Error creating citation for item " + item.getID() + ": " + e.getMessage());
302+
return null;
303+
}
304+
}
305+
306+
/**
307+
* Decode time period W3CDTF profile of ISO 8601.
308+
* (Copied from EraDspaceUtils since we can't use it)
309+
*/
310+
private String[] decodeTimePeriod(String encoding) {
311+
String[] dates = null;
312+
313+
if (encoding != null) {
314+
String startStr = null;
315+
String endStr = null;
316+
317+
// get tokens delimited by ";"- there should be three -
318+
// start=, end= and scheme=
319+
StringTokenizer st = new StringTokenizer(encoding, ";");
320+
321+
if (st.countTokens() > 1) {
322+
for (int i = 0; i < st.countTokens(); i++) {
323+
if (i == 0) {
324+
startStr = st.nextToken();
325+
} else if (i == 1) {
326+
endStr = st.nextToken();
327+
} else {
328+
break;
329+
}
330+
}
331+
332+
String startArray[] = startStr.split("=");
333+
String endArray[] = endStr.split("=");
334+
335+
if (startArray.length == 2 || endArray.length == 2) {
336+
dates = new String[2];
337+
}
338+
339+
if (startArray.length == 2) {
340+
dates[0] = startArray[1];
341+
}
342+
343+
if (endArray.length == 2) {
344+
dates[1] = endArray[1];
345+
}
346+
}
347+
}
348+
349+
return dates;
350+
}
351+
352+
}

dspace/config/launcher.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,13 @@
345345
<class>org.dspace.iiif.canvasdimension.CanvasDimensionCLI</class>
346346
</step>
347347
</command>
348+
<!-- ERA specific custom comands-->
349+
<command>
350+
<name>era-citation-updater</name>
351+
<description>Create or update citations for Items.</description>
352+
<step>
353+
<class>uk.ac.ed.era.commands.EraCitationUpdaterCLI</class>
354+
</step>
355+
</command>
356+
<!-- END-->
348357
</commands>

0 commit comments

Comments
 (0)