public void exportIntroductionPuzzle(IntroductionPuzzle puzzle, OutputStream os)
      throws TransformerException, ParserConfigurationException {

    Document xmlDoc;
    synchronized (
        mDocumentBuilder) { // TODO: Figure out whether the DocumentBuilder is maybe synchronized
                            // anyway
      xmlDoc = mDOM.createDocument(null, WebOfTrust.WOT_NAME, null);
    }

    // 1.0 does not support all Unicode characters which the String class supports. To prevent us
    // from having to filter all Strings, we use 1.1
    xmlDoc.setXmlVersion("1.1");

    Element rootElement = xmlDoc.getDocumentElement();

    // We include the WoT version to have an easy way of handling bogus XML which might be created
    // by bugged versions.
    rootElement.setAttribute("Version", Long.toString(Version.getRealVersion()));

    Element puzzleElement = xmlDoc.createElement("IntroductionPuzzle");
    puzzleElement.setAttribute(
        "Version",
        Integer.toString(INTRODUCTION_XML_FORMAT_VERSION)); /* Version of the XML format */

    // This lock is actually not necessary because all values which are taken from the puzzle are
    // final. We leave it here just to make sure that it does
    // not get lost if it becomes necessary someday.
    synchronized (puzzle) {
      puzzleElement.setAttribute("ID", puzzle.getID());
      puzzleElement.setAttribute("Type", puzzle.getType().toString());
      puzzleElement.setAttribute("MimeType", puzzle.getMimeType());
      synchronized (mDateFormat) {
        puzzleElement.setAttribute("ValidUntil", mDateFormat.format(puzzle.getValidUntilDate()));
      }

      Element dataElement = xmlDoc.createElement("Data");
      dataElement.setAttribute("Value", Base64.encodeStandard(puzzle.getData()));
      puzzleElement.appendChild(dataElement);
    }

    rootElement.appendChild(puzzleElement);

    DOMSource domSource = new DOMSource(xmlDoc);
    StreamResult resultStream = new StreamResult(os);
    synchronized (mSerializer) {
      mSerializer.transform(domSource, resultStream);
    }
  }
  /**
   * @param xmlInputStream An InputStream which must not return more than {@link
   *     MAX_INTRODUCTIONPUZZLE_BYTE_SIZE} bytes.
   */
  public IntroductionPuzzle importIntroductionPuzzle(
      FreenetURI puzzleURI, InputStream xmlInputStream)
      throws SAXException, IOException, InvalidParameterException, UnknownIdentityException,
          IllegalBase64Exception, ParseException {

    xmlInputStream =
        new OneBytePerReadInputStream(
            xmlInputStream); // Workaround for Java bug, see the stream class for explanation

    // May not be accurate by definition of available(). So the JavaDoc requires the callers to obey
    // the size limit, this is a double-check.
    if (xmlInputStream.available() > MAX_INTRODUCTIONPUZZLE_BYTE_SIZE)
      throw new IllegalArgumentException(
          "XML contains too many bytes: " + xmlInputStream.available());

    String puzzleID;
    IntroductionPuzzle.PuzzleType puzzleType;
    String puzzleMimeType;
    Date puzzleValidUntilDate;
    byte[] puzzleData;

    Document xmlDoc;
    synchronized (
        mDocumentBuilder) { // TODO: Figure out whether the DocumentBuilder is maybe synchronized
                            // anyway
      xmlDoc = mDocumentBuilder.parse(xmlInputStream);
    }
    Element puzzleElement = (Element) xmlDoc.getElementsByTagName("IntroductionPuzzle").item(0);

    if (Integer.parseInt(puzzleElement.getAttribute("Version")) > INTRODUCTION_XML_FORMAT_VERSION)
      throw new InvalidParameterException(
          "Version "
              + puzzleElement.getAttribute("Version")
              + " > "
              + INTRODUCTION_XML_FORMAT_VERSION);

    puzzleID = puzzleElement.getAttribute("ID");
    puzzleType = IntroductionPuzzle.PuzzleType.valueOf(puzzleElement.getAttribute("Type"));
    puzzleMimeType = puzzleElement.getAttribute("MimeType");
    synchronized (mDateFormat) {
      puzzleValidUntilDate = mDateFormat.parse(puzzleElement.getAttribute("ValidUntil"));
    }

    Element dataElement = (Element) puzzleElement.getElementsByTagName("Data").item(0);
    puzzleData = Base64.decodeStandard(dataElement.getAttribute("Value"));

    IntroductionPuzzle puzzle;

    synchronized (mWoT) {
      Identity puzzleInserter = mWoT.getIdentityByURI(puzzleURI);
      puzzle =
          new IntroductionPuzzle(
              mWoT,
              puzzleInserter,
              puzzleID,
              puzzleType,
              puzzleMimeType,
              puzzleData,
              IntroductionPuzzle.getDateFromRequestURI(puzzleURI),
              puzzleValidUntilDate,
              IntroductionPuzzle.getIndexFromRequestURI(puzzleURI));

      mWoT.getIntroductionPuzzleStore().storeAndCommit(puzzle);
    }

    return puzzle;
  }