Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 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

    
24
package edu.ucsb.nceas.metacat.dataone;
25

    
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.security.NoSuchAlgorithmException;
29
import java.sql.SQLException;
30
import java.util.Calendar;
31
import java.util.Date;
32
import java.util.List;
33
import java.util.Timer;
34

    
35
import javax.servlet.http.HttpServletRequest;
36

    
37
import org.apache.commons.io.IOUtils;
38
import org.apache.log4j.Logger;
39
import org.dataone.client.CNode;
40
import org.dataone.client.D1Client;
41
import org.dataone.client.MNode;
42
import org.dataone.client.auth.CertificateManager;
43
import org.dataone.configuration.Settings;
44
import org.dataone.service.exceptions.BaseException;
45
import org.dataone.service.exceptions.IdentifierNotUnique;
46
import org.dataone.service.exceptions.InsufficientResources;
47
import org.dataone.service.exceptions.InvalidRequest;
48
import org.dataone.service.exceptions.InvalidSystemMetadata;
49
import org.dataone.service.exceptions.InvalidToken;
50
import org.dataone.service.exceptions.NotAuthorized;
51
import org.dataone.service.exceptions.NotFound;
52
import org.dataone.service.exceptions.NotImplemented;
53
import org.dataone.service.exceptions.ServiceFailure;
54
import org.dataone.service.exceptions.SynchronizationFailed;
55
import org.dataone.service.exceptions.UnsupportedType;
56
import org.dataone.service.exceptions.VersionMismatch;
57
import org.dataone.service.mn.tier1.v1.MNCore;
58
import org.dataone.service.mn.tier1.v1.MNRead;
59
import org.dataone.service.mn.tier2.v1.MNAuthorization;
60
import org.dataone.service.mn.tier3.v1.MNStorage;
61
import org.dataone.service.mn.tier4.v1.MNReplication;
62
import org.dataone.service.types.v1.AccessPolicy;
63
import org.dataone.service.types.v1.Checksum;
64
import org.dataone.service.types.v1.DescribeResponse;
65
import org.dataone.service.types.v1.Event;
66
import org.dataone.service.types.v1.Group;
67
import org.dataone.service.types.v1.Identifier;
68
import org.dataone.service.types.v1.Log;
69
import org.dataone.service.types.v1.LogEntry;
70
import org.dataone.service.types.v1.MonitorInfo;
71
import org.dataone.service.types.v1.MonitorList;
72
import org.dataone.service.types.v1.Node;
73
import org.dataone.service.types.v1.NodeList;
74
import org.dataone.service.types.v1.NodeReference;
75
import org.dataone.service.types.v1.NodeState;
76
import org.dataone.service.types.v1.NodeType;
77
import org.dataone.service.types.v1.ObjectFormatIdentifier;
78
import org.dataone.service.types.v1.ObjectList;
79
import org.dataone.service.types.v1.Permission;
80
import org.dataone.service.types.v1.Ping;
81
import org.dataone.service.types.v1.ReplicationStatus;
82
import org.dataone.service.types.v1.Schedule;
83
import org.dataone.service.types.v1.Service;
84
import org.dataone.service.types.v1.Services;
85
import org.dataone.service.types.v1.Session;
86
import org.dataone.service.types.v1.Subject;
87
import org.dataone.service.types.v1.Synchronization;
88
import org.dataone.service.types.v1.SystemMetadata;
89
import org.dataone.service.types.v1.util.ChecksumUtil;
90
import org.dataone.service.util.Constants;
91

    
92
import edu.ucsb.nceas.metacat.DocumentImpl;
93
import edu.ucsb.nceas.metacat.EventLog;
94
import edu.ucsb.nceas.metacat.IdentifierManager;
95
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
96
import edu.ucsb.nceas.metacat.MetacatHandler;
97
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
98
import edu.ucsb.nceas.metacat.database.DBConnection;
99
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
100
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
101
import edu.ucsb.nceas.metacat.properties.PropertyService;
102
import edu.ucsb.nceas.metacat.shared.ServiceException;
103
import edu.ucsb.nceas.metacat.util.SystemUtil;
104
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
105

    
106
/**
107
 * Represents Metacat's implementation of the DataONE Member Node 
108
 * service API. Methods implement the various MN* interfaces, and methods common
109
 * to both Member Node and Coordinating Node interfaces are found in the
110
 * D1NodeService base class.
111
 * 
112
 * Implements:
113
 * MNCore.ping()
114
 * MNCore.getLogRecords()
115
 * MNCore.getObjectStatistics()
116
 * MNCore.getOperationStatistics()
117
 * MNCore.getStatus()
118
 * MNCore.getCapabilities()
119
 * MNRead.get()
120
 * MNRead.getSystemMetadata()
121
 * MNRead.describe()
122
 * MNRead.getChecksum()
123
 * MNRead.listObjects()
124
 * MNRead.synchronizationFailed()
125
 * MNAuthorization.isAuthorized()
126
 * MNAuthorization.setAccessPolicy()
127
 * MNStorage.create()
128
 * MNStorage.update()
129
 * MNStorage.delete()
130
 * MNReplication.replicate()
131
 * 
132
 */
133
public class MNodeService extends D1NodeService 
134
    implements MNAuthorization, MNCore, MNRead, MNReplication, MNStorage {
135

    
136
    /* the logger instance */
137
    private Logger logMetacat = null;
138
    
139
    /* A reference to a remote Memeber Node */
140
    private MNode mn;
141
    
142
    /* A reference to a Coordinating Node */
143
    private CNode cn;
144

    
145

    
146
    /**
147
     * Singleton accessor to get an instance of MNodeService.
148
     * 
149
     * @return instance - the instance of MNodeService
150
     */
151
    public static MNodeService getInstance(HttpServletRequest request) {
152
        return new MNodeService(request);
153
    }
154

    
155
    /**
156
     * Constructor, private for singleton access
157
     */
158
    private MNodeService(HttpServletRequest request) {
159
        super(request);
160
        logMetacat = Logger.getLogger(MNodeService.class);
161
        
162
        // set the Member Node certificate file location
163
        CertificateManager.getInstance().setCertificateLocation(Settings.getConfiguration().getString("D1Client.certificate.file"));
164
    }
165

    
166
    /**
167
     * Deletes an object from the Member Node, where the object is either a 
168
     * data object or a science metadata object.
169
     * 
170
     * @param session - the Session object containing the credentials for the Subject
171
     * @param pid - The object identifier to be deleted
172
     * 
173
     * @return pid - the identifier of the object used for the deletion
174
     * 
175
     * @throws InvalidToken
176
     * @throws ServiceFailure
177
     * @throws NotAuthorized
178
     * @throws NotFound
179
     * @throws NotImplemented
180
     * @throws InvalidRequest
181
     */
182
    @Override
183
    public Identifier delete(Session session, Identifier pid) 
184
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
185

    
186
        String localId = null;
187
        boolean allowed = false;
188
        String username = Constants.SUBJECT_PUBLIC;
189
        String[] groupnames = null;
190
        if (session == null) {
191
        	throw new InvalidToken("1330", "No session has been provided");
192
        } else {
193
            username = session.getSubject().getValue();
194
            if (session.getSubjectInfo() != null) {
195
                List<Group> groupList = session.getSubjectInfo().getGroupList();
196
                if (groupList != null) {
197
                    groupnames = new String[groupList.size()];
198
                    for (int i = 0; i > groupList.size(); i++) {
199
                        groupnames[i] = groupList.get(i).getGroupName();
200
                    }
201
                }
202
            }
203
        }
204

    
205
        // do we have a valid pid?
206
        if (pid == null || pid.getValue().trim().equals("")) {
207
            throw new ServiceFailure("1350", "The provided identifier was invalid.");
208
        }
209

    
210
        // check for the existing identifier
211
        try {
212
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
213
        } catch (McdbDocNotFoundException e) {
214
            throw new NotFound("1340", "The object with the provided " + "identifier was not found.");
215
        }
216

    
217
        // does the subject have DELETE (a D1 CHANGE_PERMISSION level) priveleges on the pid?
218
        allowed = isAuthorized(session, pid, Permission.CHANGE_PERMISSION);
219
            
220

    
221
        if (allowed) {
222
            try {
223
                // delete the document
224
                DocumentImpl.delete(localId, username, groupnames, null);
225
                EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), username, localId, Event.DELETE.xmlValue());
226

    
227
                // archive it
228
                SystemMetadata sysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
229
                sysMeta.setArchived(true);
230
                HazelcastService.getInstance().getSystemMetadataMap().put(pid, sysMeta);
231
                
232
                // remove the system metadata for it
233
                //HazelcastService.getInstance().getSystemMetadataMap().remove(pid);
234
                
235
            } catch (McdbDocNotFoundException e) {
236
                throw new NotFound("1340", "The provided identifier was invalid.");
237

    
238
            } catch (SQLException e) {
239
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
240

    
241
            } catch (InsufficientKarmaException e) {
242
                throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
243

    
244
            } catch (Exception e) { // for some reason DocumentImpl throws a general Exception
245
                throw new ServiceFailure("1350", "There was a problem deleting the object." + "The error message was: " + e.getMessage());
246
            }
247

    
248
        } else {
249
            throw new NotAuthorized("1320", "The provided identity does not have " + "permission to DELETE objects on the Member Node.");
250
        }
251

    
252
        return pid;
253
    }
254

    
255
    /**
256
     * Updates an existing object by creating a new object identified by 
257
     * newPid on the Member Node which explicitly obsoletes the object 
258
     * identified by pid through appropriate changes to the SystemMetadata 
259
     * of pid and newPid
260
     * 
261
     * @param session - the Session object containing the credentials for the Subject
262
     * @param pid - The identifier of the object to be updated
263
     * @param object - the new object bytes
264
     * @param sysmeta - the new system metadata describing the object
265
     * 
266
     * @return newPid - the identifier of the new object
267
     * 
268
     * @throws InvalidToken
269
     * @throws ServiceFailure
270
     * @throws NotAuthorized
271
     * @throws NotFound
272
     * @throws NotImplemented
273
     * @throws IdentifierNotUnique
274
     * @throws UnsupportedType
275
     * @throws InsufficientResources
276
     * @throws InvalidSystemMetadata
277
     * @throws InvalidRequest
278
     */
279
    @Override
280
    public Identifier update(Session session, Identifier pid, InputStream object, 
281
        Identifier newPid, SystemMetadata sysmeta) 
282
        throws InvalidToken, ServiceFailure, NotAuthorized, IdentifierNotUnique, 
283
        UnsupportedType, InsufficientResources, NotFound, 
284
        InvalidSystemMetadata, NotImplemented, InvalidRequest {
285

    
286
        String localId = null;
287
        boolean allowed = false;
288
        boolean isScienceMetadata = false;
289
        
290
        if (session == null) {
291
        	throw new InvalidToken("1210", "No session has been provided");
292
        }
293
        Subject subject = session.getSubject();
294

    
295
        // do we have a valid pid?
296
        if (pid == null || pid.getValue().trim().equals("")) {
297
            throw new InvalidRequest("1202", "The provided identifier was invalid.");
298
            
299
        }
300

    
301
        // check for the existing identifier
302
        try {
303
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
304
            
305
        } catch (McdbDocNotFoundException e) {
306
            throw new InvalidRequest("1202", "The object with the provided " + 
307
                "identifier was not found.");
308
            
309
        }
310
        
311
        // set the originating node
312
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
313
        sysmeta.setOriginMemberNode(originMemberNode);
314
        
315
        // set the submitter to match the certificate
316
        sysmeta.setSubmitter(subject);
317
        // set the dates
318
        Date now = Calendar.getInstance().getTime();
319
        sysmeta.setDateSysMetadataModified(now);
320
        sysmeta.setDateUploaded(now);
321

    
322
        // does the subject have WRITE ( == update) priveleges on the pid?
323
        allowed = isAuthorized(session, pid, Permission.WRITE);
324

    
325
        if (allowed) {
326
        	
327
        	// check quality of SM
328
        	if (sysmeta.getObsoletedBy() != null) {
329
        		throw new InvalidSystemMetadata("1300", "Cannot include obsoletedBy when updating object");
330
        	}
331
        	if (sysmeta.getObsoletes() != null && !sysmeta.getObsoletes().getValue().equals(pid.getValue())) {
332
        		throw new InvalidSystemMetadata("1300", "The identifier provided in obsoletes does not match old Identifier");
333
        	}
334

    
335
            // get the existing system metadata for the object
336
            SystemMetadata existingSysMeta = getSystemMetadata(session, pid);
337

    
338
            // add the newPid to the obsoletedBy list for the existing sysmeta
339
            existingSysMeta.setObsoletedBy(newPid);
340

    
341
            // then update the existing system metadata
342
            updateSystemMetadata(existingSysMeta);
343

    
344
            // prep the new system metadata, add pid to the affected lists
345
            sysmeta.setObsoletes(pid);
346
            //sysmeta.addDerivedFrom(pid);
347

    
348
            isScienceMetadata = isScienceMetadata(sysmeta);
349

    
350
            // do we have XML metadata or a data object?
351
            if (isScienceMetadata) {
352

    
353
                // update the science metadata XML document
354
                // TODO: handle non-XML metadata/data documents (like netCDF)
355
                // TODO: don't put objects into memory using stream to string
356
                String objectAsXML = "";
357
                try {
358
                    objectAsXML = IOUtils.toString(object, "UTF-8");
359
                    localId = insertOrUpdateDocument(objectAsXML, newPid, session, "update");
360
                    // register the newPid and the generated localId
361
                    if (newPid != null) {
362
                        IdentifierManager.getInstance().createMapping(newPid.getValue(), localId);
363

    
364
                    }
365

    
366
                } catch (IOException e) {
367
                    String msg = "The Node is unable to create the object. " + "There was a problem converting the object to XML";
368
                    logMetacat.info(msg);
369
                    throw new ServiceFailure("1310", msg + ": " + e.getMessage());
370

    
371
                }
372

    
373
            } else {
374

    
375
                // update the data object
376
                localId = insertDataObject(object, newPid, session);
377

    
378
            }
379

    
380
            // and insert the new system metadata
381
            insertSystemMetadata(sysmeta);
382

    
383
            // log the update event
384
            EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), subject.getValue(), localId, Event.UPDATE.toString());
385

    
386
        } else {
387
            throw new NotAuthorized("1200", "The provided identity does not have " + "permission to UPDATE the object identified by " + pid.getValue()
388
                    + " on the Member Node.");
389
        }
390

    
391
        return newPid;
392
    }
393

    
394
    public Identifier create(Session session, Identifier pid, InputStream object, SystemMetadata sysmeta) throws InvalidToken, ServiceFailure, NotAuthorized,
395
            IdentifierNotUnique, UnsupportedType, InsufficientResources, InvalidSystemMetadata, NotImplemented, InvalidRequest {
396

    
397
      // check for null session
398
        if (session == null) {
399
          throw new InvalidToken("1110", "Session is required to WRITE to the Node.");
400
        }
401
        // set the submitter to match the certificate
402
        sysmeta.setSubmitter(session.getSubject());
403
        // set the originating node
404
        NodeReference originMemberNode = this.getCapabilities().getIdentifier();
405
        sysmeta.setOriginMemberNode(originMemberNode);
406
        // set the dates
407
        Date now = Calendar.getInstance().getTime();
408
    sysmeta.setDateSysMetadataModified(now);
409
    sysmeta.setDateUploaded(now);
410
        // call the shared impl
411
        return super.create(session, pid, object, sysmeta);
412
    }
413

    
414
    /**
415
     * Called by a Coordinating Node to request that the Member Node create a 
416
     * copy of the specified object by retrieving it from another Member 
417
     * Node and storing it locally so that it can be made accessible to 
418
     * the DataONE system.
419
     * 
420
     * @param session - the Session object containing the credentials for the Subject
421
     * @param sysmeta - Copy of the CN held system metadata for the object
422
     * @param sourceNode - A reference to node from which the content should be 
423
     *                     retrieved. The reference should be resolved by 
424
     *                     checking the CN node registry.
425
     * 
426
     * @return true if the replication succeeds
427
     * 
428
     * @throws ServiceFailure
429
     * @throws NotAuthorized
430
     * @throws NotImplemented
431
     * @throws UnsupportedType
432
     * @throws InsufficientResources
433
     * @throws InvalidRequest
434
     */
435
    @Override
436
    public boolean replicate(Session session, SystemMetadata sysmeta,
437
            NodeReference sourceNode) throws NotImplemented, ServiceFailure,
438
            NotAuthorized, InvalidRequest, InsufficientResources,
439
            UnsupportedType {
440

    
441
        logMetacat.info("MNodeService.replicate() called with parameters: \n"
442
                + "\tSession.Subject      = " + session.getSubject().getValue()
443
                + "\n" + "\tSystemMetadata       = " + sysmeta.toString()
444
                + "\n" + "\tSource NodeReference =" + sourceNode.getValue());
445

    
446
        boolean result = false;
447
        String nodeIdStr = null;
448
        NodeReference nodeId = null;
449

    
450
        // get the referenced object
451
        Identifier pid = sysmeta.getIdentifier();
452

    
453
        // get from the membernode
454
        // TODO: switch credentials for the server retrieval?
455
        this.mn = D1Client.getMN(sourceNode);
456
        this.cn = D1Client.getCN();
457
        InputStream object = null;
458
        Session thisNodeSession = null;
459
        SystemMetadata localSystemMetadata = null;
460
        BaseException failure = null;
461

    
462
        // TODO: check credentials
463
        // cannot be called by public
464
        if (session == null) {
465
            String msg = "No session was provided.";
466
            failure = new NotAuthorized("2152", msg);
467
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
468
            logMetacat.info(msg);
469
            return true;
470
        }
471

    
472

    
473
        // get the local node id
474
        try {
475
            nodeIdStr = PropertyService.getProperty("dataone.memberNodeId");
476
            nodeId = new NodeReference();
477
            nodeId.setValue(nodeIdStr);
478

    
479
        } catch (PropertyNotFoundException e1) {
480
            String msg = "Couldn't get dataone.memberNodeId property: " + e1.getMessage();
481
            failure = new ServiceFailure("2151", msg);
482
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
483
            logMetacat.error(msg);
484
            return true;
485

    
486
        }
487
        
488

    
489
        try {
490
            // do we already have a replica?
491
            try {
492
                localSystemMetadata = 
493
                    HazelcastService.getInstance().getSystemMetadataMap().get(pid);
494
                object = getReplica(thisNodeSession, pid);
495
                
496
            } catch (RuntimeException e) {
497
                String msg = "An error occurred getting system metadata for " +
498
                    pid.getValue();
499
                failure = new ServiceFailure("2151", msg);
500
                setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
501
                logMetacat.error(msg);
502
                return true;
503

    
504
            }
505

    
506
            // no local replica, get a replica
507
            if (localSystemMetadata == null) {
508
                // session should be null to use the default certificate
509
                // location set in the Certificate manager
510
                object = mn.getReplica(thisNodeSession, pid);
511
                logMetacat.info("MNodeService.replicate() called for identifier "
512
                                + pid.getValue());
513

    
514
            }
515

    
516
        } catch (InvalidToken e) {            
517
            String msg = "Could not retrieve object to replicate (InvalidToken): "+ e.getMessage();
518
            failure = new ServiceFailure("2151", msg);
519
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
520
            logMetacat.error(msg);
521
            throw new ServiceFailure("2151", msg);
522

    
523
        } catch (NotFound e) {
524
            String msg = "Could not retrieve object to replicate (NotFound): "+ e.getMessage();
525
            failure = new ServiceFailure("2151", msg);
526
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
527
            logMetacat.error(msg);
528
            throw new ServiceFailure("2151", msg);
529

    
530
        }
531

    
532
        // verify checksum on the object, if supported
533
        if (object.markSupported()) {
534
            Checksum givenChecksum = sysmeta.getChecksum();
535
            Checksum computedChecksum = null;
536
            try {
537
                computedChecksum = ChecksumUtil.checksum(object,
538
                        givenChecksum.getAlgorithm());
539
                object.reset();
540
            } catch (Exception e) {
541
                String msg = "Error computing checksum on replica: "
542
                        + e.getMessage();
543
                ServiceFailure sf = new ServiceFailure("2151", msg);
544
                sf.initCause(e);
545
                throw sf;
546
            }
547
            if (!givenChecksum.getValue().equals(computedChecksum)) {
548
                throw new ServiceFailure("2151",
549
                        "Computed checksum does not match declared checksum");
550
            }
551
        }
552

    
553
        // add it to local store
554
        Identifier retPid;
555
        try {
556
            // skip the MN.create -- this mutates the system metadata and we
557
            // dont want it to
558
            retPid = super.create(session, pid, object, sysmeta);
559
            result = (retPid.getValue().equals(pid.getValue()));
560
            
561
        } catch (InvalidToken e) {
562
            String msg = "Could not save object to local store (InvalidToken): " + e.getMessage();
563
            failure = new ServiceFailure("2151", msg);
564
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
565
            logMetacat.error(msg);
566
            throw new ServiceFailure("2151", msg);
567
        
568
        } catch (IdentifierNotUnique e) {
569
            String msg = "Could not save object to local store (IdentifierNotUnique): " + e.getMessage();
570
            failure = new ServiceFailure("2151", msg);
571
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
572
            logMetacat.error(msg);
573
            throw new ServiceFailure("2151", msg);
574
        
575
        } catch (InvalidSystemMetadata e) {
576
            String msg = "Could not save object to local store (InvalidSystemMetadata): " + e.getMessage();
577
            failure = new ServiceFailure("2151", msg);
578
            setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.FAILED, failure);
579
            logMetacat.error(msg);
580
            throw new ServiceFailure("2151", msg);
581
            
582
        }
583

    
584
        // finish by setting the replication status
585
        setReplicationStatus(thisNodeSession, pid, nodeId, ReplicationStatus.COMPLETED, null);
586
        return result;
587

    
588
    }
589

    
590
    /**
591
     * Return the object identified by the given object identifier
592
     * 
593
     * @param session - the Session object containing the credentials for the Subject
594
     * @param pid - the object identifier for the given object
595
     * 
596
     * @return inputStream - the input stream of the given object
597
     * 
598
     * @throws InvalidToken
599
     * @throws ServiceFailure
600
     * @throws NotAuthorized
601
     * @throws InvalidRequest
602
     * @throws NotImplemented
603
     */
604
    @Override
605
    public InputStream get(Session session, Identifier pid) 
606
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, NotImplemented {
607

    
608
        return super.get(session, pid);
609

    
610
    }
611

    
612
    /**
613
     * Returns a Checksum for the specified object using an accepted hashing algorithm
614
     * 
615
     * @param session - the Session object containing the credentials for the Subject
616
     * @param pid - the object identifier for the given object
617
     * @param algorithm -  the name of an algorithm that will be used to compute 
618
     *                     a checksum of the bytes of the object
619
     * 
620
     * @return checksum - the checksum of the given object
621
     * 
622
     * @throws InvalidToken
623
     * @throws ServiceFailure
624
     * @throws NotAuthorized
625
     * @throws NotFound
626
     * @throws InvalidRequest
627
     * @throws NotImplemented
628
     */
629
    @Override
630
    public Checksum getChecksum(Session session, Identifier pid, String algorithm) 
631
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
632
        InvalidRequest, NotImplemented {
633

    
634
        Checksum checksum = null;
635

    
636
        InputStream inputStream = get(session, pid);
637

    
638
        try {
639
            checksum = ChecksumUtil.checksum(inputStream, algorithm);
640

    
641
        } catch (NoSuchAlgorithmException e) {
642
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
643
                    + e.getMessage());
644
        } catch (IOException e) {
645
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned due to an internal error: "
646
                    + e.getMessage());
647
        }
648

    
649
        if (checksum == null) {
650
            throw new ServiceFailure("1410", "The checksum for the object specified by " + pid.getValue() + "could not be returned.");
651
        }
652

    
653
        return checksum;
654
    }
655

    
656
    /**
657
     * Return the system metadata for a given object
658
     * 
659
     * @param session - the Session object containing the credentials for the Subject
660
     * @param pid - the object identifier for the given object
661
     * 
662
     * @return inputStream - the input stream of the given system metadata object
663
     * 
664
     * @throws InvalidToken
665
     * @throws ServiceFailure
666
     * @throws NotAuthorized
667
     * @throws NotFound
668
     * @throws InvalidRequest
669
     * @throws NotImplemented
670
     */
671
    @Override
672
    public SystemMetadata getSystemMetadata(Session session, Identifier pid) 
673
        throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
674
        NotImplemented {
675

    
676
        return super.getSystemMetadata(session, pid);
677
    }
678

    
679
    /**
680
     * Retrieve the list of objects present on the MN that match the calling parameters
681
     * 
682
     * @param session - the Session object containing the credentials for the Subject
683
     * @param startTime - Specifies the beginning of the time range from which 
684
     *                    to return object (>=)
685
     * @param endTime - Specifies the beginning of the time range from which 
686
     *                  to return object (>=)
687
     * @param objectFormat - Restrict results to the specified object format
688
     * @param replicaStatus - Indicates if replicated objects should be returned in the list
689
     * @param start - The zero-based index of the first value, relative to the 
690
     *                first record of the resultset that matches the parameters.
691
     * @param count - The maximum number of entries that should be returned in 
692
     *                the response. The Member Node may return less entries 
693
     *                than specified in this value.
694
     * 
695
     * @return objectList - the list of objects matching the criteria
696
     * 
697
     * @throws InvalidToken
698
     * @throws ServiceFailure
699
     * @throws NotAuthorized
700
     * @throws InvalidRequest
701
     * @throws NotImplemented
702
     */
703
    @Override
704
    public ObjectList listObjects(Session session, Date startTime, Date endTime, ObjectFormatIdentifier objectFormatId, Boolean replicaStatus, Integer start,
705
            Integer count) throws NotAuthorized, InvalidRequest, NotImplemented, ServiceFailure, InvalidToken {
706

    
707
        ObjectList objectList = null;
708

    
709
        try {
710
            objectList = IdentifierManager.getInstance().querySystemMetadata(startTime, endTime, objectFormatId, replicaStatus, start, count);
711
        } catch (Exception e) {
712
            throw new ServiceFailure("1580", "Error querying system metadata: " + e.getMessage());
713
        }
714

    
715
        return objectList;
716
    }
717

    
718
    /**
719
     * Return a description of the node's capabilities and services.
720
     * 
721
     * @return node - the technical capabilities of the Member Node
722
     * 
723
     * @throws ServiceFailure
724
     * @throws NotAuthorized
725
     * @throws InvalidRequest
726
     * @throws NotImplemented
727
     */
728
    @Override
729
    public Node getCapabilities() 
730
        throws NotImplemented, ServiceFailure {
731

    
732
        String nodeName = null;
733
        String nodeId = null;
734
        String subject = null;
735
        String nodeDesc = null;
736
        String nodeTypeString = null;
737
        NodeType nodeType = null;
738
        String mnCoreServiceVersion = null;
739
        String mnReadServiceVersion = null;
740
        String mnAuthorizationServiceVersion = null;
741
        String mnStorageServiceVersion = null;
742
        String mnReplicationServiceVersion = null;
743

    
744
        boolean nodeSynchronize = false;
745
        boolean nodeReplicate = false;
746
        boolean mnCoreServiceAvailable = false;
747
        boolean mnReadServiceAvailable = false;
748
        boolean mnAuthorizationServiceAvailable = false;
749
        boolean mnStorageServiceAvailable = false;
750
        boolean mnReplicationServiceAvailable = false;
751

    
752
        try {
753
            // get the properties of the node based on configuration information
754
            nodeName = PropertyService.getProperty("dataone.nodeName");
755
            nodeId = PropertyService.getProperty("dataone.memberNodeId");
756
            subject = PropertyService.getProperty("dataone.subject");
757
            nodeDesc = PropertyService.getProperty("dataone.nodeDescription");
758
            nodeTypeString = PropertyService.getProperty("dataone.nodeType");
759
            nodeType = NodeType.convert(nodeTypeString);
760
            nodeSynchronize = new Boolean(PropertyService.getProperty("dataone.nodeSynchronize")).booleanValue();
761
            nodeReplicate = new Boolean(PropertyService.getProperty("dataone.nodeReplicate")).booleanValue();
762

    
763
            mnCoreServiceVersion = PropertyService.getProperty("dataone.mnCore.serviceVersion");
764
            mnReadServiceVersion = PropertyService.getProperty("dataone.mnRead.serviceVersion");
765
            mnAuthorizationServiceVersion = PropertyService.getProperty("dataone.mnAuthorization.serviceVersion");
766
            mnStorageServiceVersion = PropertyService.getProperty("dataone.mnStorage.serviceVersion");
767
            mnReplicationServiceVersion = PropertyService.getProperty("dataone.mnReplication.serviceVersion");
768

    
769
            mnCoreServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnCore.serviceAvailable")).booleanValue();
770
            mnReadServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnRead.serviceAvailable")).booleanValue();
771
            mnAuthorizationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnAuthorization.serviceAvailable")).booleanValue();
772
            mnStorageServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnStorage.serviceAvailable")).booleanValue();
773
            mnReplicationServiceAvailable = new Boolean(PropertyService.getProperty("dataone.mnReplication.serviceAvailable")).booleanValue();
774

    
775
            // Set the properties of the node based on configuration information and
776
            // calls to current status methods
777
            String serviceName = SystemUtil.getContextURL() + "/" + PropertyService.getProperty("dataone.serviceName");
778
            Node node = new Node();
779
            node.setBaseURL(serviceName + "/" + nodeTypeString);
780
            node.setDescription(nodeDesc);
781

    
782
            // set the node's health information
783
            node.setState(NodeState.UP);
784
            
785
            // set the ping response to the current value
786
            Ping canPing = new Ping();
787
            canPing.setSuccess(false);
788
            try {
789
            	Date pingDate = ping();
790
                canPing.setSuccess(pingDate != null);
791
            } catch (BaseException e) {
792
                e.printStackTrace();
793
                // guess it can't be pinged
794
            }
795
            
796
            node.setPing(canPing);
797

    
798
            NodeReference identifier = new NodeReference();
799
            identifier.setValue(nodeId);
800
            node.setIdentifier(identifier);
801
            Subject s = new Subject();
802
            s.setValue(subject);
803
            node.addSubject(s);
804
            node.setName(nodeName);
805
            node.setReplicate(nodeReplicate);
806
            node.setSynchronize(nodeSynchronize);
807

    
808
            // services: MNAuthorization, MNCore, MNRead, MNReplication, MNStorage
809
            Services services = new Services();
810

    
811
            Service sMNCore = new Service();
812
            sMNCore.setName("MNCore");
813
            sMNCore.setVersion(mnCoreServiceVersion);
814
            sMNCore.setAvailable(mnCoreServiceAvailable);
815

    
816
            Service sMNRead = new Service();
817
            sMNRead.setName("MNRead");
818
            sMNRead.setVersion(mnReadServiceVersion);
819
            sMNRead.setAvailable(mnReadServiceAvailable);
820

    
821
            Service sMNAuthorization = new Service();
822
            sMNAuthorization.setName("MNAuthorization");
823
            sMNAuthorization.setVersion(mnAuthorizationServiceVersion);
824
            sMNAuthorization.setAvailable(mnAuthorizationServiceAvailable);
825

    
826
            Service sMNStorage = new Service();
827
            sMNStorage.setName("MNStorage");
828
            sMNStorage.setVersion(mnStorageServiceVersion);
829
            sMNStorage.setAvailable(mnStorageServiceAvailable);
830

    
831
            Service sMNReplication = new Service();
832
            sMNReplication.setName("MNReplication");
833
            sMNReplication.setVersion(mnReplicationServiceVersion);
834
            sMNReplication.setAvailable(mnReplicationServiceAvailable);
835

    
836
            services.addService(sMNRead);
837
            services.addService(sMNCore);
838
            services.addService(sMNAuthorization);
839
            services.addService(sMNStorage);
840
            services.addService(sMNReplication);
841
            node.setServices(services);
842

    
843
            // Set the schedule for synchronization
844
            Synchronization synchronization = new Synchronization();
845
            Schedule schedule = new Schedule();
846
            Date now = new Date();
847
            schedule.setYear(PropertyService.getProperty("dataone.nodeSynchronization.schedule.year"));
848
            schedule.setMon(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mon"));
849
            schedule.setMday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.mday"));
850
            schedule.setWday(PropertyService.getProperty("dataone.nodeSynchronization.schedule.wday"));
851
            schedule.setHour(PropertyService.getProperty("dataone.nodeSynchronization.schedule.hour"));
852
            schedule.setMin(PropertyService.getProperty("dataone.nodeSynchronization.schedule.min"));
853
            schedule.setSec(PropertyService.getProperty("dataone.nodeSynchronization.schedule.sec"));
854
            synchronization.setSchedule(schedule);
855
            synchronization.setLastHarvested(now);
856
            synchronization.setLastCompleteHarvest(now);
857
            node.setSynchronization(synchronization);
858

    
859
            node.setType(nodeType);
860
            return node;
861

    
862
        } catch (PropertyNotFoundException pnfe) {
863
            String msg = "MNodeService.getCapabilities(): " + "property not found: " + pnfe.getMessage();
864
            logMetacat.error(msg);
865
            throw new ServiceFailure("2162", msg);
866
        }
867
    }
868

    
869
    /**
870
     * Returns the number of operations that have been serviced by the node 
871
     * over time periods of one and 24 hours.
872
     * 
873
     * @param session - the Session object containing the credentials for the Subject
874
     * @param period - An ISO8601 compatible DateTime range specifying the time 
875
     *                 range for which to return operation statistics.
876
     * @param requestor - Limit to operations performed by given requestor identity.
877
     * @param event -  Enumerated value indicating the type of event being examined
878
     * @param format - Limit to events involving objects of the specified format
879
     * 
880
     * @return the desired log records
881
     * 
882
     * @throws InvalidToken
883
     * @throws ServiceFailure
884
     * @throws NotAuthorized
885
     * @throws InvalidRequest
886
     * @throws NotImplemented
887
     */
888
    public MonitorList getOperationStatistics(Session session, Date startTime, 
889
        Date endTime, Subject requestor, Event event, ObjectFormatIdentifier formatId)
890
        throws NotImplemented, ServiceFailure, NotAuthorized, InsufficientResources, UnsupportedType {
891

    
892
        MonitorList monitorList = new MonitorList();
893

    
894
        try {
895

    
896
            // get log records first
897
            Log logs = getLogRecords(session, startTime, endTime, event, 0, null);
898

    
899
            // TODO: aggregate by day or hour -- needs clarification
900
            int count = 1;
901
            for (LogEntry logEntry : logs.getLogEntryList()) {
902
                Identifier pid = logEntry.getIdentifier();
903
                Date logDate = logEntry.getDateLogged();
904
                // if we are filtering by format
905
                if (formatId != null) {
906
                    SystemMetadata sysmeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
907
                    if (!sysmeta.getFormatId().getValue().equals(formatId.getValue())) {
908
                        // does not match
909
                        continue;
910
                    }
911
                }
912
                MonitorInfo item = new MonitorInfo();
913
                item.setCount(count);
914
                item.setDate(new java.sql.Date(logDate.getTime()));
915
                monitorList.addMonitorInfo(item);
916

    
917
            }
918
        } catch (Exception e) {
919
            e.printStackTrace();
920
            throw new ServiceFailure("2081", "Could not retrieve statistics: " + e.getMessage());
921
        }
922

    
923
        return monitorList;
924

    
925
    }
926

    
927
    /**
928
     * A callback method used by a CN to indicate to a MN that it cannot 
929
     * complete synchronization of the science metadata identified by pid.  Log
930
     * the event in the metacat event log.
931
     * 
932
     * @param session
933
     * @param syncFailed
934
     * 
935
     * @throws ServiceFailure
936
     * @throws NotAuthorized
937
     * @throws NotImplemented
938
     */
939
    @Override
940
    public void synchronizationFailed(Session session, SynchronizationFailed syncFailed) 
941
        throws NotImplemented, ServiceFailure, NotAuthorized {
942

    
943
        String localId;
944

    
945
        try {
946
            localId = IdentifierManager.getInstance().getLocalId(syncFailed.getPid());
947
        } catch (McdbDocNotFoundException e) {
948
            throw new ServiceFailure("2161", "The identifier specified by " + syncFailed.getPid() + " was not found on this node.");
949

    
950
        }
951
        // TODO: update the CN URL below when the CNRead.SynchronizationFailed
952
        // method is changed to include the URL as a parameter
953
        logMetacat.debug("Synchronization for the object identified by " + syncFailed.getPid() + " failed from " + syncFailed.getNodeId()
954
                + " Logging the event to the Metacat EventLog as a 'syncFailed' event.");
955
        // TODO: use the event type enum when the SYNCHRONIZATION_FAILED event is added
956
        String principal = Constants.SUBJECT_PUBLIC;
957
        if (session != null && session.getSubject() != null) {
958
          principal = session.getSubject().getValue();
959
        }
960
        try {
961
          EventLog.getInstance().log(request.getRemoteAddr(), request.getHeader("User-Agent"), principal, localId, "synchronization_failed");
962
        } catch (Exception e) {
963
            throw new ServiceFailure("2161", "Could not log the error for: " + syncFailed.getPid());
964
    }
965
        //EventLog.getInstance().log("CN URL WILL GO HERE", 
966
        //  session.getSubject().getValue(), localId, Event.SYNCHRONIZATION_FAILED);
967

    
968
    }
969

    
970
    /**
971
     * Essentially a get() but with different logging behavior
972
     */
973
    @Override
974
    public InputStream getReplica(Session session, Identifier pid) 
975
        throws NotAuthorized, NotImplemented, ServiceFailure, InvalidToken {
976

    
977
        logMetacat.info("MNodeService.getReplica() called.");
978

    
979
        // cannot be called by public
980
        if (session == null) {
981
        	throw new InvalidToken("2183", "No session was provided.");
982
        }
983
        
984
        logMetacat.info("MNodeService.getReplica() called with parameters: \n" +
985
             "\tSession.Subject      = " + session.getSubject().getValue() + "\n" +
986
             "\tIdentifier           = " + pid.getValue());
987

    
988
        InputStream inputStream = null; // bytes to be returned
989
        handler = new MetacatHandler(new Timer());
990
        boolean allowed = false;
991
        String localId; // the metacat docid for the pid
992

    
993
        // get the local docid from Metacat
994
        try {
995
            localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
996
        } catch (McdbDocNotFoundException e) {
997
            throw new ServiceFailure("2181", "The object specified by " + 
998
                    pid.getValue() + " does not exist at this node.");
999
            
1000
        }
1001

    
1002
        Subject targetNodeSubject = session.getSubject();
1003

    
1004
        // check for authorization to replicate, null session to act as this source MN
1005
        try {
1006
            allowed = D1Client.getCN().isNodeAuthorized(null, targetNodeSubject, pid);
1007
        } catch (InvalidToken e1) {
1008
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1009
                + e1.getMessage());
1010
            
1011
        } catch (NotFound e1) {
1012
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1013
                    + e1.getMessage());
1014

    
1015
        } catch (InvalidRequest e1) {
1016
            throw new ServiceFailure("2181", "Could not determine if node is authorized: " 
1017
                    + e1.getMessage());
1018

    
1019
        }
1020

    
1021
        logMetacat.info("Called D1Client.isNodeAuthorized(). Allowed = " + allowed +
1022
            " for identifier " + pid.getValue());
1023

    
1024
        // if the person is authorized, perform the read
1025
        if (allowed) {
1026
            try {
1027
                inputStream = handler.read(localId);
1028
            } catch (Exception e) {
1029
                throw new ServiceFailure("1020", "The object specified by " + 
1030
                    pid.getValue() + "could not be returned due to error: " + e.getMessage());
1031
            }
1032
        }
1033

    
1034
        // if we fail to set the input stream
1035
        if (inputStream == null) {
1036
            throw new ServiceFailure("2181", "The object specified by " + 
1037
                pid.getValue() + "does not exist at this node.");
1038
        }
1039

    
1040
        // log the replica event
1041
        String principal = null;
1042
        if (session.getSubject() != null) {
1043
            principal = session.getSubject().getValue();
1044
        }
1045
        EventLog.getInstance().log(request.getRemoteAddr(), 
1046
            request.getHeader("User-Agent"), principal, localId, "replicate");
1047

    
1048
        return inputStream;
1049
    }
1050

    
1051
    /**
1052
     * A method to notify the Member Node that the authoritative copy of 
1053
     * system metadata on the Coordinating Nodes has changed.
1054
     * 
1055
     * @param session   Session information that contains the identity of the 
1056
     *                  calling user as retrieved from the X.509 certificate 
1057
     *                  which must be traceable to the CILogon service.
1058
     * @param serialVersion   The serialVersion of the system metadata
1059
     * @param dateSysMetaLastModified  The time stamp for when the system metadata was changed
1060
     * @throws NotImplemented
1061
     * @throws ServiceFailure
1062
     * @throws NotAuthorized
1063
     * @throws InvalidRequest
1064
     * @throws InvalidToken
1065
     */
1066
    public void systemMetadataChanged(Session session, Identifier pid,
1067
        long serialVersion, Date dateSysMetaLastModified) 
1068
        throws NotImplemented, ServiceFailure, NotAuthorized, InvalidRequest,
1069
        InvalidToken {
1070
        
1071
        SystemMetadata currentLocalSysMeta = null;
1072
        SystemMetadata newSysMeta = null;
1073
        CNode cn = D1Client.getCN();
1074
        NodeList nodeList = null;
1075
        Subject callingSubject = null;
1076
        boolean allowed = false;
1077
        
1078
        // are we allowed to call this?
1079
        callingSubject = session.getSubject();
1080
        nodeList = cn.listNodes();
1081
        
1082
        for(Node node : nodeList.getNodeList()) {
1083
            // must be a CN
1084
            if ( node.getType().equals(NodeType.CN)) {
1085
               List<Subject> subjectList = node.getSubjectList();
1086
               // the calling subject must be in the subject list
1087
               if ( subjectList.contains(callingSubject)) {
1088
                   allowed = true;
1089
                   
1090
               }
1091
               
1092
            }
1093
        }
1094
        
1095
        if (!allowed ) {
1096
            String msg = "The subject identified by " + callingSubject.getValue() +
1097
              " is not authorized to call this service.";
1098
            throw new NotAuthorized("1331", msg);
1099
            
1100
        }
1101
        
1102
        // compare what we have locally to what is sent in the change notification
1103
        try {
1104
            currentLocalSysMeta = HazelcastService.getInstance().getSystemMetadataMap().get(pid);
1105
             
1106
        } catch (RuntimeException e) {
1107
            String msg = "SystemMetadata for pid " + pid.getValue() +
1108
              " couldn't be updated because it couldn't be found locally: " +
1109
              e.getMessage();
1110
            logMetacat.error(msg);
1111
            ServiceFailure sf = new ServiceFailure("1333", msg);
1112
            sf.initCause(e);
1113
            throw sf; 
1114
        }
1115
        
1116
        if (currentLocalSysMeta.getSerialVersion().longValue() < serialVersion ) {
1117
            try {
1118
                newSysMeta = cn.getSystemMetadata(null, pid);
1119
            } catch (NotFound e) {
1120
                // huh? you just said you had it
1121
            	String msg = "On updating the local copy of system metadata " + 
1122
                "for pid " + pid.getValue() +", the CN reports it is not found." +
1123
                " The error message was: " + e.getMessage();
1124
                logMetacat.error(msg);
1125
                ServiceFailure sf = new ServiceFailure("1333", msg);
1126
                sf.initCause(e);
1127
                throw sf;
1128
            }
1129
            
1130
            // update the local copy of system metadata for the pid
1131
            try {
1132
                HazelcastService.getInstance().getSystemMetadataMap().put(newSysMeta.getIdentifier(), newSysMeta);
1133
                logMetacat.info("Updated local copy of system metadata for pid " +
1134
                    pid.getValue() + " after change notification from the CN.");
1135
                
1136
            } catch (RuntimeException e) {
1137
                String msg = "SystemMetadata for pid " + pid.getValue() +
1138
                  " couldn't be updated: " +
1139
                  e.getMessage();
1140
                logMetacat.error(msg);
1141
                ServiceFailure sf = new ServiceFailure("1333", msg);
1142
                sf.initCause(e);
1143
                throw sf;
1144
            }
1145
        }
1146
        
1147
    }
1148
    
1149
    /*
1150
     * Set the replication status for the object on the Coordinating Node
1151
     * 
1152
     * @param session - the session for the this target node
1153
     * @param pid - the identifier of the object being updated
1154
     * @param nodeId - the identifier of this target node
1155
     * @param status - the replication status to set
1156
     * @param failure - the exception to include, if any
1157
     */
1158
    private void setReplicationStatus(Session session, Identifier pid, 
1159
        NodeReference nodeId, ReplicationStatus status, BaseException failure) 
1160
        throws ServiceFailure, NotImplemented, NotAuthorized, 
1161
        InvalidRequest {
1162
        
1163
        // call the CN as the MN to set the replication status
1164
        try {
1165
            this.cn = D1Client.getCN();
1166
            this.cn.setReplicationStatus(session, pid, nodeId,
1167
                    status, failure);
1168
            
1169
        } catch (InvalidToken e) {
1170
            throw new ServiceFailure("2151",
1171
                    "Could not set the replication status on the CN (InvalidToken): " + 
1172
                    e.getMessage());
1173
            
1174
        } catch (NotFound e) {
1175
            throw new ServiceFailure("2151",
1176
                    "Could not set the replication status on the CN (NotFound): " + 
1177
                    e.getMessage());
1178
            
1179
        } catch (VersionMismatch e) {
1180
            throw new ServiceFailure("2151",
1181
                    "Could not set the replication status on the CN (VersionMismatch): " + 
1182
                    e.getMessage());
1183
            
1184
        }
1185

    
1186

    
1187
    }
1188
    
1189
}
(3-3/5)