forked from DSpace/dspace-replicate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBagItRestoreFromAIP.java
More file actions
301 lines (277 loc) · 12.6 KB
/
Copy pathBagItRestoreFromAIP.java
File metadata and controls
301 lines (277 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.ctask.replicate;
import static org.dspace.pack.PackerFactory.OBJECT_TYPE;
import static org.dspace.pack.PackerFactory.OTHER_IDS;
import static org.dspace.pack.PackerFactory.OWNER_ID;
import static org.dspace.pack.PackerFactory.WITHDRAWN;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.Site;
import org.dspace.content.WorkspaceItem;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.InstallItemService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.curate.AbstractCurationTask;
import org.dspace.curate.Curator;
import org.dspace.curate.Distributive;
import org.dspace.curate.Mutative;
import org.dspace.embargo.factory.EmbargoServiceFactory;
import org.dspace.embargo.service.EmbargoService;
import org.dspace.pack.Packer;
import org.dspace.pack.PackerFactory;
import org.dspace.pack.bagit.BagItAipReader;
import org.dspace.pack.bagit.CatalogPacker;
import org.dspace.pack.bagit.SitePacker;
/**
* BagItRestoreFromAIP task performs essentially an 'undelete' on an object that
* has been deleted from the repository, using the replica copy.
* If the object is a container, it recovers all its children/members.
*
* @author richardrodgers
* @see TransmitAIP
*/
@Distributive
@Mutative
public class BagItRestoreFromAIP extends AbstractCurationTask {
private static final Logger log = LogManager.getLogger();
private final EmbargoService embargoService = EmbargoServiceFactory.getInstance().getEmbargoService();
private final WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
private final InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
private final CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
private String archFmt;
// Group where all AIPs are stored
private String storeGroupName;
// Group where object deletion catalog/records are stored
private String deleteGroupName;
@Override
public void init(Curator curator, String taskId) throws IOException {
super.init(curator, taskId);
deleteGroupName = configurationService.getProperty("replicate.group.delete.name");
storeGroupName = configurationService.getProperty("replicate.group.aip.name");
archFmt = configurationService.getProperty("replicate.packer.archfmt");
}
/**
* Perform 'Recover From AIP' task on a particular object. If the {@code dso} is a {@link Site}, attempt to restore
* the Site and child objects. Otherwise this method returns an exception.
*
* @param dso DSpace Object to recover
* @return integer which represents Curator return status
* @throws IOException if IO error
*/
@Override
public int perform(final DSpaceObject dso) throws IOException {
// allow recovery of Site objects as they always exist in a new DSpace repository
if (dso != null && dso.getType() == Constants.SITE) {
String result;
int status = Curator.CURATE_SUCCESS;
try {
Context context = Curator.curationContext();
final ReplicaManager repMan = ReplicaManager.instance();
final String storageId = repMan.storageId(context, dso.getHandle(), archFmt);
final File file = repMan.fetchObject(context, storeGroupName, storageId);
if (file != null) {
final SitePacker sitePacker = new SitePacker(context, (Site) dso, archFmt);
sitePacker.unpack(file);
final List<String> members = sitePacker.getMembers().or(new ArrayList<>());
for (final String member : members) {
recover(context, repMan, member);
}
result = "Successfully restored Site and children from AIP(s)";
} else {
result = "Failed to restore Site. AIP could not be found in Replica Store.";
status = Curator.CURATE_FAIL;
}
} catch (AuthorizeException | SQLException e) {
throw new IOException(e);
}
report(result);
setResult(result);
return status;
} else {
throw new IllegalStateException("Cannot recover if object exists");
}
}
/**
* Perform 'Recover From AIP' task by retrieving an object package
* of a particular ID (name) and restoring it to the current DSpace
* Context.
* @param ctx current DSpace context
* @param id identifier of object to restore
* @return integer which represents Curator return status
* @throws IOException if IO error
*/
@Override
public int perform(Context ctx, String id) throws IOException {
ReplicaManager repMan = ReplicaManager.instance();
// first we locate the deletion catalog for this object
String catId = repMan.deletionCatalogId(id, archFmt);
File catArchive = repMan.fetchObject(ctx, deleteGroupName, catId);
int status = Curator.CURATE_FAIL;
String result;
// CANNOT continue if the deletion catalog cannot be located
if (catArchive != null) {
CatalogPacker cpack = new CatalogPacker(ctx, id);
cpack.unpack(catArchive);
// RLR TODO - remove filename collision next delete requires
catArchive.delete();
// recover root object itself, then any members
recover(ctx, repMan, id);
for (String mem : cpack.getMembers()) {
recover(ctx, repMan, mem);
}
// remove the deletion catalog (as the object is now restored)
repMan.removeObject(deleteGroupName, catId);
result = "Successfully restored Object '" + id + "' (and any child objects) from AIP.";
status = Curator.CURATE_SUCCESS;
} else {
result = "Failed to restore Object '" + id + "'. Deletion record could not be found in Replica Store. Are" +
" you sure this object was previously deleted?";
}
report(result);
setResult(result);
return status;
}
/**
* Recover an object from an ObjectStore based on its identifier. If an object already exists, log a warning and
* skip it.
*
* @param ctx current DSpace Context
* @param repMan ReplicaManager (used to access ObjectStore)
* @param id Identifier of object in ObjectStore
* @throws IOException if IO error
*/
private void recover(Context ctx, ReplicaManager repMan, String id) throws IOException {
final String objId = repMan.storageId(ctx, id, archFmt);
final File archive = repMan.fetchObject(ctx, storeGroupName, objId);
DSpaceObject dso;
try {
dso = dspaceObjectUtils.findDSpaceObject(ctx,id);
} catch (SQLException sqlE) {
throw new IOException(sqlE.getMessage(), sqlE);
}
if (archive != null && dso == null) {
final BagItAipReader reader = new BagItAipReader(archive.toPath());
final Properties props = reader.readProperties();
final String type = props.getProperty(OBJECT_TYPE);
final String ownerId = props.getProperty(OWNER_ID);
if ("item".equals(type)) {
recoverItem(ctx, archive, id, props);
} else if ("collection".equals(type)) {
recoverCollection(ctx, archive, id, ownerId);
} else if ("community".equals(type)) {
recoverCommunity(ctx, archive, id, ownerId);
}
// discard bag when done
reader.clean();
} else if (dso != null) {
log.warn("Unable to restore object for {}. Object already exists!", id);
}
}
/**
* Recover a DSpace Item from a particular AIP package file
* @param ctx current DSpace context
* @param archive AIP package file
* @param objId identifier of object we are restoring
* @param props properties which control how item is restored
* @throws IOException if IO error
*/
private void recoverItem(Context ctx, File archive, String objId, Properties props) throws IOException {
try {
String collId = props.getProperty(OWNER_ID);
Collection coll = (Collection) handleService.resolveToObject(ctx, collId);
WorkspaceItem wi = workspaceItemService.create(ctx, coll, false);
Packer packer = PackerFactory.instance(ctx, wi.getItem());
// stuff bag contents into item
packer.unpack(archive);
// Install item
Item item = installItemService.restoreItem(ctx, wi, objId);
String collections = props.getProperty(OTHER_IDS);
if (collections != null) {
// reset linked collections
for (String link : collections.split(",")) {
Collection linkC = (Collection) handleService.resolveToObject(ctx, link);
collectionService.addItem(ctx, linkC, item);
}
}
// now post-process: withdrawals, embargoes, etc
if (props.getProperty(WITHDRAWN) != null) {
itemService.withdraw(ctx, item);
}
embargoService.setEmbargo(ctx, item);
} catch (AuthorizeException | SQLException authE) {
throw new IOException(authE);
}
}
/**
* Recover a DSpace Collection from a particular AIP package file
* @param ctx current DSpace context
* @param archive AIP package file
* @param collId identifier of collection we are restoring
* @param commId identifier of parent community for this collection
* @throws IOException if IO error
*/
private void recoverCollection(Context ctx, File archive, String collId, String commId) throws IOException {
try {
if (commId != null) {
Community parentCommunity = (Community) handleService.resolveToObject(ctx, commId);
Collection collection = collectionService.create(ctx, parentCommunity, collId);
if (collection != null) {
// update with AIP data
Packer packer = PackerFactory.instance(ctx, collection);
packer.unpack(archive);
} else {
log.error("Unable to restore collection {} because it could not be found.", collId);
}
} else {
log.error("Collection '{}' lacks parent community", collId);
}
} catch (AuthorizeException | SQLException authE) {
throw new IOException(authE);
}
}
/**
* Recover a DSpace Community from a particular AIP package file
* @param ctx current DSpace context
* @param archive AIP package file
* @param commId identifier of community we are restoring
* @param parentId identifier of parent community (if any) for community
* @throws IOException if IO error
*/
private void recoverCommunity(Context ctx, File archive, String commId, String parentId) throws IOException {
// if not top-level, have parent create it
Community community = null;
try {
if (parentId != null) {
Community parentCommunity = (Community) handleService.resolveToObject(ctx, parentId);
community = communityService.createSubcommunity(ctx, parentCommunity, commId);
} else {
community = communityService.create(null, ctx, commId);
}
// update with AIP data
Packer packer = PackerFactory.instance(ctx, community);
packer.unpack(archive);
} catch (AuthorizeException | SQLException authE) {
throw new IOException(authE);
}
}
}