/** * GET implementation returns the contents of the Item as a package. The query arg "package" must * be specified. * * @throws SQLException the SQL exception * @throws AuthorizeException the authorize exception * @throws ServletException the servlet exception * @throws IOException Signals that an I/O exception has occurred. * @throws DAVStatusException the DAV status exception */ @Override protected void get() throws SQLException, AuthorizeException, IOException, DAVStatusException { // Check for overall read permission on Item, because nothing else will AuthorizeManager.authorizeAction(this.context, this.item, Constants.READ); String packageType = this.request.getParameter("package"); Bundle[] original = this.item.getBundles("ORIGINAL"); int bsid; if (packageType == null) { packageType = "default"; } PackageDisseminator dip = (PackageDisseminator) PluginManager.getNamedPlugin(PackageDisseminator.class, packageType); if (dip == null) { throw new DAVStatusException( HttpServletResponse.SC_BAD_REQUEST, "Cannot find a disseminate plugin for package=" + packageType); } else { try { PackageParameters pparams = PackageParameters.create(this.request); this.response.setContentType(dip.getMIMEType(pparams)); dip.disseminate(this.context, this.item, pparams, this.response.getOutputStream()); } catch (CrosswalkException pe) { throw new DAVStatusException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed in crosswalk of metadata: " + pe.toString()); } catch (PackageException pe) { pe.log(log); throw new DAVStatusException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, pe.toString()); } } }
public Element disseminateElement(DSpaceObject dso) throws CrosswalkException, IOException, SQLException, AuthorizeException { if (dso.getType() != Constants.ITEM) { throw new CrosswalkObjectNotSupported( "METSDisseminationCrosswalk can only crosswalk an Item."); } Item item = (Item) dso; PackageDisseminator dip = (PackageDisseminator) PluginManager.getNamedPlugin(PackageDisseminator.class, METS_PACKAGER_PLUGIN); if (dip == null) { throw new CrosswalkInternalException( "Cannot find a disseminate plugin for package=" + METS_PACKAGER_PLUGIN); } try { // Set the manifestOnly=true param so we just get METS document PackageParameters pparams = new PackageParameters(); pparams.put("manifestOnly", "true"); // Create a temporary file to disseminate into String tempDirectory = ConfigurationManager.getProperty("upload.temp.dir"); File tempFile = File.createTempFile("METSDissemination" + item.hashCode(), null, new File(tempDirectory)); tempFile.deleteOnExit(); // Disseminate METS to temp file Context context = new Context(); dip.disseminate(context, item, pparams, tempFile); try { SAXBuilder builder = new SAXBuilder(); Document metsDocument = builder.build(tempFile); return metsDocument.getRootElement(); } catch (JDOMException je) { throw new MetadataValidationException( "Error parsing METS (see wrapped error message for more details) ", je); } } catch (PackageException pe) { throw new CrosswalkInternalException( "Failed making METS manifest in packager (see wrapped error message for more details) ", pe); } }
/** * Creates new parameters object with the parameter values from a servlet request object. * * @param request - the request from which to take the values * @return new parameters object. */ public static PackageParameters create(ServletRequest request) { PackageParameters result = new PackageParameters(); Enumeration pe = request.getParameterNames(); while (pe.hasMoreElements()) { String name = (String) pe.nextElement(); String v[] = request.getParameterValues(name); if (v.length == 0) result.setProperty(name, ""); else if (v.length == 1) result.setProperty(name, v[0]); else { StringBuffer sb = new StringBuffer(); for (int i = 0; i < v.length; ++i) { if (i > 0) sb.append(SEPARATOR); sb.append(v[i]); } result.setProperty(name, sb.toString()); } } return result; }
/** Write out a METS manifest. Mostly lifted from Rob Tansley's METS exporter. */ private void writeManifest(Context context, Item item, PackageParameters params, OutputStream out) throws PackageValidationException, CrosswalkException, AuthorizeException, SQLException, IOException { try { // Create the METS file Mets mets = new Mets(); // Top-level stuff mets.setID(gensym("mets")); mets.setOBJID("hdl:" + item.getHandle()); mets.setLABEL("DSpace Item"); mets.setPROFILE(getProfile()); // MetsHdr MetsHdr metsHdr = new MetsHdr(); metsHdr.setCREATEDATE(new Date()); // FIXME: CREATEDATE is now: // maybe should be item create // date? // Agent Agent agent = new Agent(); agent.setROLE(Role.CUSTODIAN); agent.setTYPE(Type.ORGANIZATION); Name name = new Name(); name.getContent().add(new PCData(ConfigurationManager.getProperty("dspace.name"))); agent.getContent().add(name); metsHdr.getContent().add(agent); mets.getContent().add(metsHdr); // add DMD sections // Each type element MAY be either just a MODS-and-crosswalk name, OR // a combination "MODS-name:crosswalk-name" (e.g. "DC:qDC"). String dmdTypes[] = getDmdTypes(params); // record of ID of each dmdsec to make DMDID in structmap. String dmdGroup = gensym("dmd_group"); String dmdId[] = new String[dmdTypes.length]; for (int i = 0; i < dmdTypes.length; ++i) { dmdId[i] = gensym("dmd"); XmlData xmlData = new XmlData(); String xwalkName, metsName; String parts[] = dmdTypes[i].split(":", 2); if (parts.length > 1) { metsName = parts[0]; xwalkName = parts[1]; } else xwalkName = metsName = dmdTypes[i]; DisseminationCrosswalk xwalk = (DisseminationCrosswalk) PluginManager.getNamedPlugin(DisseminationCrosswalk.class, xwalkName); if (xwalk == null) throw new PackageValidationException("Cannot find " + dmdTypes[i] + " crosswalk plugin!"); else crosswalkToMets(xwalk, item, xmlData); DmdSec dmdSec = new DmdSec(); dmdSec.setID(dmdId[i]); dmdSec.setGROUPID(dmdGroup); MdWrap mdWrap = new MdWrap(); setMdType(mdWrap, metsName); mdWrap.getContent().add(xmlData); dmdSec.getContent().add(mdWrap); mets.getContent().add(dmdSec); } // Only add license AMD section if there are any licenses. // Catch authorization failures accessing license bitstreams // only if we are skipping unauthorized bitstreams. String licenseID = null; try { AmdSec amdSec = new AmdSec(); addRightsMd(context, item, amdSec); if (amdSec.getContent().size() > 0) { licenseID = gensym("license"); amdSec.setID(licenseID); mets.getContent().add(amdSec); } } catch (AuthorizeException e) { String unauth = (params == null) ? null : params.getProperty("unauthorized"); if (!(unauth != null && unauth.equalsIgnoreCase("skip"))) throw e; else log.warn("Skipping license metadata because of access failure: " + e.toString()); } // FIXME: History data???? Nooooo!!!! // fileSec - all non-metadata bundles go into fileGrp, // and each bitstream therein into a file. // Create the bitstream-level techMd and div's for structmap // at the same time so we can connec the IDREFs to IDs. FileSec fileSec = new FileSec(); String techMdType = getTechMdType(params); String parts[] = techMdType.split(":", 2); String xwalkName, metsName; if (parts.length > 1) { metsName = parts[0]; xwalkName = parts[1]; } else xwalkName = metsName = techMdType; DisseminationCrosswalk xwalk = (DisseminationCrosswalk) PluginManager.getNamedPlugin(DisseminationCrosswalk.class, xwalkName); if (xwalk == null) throw new PackageValidationException("Cannot find " + xwalkName + " crosswalk plugin!"); // log the primary bitstream for structmap String primaryBitstreamFileID = null; // accumulate content DIV items to put in structMap later. List contentDivs = new ArrayList(); // how to handle unauthorized bundle/bitstream: String unauth = (params == null) ? null : params.getProperty("unauthorized"); Bundle[] bundles = item.getBundles(); for (int i = 0; i < bundles.length; i++) { if (PackageUtils.isMetaInfoBundle(bundles[i])) continue; // unauthorized bundle? // NOTE: This must match the logic in disseminate() if (!AuthorizeManager.authorizeActionBoolean(context, bundles[i], Constants.READ)) { if (unauth != null && (unauth.equalsIgnoreCase("skip"))) continue; else throw new AuthorizeException( "Not authorized to read Bundle named \"" + bundles[i].getName() + "\""); } Bitstream[] bitstreams = bundles[i].getBitstreams(); // Create a fileGrp FileGrp fileGrp = new FileGrp(); // Bundle name for USE attribute String bName = bundles[i].getName(); if ((bName != null) && !bName.equals("")) fileGrp.setUSE(bundleToFileGrp(bName)); // watch for primary bitstream int primaryBitstreamID = -1; boolean isContentBundle = false; if ((bName != null) && bName.equals("ORIGINAL")) { isContentBundle = true; primaryBitstreamID = bundles[i].getPrimaryBitstreamID(); } for (int bits = 0; bits < bitstreams.length; bits++) { // Check for authorization. Handle unauthorized // bitstreams to match the logic in disseminate(), // i.e. "unauth=zero" means include a 0-length bitstream, // "unauth=skip" means to ignore it (and exclude from // manifest). boolean auth = AuthorizeManager.authorizeActionBoolean(context, bitstreams[bits], Constants.READ); if (!auth) { if (unauth != null && unauth.equalsIgnoreCase("skip")) continue; else if (!(unauth != null && unauth.equalsIgnoreCase("zero"))) throw new AuthorizeException( "Not authorized to read Bitstream, SID=" + String.valueOf(bitstreams[bits].getSequenceID())); } String sid = String.valueOf(bitstreams[bits].getSequenceID()); edu.harvard.hul.ois.mets.File file = new edu.harvard.hul.ois.mets.File(); String xmlIDstart = "bitstream_"; String fileID = xmlIDstart + sid; file.setID(fileID); // log primary bitstream for later (structMap) if (bitstreams[bits].getID() == primaryBitstreamID) primaryBitstreamFileID = fileID; // if this is content, add to structmap too: if (isContentBundle) { Div div = new Div(); div.setID(gensym("div")); div.setTYPE("DSpace Content Bitstream"); Fptr fptr = new Fptr(); fptr.setFILEID(fileID); div.getContent().add(fptr); contentDivs.add(div); } file.setSEQ(bitstreams[bits].getSequenceID()); String groupID = "GROUP_" + xmlIDstart + sid; /* * If we're in THUMBNAIL or TEXT bundles, the bitstream is * extracted text or a thumbnail, so we use the name to work * out which bitstream to be in the same group as */ if ((bundles[i].getName() != null) && (bundles[i].getName().equals("THUMBNAIL") || bundles[i].getName().startsWith("TEXT"))) { // Try and find the original bitstream, and chuck the // derived bitstream in the same group Bitstream original = findOriginalBitstream(item, bitstreams[bits]); if (original != null) { groupID = "GROUP_" + xmlIDstart + original.getSequenceID(); } } file.setGROUPID(groupID); file.setMIMETYPE(bitstreams[bits].getFormat().getMIMEType()); // FIXME: CREATED: no date file.setSIZE(auth ? bitstreams[bits].getSize() : 0); // translate checksum and type to METS, if available. String csType = bitstreams[bits].getChecksumAlgorithm(); String cs = bitstreams[bits].getChecksum(); if (auth && cs != null && csType != null) { try { file.setCHECKSUMTYPE(Checksumtype.parse(csType)); file.setCHECKSUM(cs); } catch (MetsException e) { log.warn("Cannot set bitstream checksum type=" + csType + " in METS."); } } // FLocat: filename is MD5 checksum FLocat flocat = new FLocat(); flocat.setLOCTYPE(Loctype.URL); flocat.setXlinkHref(makeBitstreamName(bitstreams[bits])); // Make bitstream techMD metadata, add to file. String techID = "techMd_for_bitstream_" + bitstreams[bits].getSequenceID(); AmdSec fAmdSec = new AmdSec(); fAmdSec.setID(techID); TechMD techMd = new TechMD(); techMd.setID(gensym("tech")); MdWrap mdWrap = new MdWrap(); setMdType(mdWrap, metsName); XmlData xmlData = new XmlData(); mdWrap.getContent().add(xmlData); techMd.getContent().add(mdWrap); fAmdSec.getContent().add(techMd); mets.getContent().add(fAmdSec); crosswalkToMets(xwalk, bitstreams[bits], xmlData); file.setADMID(techID); // Add FLocat to File, and File to FileGrp file.getContent().add(flocat); fileGrp.getContent().add(file); } // Add fileGrp to fileSec fileSec.getContent().add(fileGrp); } // Add fileSec to document mets.getContent().add(fileSec); // Create simple structMap: initial div represents the Item, // and user-visible content bitstreams are in its child divs. StringBuffer dmdIds = new StringBuffer(); for (int i = 0; i < dmdId.length; ++i) dmdIds.append(" " + dmdId[i]); StructMap structMap = new StructMap(); structMap.setID(gensym("struct")); structMap.setTYPE("LOGICAL"); structMap.setLABEL("DSpace"); Div div0 = new Div(); div0.setID(gensym("div")); div0.setTYPE("DSpace Item"); div0.setDMDID(dmdIds.substring(1)); if (licenseID != null) div0.setADMID(licenseID); // if there is a primary bitstream, add FPTR to it. if (primaryBitstreamFileID != null) { Fptr fptr = new Fptr(); fptr.setFILEID(primaryBitstreamFileID); div0.getContent().add(fptr); } // add DIV for each content bitstream div0.getContent().addAll(contentDivs); structMap.getContent().add(div0); // Does subclass have something to add to structMap? addStructMap(context, item, params, mets); mets.getContent().add(structMap); mets.validate(new MetsValidator()); mets.write(new MetsWriter(out)); } catch (MetsException e) { // We don't pass up a MetsException, so callers don't need to // know the details of the METS toolkit // e.printStackTrace(); throw new PackageValidationException(e); } }
/** * Export the object (Item, Collection, or Community) to a package file on the indicated * OutputStream. Gets an exception of the object cannot be packaged or there is a failure creating * the package. * * @param context - DSpace context. * @param dso - DSpace object (item, collection, etc) * @param pkg - output stream on which to write package * @throws PackageException if package cannot be created or there is a fatal error in creating it. */ public void disseminate( Context context, DSpaceObject dso, PackageParameters params, OutputStream pkg) throws PackageValidationException, CrosswalkException, AuthorizeException, SQLException, IOException { if (dso.getType() == Constants.ITEM) { Item item = (Item) dso; long lmTime = item.getLastModified().getTime(); // how to handle unauthorized bundle/bitstream: String unauth = (params == null) ? null : params.getProperty("unauthorized"); if (params != null && params.getProperty("manifestOnly") != null) { extraFiles = null; writeManifest(context, item, params, pkg); } else { extraFiles = new HashMap(); ZipOutputStream zip = new ZipOutputStream(pkg); zip.setComment("METS archive created by DSpace METSDisseminationCrosswalk"); // write manifest first. ZipEntry me = new ZipEntry(MANIFEST_FILE); me.setTime(lmTime); zip.putNextEntry(me); writeManifest(context, item, params, zip); zip.closeEntry(); // copy extra (meta?) bitstreams into zip Iterator fi = extraFiles.keySet().iterator(); while (fi.hasNext()) { String fname = (String) fi.next(); ZipEntry ze = new ZipEntry(fname); ze.setTime(lmTime); zip.putNextEntry(ze); Utils.copy((InputStream) extraFiles.get(fname), zip); zip.closeEntry(); } // copy all non-meta bitstreams into zip Bundle bundles[] = item.getBundles(); for (int i = 0; i < bundles.length; i++) { if (!PackageUtils.isMetaInfoBundle(bundles[i])) { // unauthorized bundle? if (!AuthorizeManager.authorizeActionBoolean(context, bundles[i], Constants.READ)) { if (unauth != null && (unauth.equalsIgnoreCase("skip"))) { log.warn( "Skipping Bundle[\"" + bundles[i].getName() + "\"] because you are not authorized to read it."); continue; } else throw new AuthorizeException( "Not authorized to read Bundle named \"" + bundles[i].getName() + "\""); } Bitstream[] bitstreams = bundles[i].getBitstreams(); for (int k = 0; k < bitstreams.length; k++) { boolean auth = AuthorizeManager.authorizeActionBoolean(context, bitstreams[k], Constants.READ); if (auth || (unauth != null && unauth.equalsIgnoreCase("zero"))) { ZipEntry ze = new ZipEntry(makeBitstreamName(bitstreams[k])); ze.setTime(lmTime); ze.setSize(auth ? bitstreams[k].getSize() : 0); zip.putNextEntry(ze); if (auth) Utils.copy(bitstreams[k].retrieve(), zip); else log.warn( "Adding zero-length file for Bitstream, SID=" + String.valueOf(bitstreams[k].getSequenceID()) + ", not authorized for READ."); zip.closeEntry(); } else if (unauth != null && unauth.equalsIgnoreCase("skip")) { log.warn( "Skipping Bitstream, SID=" + String.valueOf(bitstreams[k].getSequenceID()) + ", not authorized for READ."); } else { throw new AuthorizeException( "Not authorized to read Bitstream, SID=" + String.valueOf(bitstreams[k].getSequenceID())); } } } } zip.close(); extraFiles = null; } } else throw new PackageValidationException("Can only disseminate an Item now."); }
public String getMIMEType(PackageParameters params) { return (params != null && params.getProperty("manifestOnly") != null) ? "text/xml" : "application/zip"; }
public static void main(String[] argv) throws Exception { Options options = new Options(); options.addOption("c", "collection", true, "destination collection(s) Handle (repeatable)"); options.addOption("e", "eperson", true, "email address of eperson doing importing"); options.addOption( "w", "install", false, "disable workflow; install immediately without going through collection's workflow"); options.addOption("t", "type", true, "package type or MIMEtype"); options.addOption( "o", "option", true, "Packager option to pass to plugin, \"name=value\" (repeatable)"); options.addOption( "d", "disseminate", false, "Disseminate package (output); default is to submit."); options.addOption("i", "item", true, "Handle of item to disseminate."); options.addOption("h", "help", false, "help"); CommandLineParser parser = new PosixParser(); CommandLine line = parser.parse(options, argv); String sourceFile = null; String eperson = null; String[] collections = null; boolean useWorkflow = true; String packageType = null; boolean submit = true; String itemHandle = null; PackageParameters pkgParams = new PackageParameters(); if (line.hasOption('h')) { HelpFormatter myhelp = new HelpFormatter(); myhelp.printHelp("Packager [options] package-file|-\n", options); System.out.println("\nAvailable Submission Package (SIP) types:"); String pn[] = PluginManager.getAllPluginNames(PackageIngester.class); for (int i = 0; i < pn.length; ++i) System.out.println(" " + pn[i]); System.out.println("\nAvailable Dissemination Package (DIP) types:"); pn = PluginManager.getAllPluginNames(PackageDisseminator.class); for (int i = 0; i < pn.length; ++i) System.out.println(" " + pn[i]); System.exit(0); } if (line.hasOption('w')) useWorkflow = false; if (line.hasOption('e')) eperson = line.getOptionValue('e'); if (line.hasOption('c')) collections = line.getOptionValues('c'); if (line.hasOption('t')) packageType = line.getOptionValue('t'); if (line.hasOption('i')) itemHandle = line.getOptionValue('i'); String files[] = line.getArgs(); if (files.length > 0) sourceFile = files[0]; if (line.hasOption('d')) submit = false; if (line.hasOption('o')) { String popt[] = line.getOptionValues('o'); for (int i = 0; i < popt.length; ++i) { String pair[] = popt[i].split("\\=", 2); if (pair.length == 2) pkgParams.addProperty(pair[0].trim(), pair[1].trim()); else if (pair.length == 1) pkgParams.addProperty(pair[0].trim(), ""); else System.err.println("Warning: Illegal package option format: \"" + popt[i] + "\""); } } // Sanity checks on arg list: required args if (sourceFile == null || eperson == null || packageType == null || (submit && collections == null)) { System.err.println("Error - missing a REQUIRED argument or option.\n"); HelpFormatter myhelp = new HelpFormatter(); myhelp.printHelp("PackageManager [options] package-file|-\n", options); System.exit(0); } // find the EPerson, assign to context Context context = new Context(); EPerson myEPerson = null; myEPerson = EPerson.findByEmail(context, eperson); if (myEPerson == null) usageError("Error, eperson cannot be found: " + eperson); context.setCurrentUser(myEPerson); if (submit) { // make sure we have an input file // GWaller 11/1/10 Disable piping of input in - we need to archive the package so it is // simpler to assume a file stream // rather than save the System.in bytes and re-read. if (sourceFile.equals("-")) { usageError( "Error, input piping not allowed. Specify a file name of a physical file to read"); } InputStream source = new FileInputStream(sourceFile); PackageIngester sip = (PackageIngester) PluginManager.getNamedPlugin(PackageIngester.class, packageType); if (sip == null) usageError("Error, Unknown package type: " + packageType); // find collections Collection[] mycollections = null; System.out.println("Destination collections:"); // validate each collection arg to see if it's a real collection mycollections = new Collection[collections.length]; for (int i = 0; i < collections.length; i++) { // sanity check: did handle resolve, and to a collection? DSpaceObject dso = HandleManager.resolveToObject(context, collections[i]); if (dso == null) throw new IllegalArgumentException( "Bad collection list -- " + "Cannot resolve collection handle \"" + collections[i] + "\""); else if (dso.getType() != Constants.COLLECTION) throw new IllegalArgumentException( "Bad collection list -- " + "Object at handle \"" + collections[i] + "\" is not a collection!"); mycollections[i] = (Collection) dso; System.out.println( (i == 0 ? " Owning " : " ") + " Collection: " + mycollections[i].getMetadata("name")); } try { // GWaller 26/08/09 Support array of collections WorkspaceItem wi = sip.ingest(context, mycollections, source, pkgParams, null); // GWaller 11/1/10 IssueID #157 Archive the package InputStream sourceCopy = new FileInputStream(sourceFile); Bundle archivedBundle = BundleUtils.getBundleByName(wi.getItem(), Constants.ARCHIVED_CONTENT_PACKAGE_BUNDLE); Bitstream bs = archivedBundle.createBitstream(sourceCopy); bs.setName(new File(sourceFile).getName()); bs.update(); archivedBundle.update(); if (useWorkflow) { String handle = null; // Check if workflow completes immediately, and // return Handle if so. WorkflowItem wfi = WorkflowManager.startWithoutNotify(context, wi); if (wfi.getState() == WorkflowManager.WFSTATE_ARCHIVE) { Item ni = wfi.getItem(); handle = HandleManager.findHandle(context, ni); } if (handle == null) System.out.println("Created Workflow item, ID=" + String.valueOf(wfi.getID())); else System.out.println("Created and installed item, handle=" + handle); } else { InstallItem.installItem(context, wi); System.out.println( "Created and installed item, handle=" + HandleManager.findHandle(context, wi.getItem())); } context.complete(); System.exit(0); } catch (Exception e) { // abort all operations context.abort(); e.printStackTrace(); System.out.println(e); System.exit(1); } } else { OutputStream dest = (sourceFile.equals("-")) ? (OutputStream) System.out : (OutputStream) (new FileOutputStream(sourceFile)); PackageDisseminator dip = (PackageDisseminator) PluginManager.getNamedPlugin(PackageDisseminator.class, packageType); if (dip == null) usageError("Error, Unknown package type: " + packageType); DSpaceObject dso = HandleManager.resolveToObject(context, itemHandle); if (dso == null) throw new IllegalArgumentException( "Bad Item handle -- " + "Cannot resolve handle \"" + itemHandle); dip.disseminate(context, dso, pkgParams, dest); } }
/** * PUT ingests a package as a new Item. Package type (must match pluggable packager name) is in * either (a) "package" query arg in URI (b) content-type request header * * @throws SQLException the SQL exception * @throws AuthorizeException the authorize exception * @throws ServletException the servlet exception * @throws IOException Signals that an I/O exception has occurred. * @throws DAVStatusException the DAV status exception */ @Override protected void put() throws SQLException, AuthorizeException, ServletException, IOException, DAVStatusException { try { String packageType = this.request.getParameter("package"); if (packageType == null) { packageType = this.request.getContentType(); } if (packageType == null) { throw new DAVStatusException( HttpServletResponse.SC_BAD_REQUEST, "Cannot determine package type, need content-type header or package param"); } PackageIngester sip = (PackageIngester) PluginManager.getNamedPlugin(PackageIngester.class, packageType); if (sip == null) { throw new DAVStatusException( HttpServletResponse.SC_BAD_REQUEST, "Cannot find importer for package type: " + packageType); } /* * Ugh. Servlet container doesn't get end-of-file right on input * stream so we have to count it, when possible. */ int contentLength = this.request.getIntHeader("Content-Length"); InputStream pis = this.request.getInputStream(); if (contentLength >= 0) { pis = new CountedInputStream(pis, contentLength); log.debug("put: Using CountedInputStream, length=" + String.valueOf(contentLength)); } WorkspaceItem wi = sip.ingest( this.context, this.collection, pis, PackageParameters.create(this.request), null); WorkflowItem wfi = WorkflowManager.startWithoutNotify(this.context, wi); // get new item's location: if workflow completed, then look // for handle (but be ready for disappointment); otherwise, // return the workflow item's resource. int state = wfi.getState(); String location = null; if (state == WorkflowManager.WFSTATE_ARCHIVE) { Item ni = wfi.getItem(); // FIXME: I'm not sure this is what we want String handle = ni.getExternalIdentifier().getCanonicalForm(); // String handle = HandleManager.findHandle(this.context, ni); String end = (handle != null) ? DAVDSpaceObject.getPathElt(handle) : DAVItem.getPathElt(ni); DAVItem newItem = new DAVItem(this.context, this.request, this.response, makeChildPath(end), ni); location = newItem.hrefURL(); } else if (state == WorkflowManager.WFSTATE_SUBMIT || state == WorkflowManager.WFSTATE_STEP1POOL) { location = hrefPrefix() + DAVWorkflow.getPath(wfi); } else { throw new DAVStatusException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Workflow object in unexpected state, state=" + String.valueOf(state) + ", aborting PUT."); } this.context.commit(); log.info("Created new Item, location=" + location); this.response.setHeader("Location", location); this.response.setStatus(HttpServletResponse.SC_CREATED); } catch (PackageException pe) { pe.log(log); throw new DAVStatusException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, pe.toString()); } catch (CrosswalkException ie) { String reason = ""; if (ie.getCause() != null) { reason = ", Reason: " + ie.getCause().toString(); } log.error(ie.toString() + reason); throw new DAVStatusException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ie.toString() + reason); } }
/** * Create a new DSpace item out of a METS content package. All contents are dictated by the METS * manifest. Package is a ZIP archive, all files relative to top level and the manifest (as per * spec) in mets.xml. * * @param context - DSpace context. * @param collection - collection under which to create new item. * @param pkg - input stream containing package to ingest. * @param license - may be null, which takes default license. * @return workspace item created by ingest. * @throws PackageValidationException if package is unacceptable or there is a fatal error turning * it into an Item. */ public WorkspaceItem ingest( Context context, Collection collection, InputStream pkg, PackageParameters params, String license) throws PackageValidationException, CrosswalkException, AuthorizeException, SQLException, IOException { BitstreamDAO bsDAO = BitstreamDAOFactory.getInstance(context); BitstreamFormatDAO bfDAO = BitstreamFormatDAOFactory.getInstance(context); BundleDAO bundleDAO = BundleDAOFactory.getInstance(context); WorkspaceItemDAO wsiDAO = WorkspaceItemDAOFactory.getInstance(context); ZipInputStream zip = new ZipInputStream(pkg); HashMap fileIdToBitstream = new HashMap(); WorkspaceItem wi = null; boolean success = false; HashSet packageFiles = new HashSet(); boolean validate = params.getBooleanProperty("validate", true); try { /* 1. Read all the files in the Zip into bitstreams first, * because we only get to take one pass through a Zip input * stream. Give them temporary bitstream names corresponding * to the same names they had in the Zip, since those MUST * match the URL references in <Flocat> and <mdRef> elements. */ METSManifest manifest = null; wi = wsiDAO.create(collection, false); Item item = wi.getItem(); Bundle contentBundle = item.createBundle(Constants.CONTENT_BUNDLE_NAME); Bundle mdBundle = null; ZipEntry ze; while ((ze = zip.getNextEntry()) != null) { if (ze.isDirectory()) continue; Bitstream bs = null; String fname = ze.getName(); if (fname.equals(MANIFEST_FILE)) { if (preserveManifest) { mdBundle = item.createBundle(Constants.METADATA_BUNDLE_NAME); bs = mdBundle.createBitstream(new PackageUtils.UnclosableInputStream(zip)); bs.setName(fname); bs.setSource(fname); // Get magic bitstream format to identify manifest. BitstreamFormat manifestFormat = null; manifestFormat = PackageUtils.findOrCreateBitstreamFormat( context, MANIFEST_BITSTREAM_FORMAT, "application/xml", MANIFEST_BITSTREAM_FORMAT + " package manifest"); bs.setFormat(manifestFormat); manifest = METSManifest.create(bs.retrieve(), validate); } else { manifest = METSManifest.create(new PackageUtils.UnclosableInputStream(zip), validate); continue; } } else { bs = contentBundle.createBitstream(new PackageUtils.UnclosableInputStream(zip)); bs.setSource(fname); bs.setName(fname); } packageFiles.add(fname); bs.setSource(fname); bsDAO.update(bs); } zip.close(); if (manifest == null) throw new PackageValidationException( "No METS Manifest found (filename=" + MANIFEST_FILE + "). Package is unacceptable."); // initial sanity checks on manifest (in subclass) checkManifest(manifest); /* 2. Grovel a file list out of METS Manifest and compare * it to the files in package, as an integrity test. */ List manifestContentFiles = manifest.getContentFiles(); // Compare manifest files with the ones found in package: // a. Start with content files (mentioned in <fileGrp>s) HashSet missingFiles = new HashSet(); for (Iterator mi = manifestContentFiles.iterator(); mi.hasNext(); ) { // First locate corresponding Bitstream and make // map of Bitstream to <file> ID. Element mfile = (Element) mi.next(); String mfileId = mfile.getAttributeValue("ID"); if (mfileId == null) throw new PackageValidationException( "Invalid METS Manifest: file element without ID attribute."); String path = METSManifest.getFileName(mfile); Bitstream bs = contentBundle.getBitstreamByName(path); if (bs == null) { log.warn( "Cannot find bitstream for filename=\"" + path + "\", skipping it..may cause problems later."); missingFiles.add(path); } else { fileIdToBitstream.put(mfileId, bs); // Now that we're done using Name to match to <file>, // set default bitstream Name to last path element; // Zip entries all have '/' pathname separators // NOTE: set default here, hopefully crosswalk of // a bitstream techMD section will override it. String fname = bs.getName(); int lastSlash = fname.lastIndexOf('/'); if (lastSlash >= 0 && lastSlash + 1 < fname.length()) bs.setName(fname.substring(lastSlash + 1)); // Set Default bitstream format: // 1. attempt to guess from MIME type // 2. if that fails, guess from "name" extension. String mimeType = mfile.getAttributeValue("MIMETYPE"); BitstreamFormat bf = (mimeType == null) ? null : bfDAO.retrieveByMimeType(mimeType); if (bf == null) bf = FormatIdentifier.guessFormat(context, bs); bs.setFormat(bf); // if this bitstream belongs in another Bundle, move it: String bundleName = manifest.getBundleName(mfile); if (!bundleName.equals(Constants.CONTENT_BUNDLE_NAME)) { Bundle bn; Bundle bns[] = item.getBundles(bundleName); if (bns != null && bns.length > 0) bn = bns[0]; else bn = item.createBundle(bundleName); bn.addBitstream(bs); contentBundle.removeBitstream(bs); } // finally, build compare lists by deleting matches. if (packageFiles.contains(path)) packageFiles.remove(path); else missingFiles.add(path); } } // b. Process files mentioned in <mdRef>s - check and move // to METADATA bundle. for (Iterator mi = manifest.getMdFiles().iterator(); mi.hasNext(); ) { Element mdref = (Element) mi.next(); String path = METSManifest.getFileName(mdref); // finally, build compare lists by deleting matches. if (packageFiles.contains(path)) packageFiles.remove(path); else missingFiles.add(path); // if there is a bitstream with that name in Content, move // it to the Metadata bundle: Bitstream mdbs = contentBundle.getBitstreamByName(path); if (mdbs != null) { if (mdBundle == null) mdBundle = item.createBundle(Constants.METADATA_BUNDLE_NAME); mdBundle.addBitstream(mdbs); contentBundle.removeBitstream(mdbs); } } // KLUDGE: make sure Manifest file doesn't get flagged as missing // or extra, since it won't be mentioned in the manifest. if (packageFiles.contains(MANIFEST_FILE)) packageFiles.remove(MANIFEST_FILE); // Give subclass a chance to refine the lists of in-package // and missing files, delete extraneous files, etc. checkPackageFiles(packageFiles, missingFiles, manifest); // Any discrepency in file lists is a fatal error: if (!(packageFiles.isEmpty() && missingFiles.isEmpty())) { StringBuffer msg = new StringBuffer("Package is unacceptable: contents do not match manifest."); if (!missingFiles.isEmpty()) { msg.append("\nPackage is missing these files listed in Manifest:"); for (Iterator mi = missingFiles.iterator(); mi.hasNext(); ) msg.append("\n\t" + (String) mi.next()); } if (!packageFiles.isEmpty()) { msg.append("\nPackage contains extra files NOT in manifest:"); for (Iterator mi = packageFiles.iterator(); mi.hasNext(); ) msg.append("\n\t" + (String) mi.next()); } throw new PackageValidationException(msg.toString()); } /* 3. crosswalk the metadata */ // get mdref'd streams from "callback" object. MdrefManager callback = new MdrefManager(mdBundle); chooseItemDmd(context, item, manifest, callback, manifest.getItemDmds()); // crosswalk content bitstreams too. for (Iterator ei = fileIdToBitstream.entrySet().iterator(); ei.hasNext(); ) { Map.Entry ee = (Map.Entry) ei.next(); manifest.crosswalkBitstream( context, (Bitstream) ee.getValue(), (String) ee.getKey(), callback); } // Take a second pass over files to correct names of derived files // (e.g. thumbnails, extracted text) to what DSpace expects: for (Iterator mi = manifestContentFiles.iterator(); mi.hasNext(); ) { Element mfile = (Element) mi.next(); String bundleName = manifest.getBundleName(mfile); if (!bundleName.equals(Constants.CONTENT_BUNDLE_NAME)) { Element origFile = manifest.getOriginalFile(mfile); if (origFile != null) { String ofileId = origFile.getAttributeValue("ID"); Bitstream obs = (Bitstream) fileIdToBitstream.get(ofileId); String newName = makeDerivedFilename(bundleName, obs.getName()); if (newName != null) { String mfileId = mfile.getAttributeValue("ID"); Bitstream bs = (Bitstream) fileIdToBitstream.get(mfileId); bs.setName(newName); bsDAO.update(bs); } } } } // Sanity-check the resulting metadata on the Item: PackageUtils.checkMetadata(item); /* 4. Set primary bitstream; same Bundle */ Element pbsFile = manifest.getPrimaryBitstream(); if (pbsFile != null) { Bitstream pbs = (Bitstream) fileIdToBitstream.get(pbsFile.getAttributeValue("ID")); if (pbs == null) log.error( "Got Primary Bitstream file ID=" + pbsFile.getAttributeValue("ID") + ", but found no corresponding bitstream."); else { List<Bundle> bn = bundleDAO.getBundles(pbs); if (bn.size() > 0) bn.get(0).setPrimaryBitstreamID(pbs.getID()); else log.error("Sanity check, got primary bitstream without any parent bundle."); } } // have subclass manage license since it may be extra package file. addLicense(context, collection, item, manifest, callback, license); // subclass hook for final checks and rearrangements finishItem(context, item); // commit any changes to bundles Bundle allBn[] = item.getBundles(); for (int i = 0; i < allBn.length; ++i) { bundleDAO.update(allBn[i]); } wsiDAO.update(wi); success = true; log.info( LogManager.getHeader( context, "ingest", "Created new Item, db ID=" + String.valueOf(item.getID()) + ", WorkspaceItem ID=" + String.valueOf(wi.getID()))); return wi; } catch (SQLException se) { // disable attempt to delete the workspace object, since // database may have suffered a fatal error and the // transaction rollback will get rid of it anyway. wi = null; // Pass this exception on to the next handler. throw se; } finally { // kill item (which also deletes bundles, bitstreams) if ingest fails if (!success && wi != null) wsiDAO.deleteAll(wi.getID()); } }