Skip to content

Commit 0f84474

Browse files
committed
[DURACOM-457] Port Bulk Import feature from DSpace-CRIS
Adds Excel-based bulk import functionality for creating, updating, and deleting items. Supports metadata groups, bitstreams, authority references, security levels, and collection export. Fully integrated into CLI and REST APIs without CRIS dependencies.
1 parent 56113c2 commit 0f84474

File tree

92 files changed

+7684
-25
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+7684
-25
lines changed

dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java

Lines changed: 1642 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
10+
import java.sql.SQLException;
11+
12+
import org.apache.commons.cli.ParseException;
13+
import org.dspace.core.Context;
14+
import org.dspace.eperson.EPerson;
15+
import org.dspace.eperson.factory.EPersonServiceFactory;
16+
17+
/**
18+
* Extension of {@link BulkImport} for CLI.
19+
*
20+
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
21+
*
22+
*/
23+
public class BulkImportCli extends BulkImport {
24+
25+
@Override
26+
protected void assignCurrentUserInContext(Context context) throws ParseException {
27+
if (commandLine.hasOption('e')) {
28+
String ePersonEmail = commandLine.getOptionValue('e');
29+
try {
30+
EPerson ePerson =
31+
EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, ePersonEmail);
32+
if (ePerson == null) {
33+
super.handler.logError("EPerson not found: " + ePersonEmail);
34+
throw new IllegalArgumentException("Unable to find a user with email: " + ePersonEmail);
35+
}
36+
context.setCurrentUser(ePerson);
37+
} catch (SQLException e) {
38+
throw new IllegalArgumentException("SQLException trying to find user with email: " + ePersonEmail);
39+
}
40+
} else {
41+
throw new ParseException("Required parameter -e missing!");
42+
}
43+
}
44+
45+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
10+
import org.apache.commons.cli.Options;
11+
12+
/**
13+
* Extension of {@link BulkImportScriptConfiguration} for CLI.
14+
*
15+
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
16+
*
17+
*/
18+
public class BulkImportCliScriptConfiguration<T extends BulkImportCli> extends BulkImportScriptConfiguration<T> {
19+
20+
@Override
21+
public Options getOptions() {
22+
Options options = super.getOptions();
23+
options.addOption("e", "email", true, "email address of user");
24+
options.getOption("e").setRequired(true);
25+
super.options = options;
26+
return options;
27+
}
28+
29+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
10+
import java.io.InputStream;
11+
import java.sql.SQLException;
12+
import java.util.List;
13+
14+
import org.apache.commons.cli.Options;
15+
import org.dspace.authorize.service.AuthorizeService;
16+
import org.dspace.core.Context;
17+
import org.dspace.scripts.DSpaceCommandLineParameter;
18+
import org.dspace.scripts.configuration.ScriptConfiguration;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
21+
/**
22+
* Script configuration for {@link BulkImport}.
23+
*
24+
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
25+
*
26+
* @param <T> the {@link BulkImport} type
27+
*/
28+
public class BulkImportScriptConfiguration<T extends BulkImport> extends ScriptConfiguration<T> {
29+
30+
@Autowired
31+
private AuthorizeService authorizeService;
32+
33+
private Class<T> dspaceRunnableClass;
34+
35+
@Override
36+
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
37+
try {
38+
return authorizeService.isCollectionAdmin(context) || authorizeService.isAdmin(context);
39+
} catch (SQLException e) {
40+
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
41+
}
42+
}
43+
44+
@Override
45+
public Options getOptions() {
46+
if (options == null) {
47+
Options options = new Options();
48+
49+
options.addOption("c", "collection", true, "the own collection of the imported items");
50+
options.getOption("c").setType(String.class);
51+
options.getOption("c").setRequired(true);
52+
53+
options.addOption("f", "file", true, "source file");
54+
options.getOption("f").setType(InputStream.class);
55+
options.getOption("f").setRequired(true);
56+
57+
options.addOption("er", "concludeOnError", false, "conclude the import at the first error");
58+
options.getOption("er").setType(boolean.class);
59+
options.getOption("er").setRequired(false);
60+
61+
super.options = options;
62+
}
63+
return options;
64+
}
65+
66+
@Override
67+
public Class<T> getDspaceRunnableClass() {
68+
return dspaceRunnableClass;
69+
}
70+
71+
/**
72+
* Generic setter for the dspaceRunnableClass
73+
*
74+
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this
75+
* BulkImportScriptConfiguration
76+
*/
77+
@Override
78+
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
79+
this.dspaceRunnableClass = dspaceRunnableClass;
80+
}
81+
82+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
10+
import org.apache.poi.ss.usermodel.Sheet;
11+
12+
/**
13+
* Enum that identifies the type of a bulk import excel sheet.
14+
*
15+
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
16+
*
17+
*/
18+
public enum BulkImportSheetType {
19+
20+
/**
21+
* The type of the main sheet of the Bulk Import excel.
22+
*/
23+
ENTITY_ROWS,
24+
25+
/**
26+
* The type of the sheets with nested metadata groups.
27+
*/
28+
METADATA_GROUPS,
29+
30+
/**
31+
* The type of the sheet with the bitstream access condition and metadata
32+
* fields.
33+
*/
34+
BITSTREAMS;
35+
36+
public static BulkImportSheetType getTypeFromSheet(Sheet sheet) {
37+
38+
if (sheet.getWorkbook().getSheetIndex(sheet) == 0) {
39+
return BulkImportSheetType.ENTITY_ROWS;
40+
}
41+
42+
if (BulkImport.BITSTREAMS_SHEET_NAME.equalsIgnoreCase(sheet.getSheetName())) {
43+
return BulkImportSheetType.BITSTREAMS;
44+
}
45+
46+
return BulkImportSheetType.METADATA_GROUPS;
47+
48+
}
49+
50+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
import java.util.Map;
10+
11+
import org.dspace.content.vo.MetadataValueVO;
12+
import org.dspace.core.Context;
13+
14+
/**
15+
* The purpose of this class is to manage MetadataValueVO transformations.
16+
*
17+
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it)
18+
*/
19+
public class BulkImportTransformerService {
20+
21+
private Map<String, BulkImportValueTransformer> field2ValueTransformer;
22+
23+
public BulkImportTransformerService (Map<String, BulkImportValueTransformer> field2ValueTransformer) {
24+
this.field2ValueTransformer = field2ValueTransformer;
25+
}
26+
27+
public MetadataValueVO converter(Context context, String field, MetadataValueVO metadataValue) {
28+
if (field2ValueTransformer.containsKey(field)) {
29+
BulkImportValueTransformer transformer = field2ValueTransformer.get(field);
30+
return transformer.transform(context, metadataValue);
31+
}
32+
return metadataValue;
33+
}
34+
35+
public Map<String, BulkImportValueTransformer> getField2ValueTransformer() {
36+
return field2ValueTransformer;
37+
}
38+
39+
public void setField2ValueTransformer(Map<String, BulkImportValueTransformer> field2ValueTransformer) {
40+
this.field2ValueTransformer = field2ValueTransformer;
41+
}
42+
43+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkedit;
9+
import org.dspace.content.vo.MetadataValueVO;
10+
import org.dspace.core.Context;
11+
12+
/**
13+
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it)
14+
*/
15+
public interface BulkImportValueTransformer {
16+
17+
MetadataValueVO transform(Context context, MetadataValueVO metadataValue);
18+
19+
}

dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ public void internalRun() throws Exception {
190190
// Create a context
191191
Context c = null;
192192
c = new Context();
193-
c.turnOffAuthorisationSystem();
194193

195194
// Find the EPerson, assign to context
196195
assignCurrentUserInContext(c);
196+
assignSpecialGroupsInContext(c);
197197

198198
if (authorityControlled == null) {
199199
setAuthorizedMetadataFields();
@@ -218,6 +218,7 @@ public void internalRun() throws Exception {
218218
initMetadataImport(csv);
219219
List<BulkEditChange> changes;
220220

221+
handleAuthorizationSystem(c);
221222
if (!commandLine.hasOption('s') || validateOnly) {
222223
// See what has changed
223224
try {
@@ -261,7 +262,7 @@ public void internalRun() throws Exception {
261262
}
262263

263264
// Finish off and tidy up
264-
c.restoreAuthSystemState();
265+
handleAuthorizationSystem(c);
265266
c.complete();
266267
} catch (Exception e) {
267268
c.abort();
@@ -283,6 +284,12 @@ protected void assignCurrentUserInContext(Context context) throws ParseException
283284
}
284285
}
285286

287+
private void assignSpecialGroupsInContext(Context context) throws SQLException {
288+
for (UUID uuid : handler.getSpecialGroups()) {
289+
context.setSpecialGroup(uuid);
290+
}
291+
}
292+
286293
/**
287294
* This method determines whether the changes should be applied or not. This is default set to true for the REST
288295
* script as we don't want to interact with the caller. This will be overwritten in the CLI script to ask for
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* The contents of this file are subject to the license and copyright
3+
* detailed in the LICENSE and NOTICE files at the root of the source
4+
* tree and available online at
5+
*
6+
* http://www.dspace.org/license/
7+
*/
8+
package org.dspace.app.bulkimport.exception;
9+
10+
/**
11+
* Exception for errors that occurs during the items bulk import via excel.
12+
*
13+
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
14+
*
15+
*/
16+
public class BulkImportException extends RuntimeException {
17+
18+
private static final long serialVersionUID = -74730626862418515L;
19+
20+
/**
21+
* Constructor with error message and cause.
22+
*
23+
* @param message the error message
24+
* @param cause the error cause
25+
*/
26+
public BulkImportException(String message, Throwable cause) {
27+
super(message, cause);
28+
}
29+
30+
/**
31+
* Constructor with error message.
32+
*
33+
* @param message the error message
34+
*/
35+
public BulkImportException(String message) {
36+
super(message);
37+
}
38+
39+
/**
40+
* Constructor with error cause.
41+
*
42+
* @param cause the error cause
43+
*/
44+
public BulkImportException(Throwable cause) {
45+
super(cause);
46+
}
47+
48+
}

0 commit comments

Comments
 (0)