/**
   * 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());
    }
  }