@Override
  public NodeRef createNode(
      Resource resource,
      RepositoryLocation targetLocation,
      String encoding,
      String mimetype,
      QName nodeType)
      throws IOException {
    NodeRef rootNode = nodeService.getRootNode(targetLocation.getStoreRef());
    final List<NodeRef> parentNodes =
        searchService.selectNodes(
            rootNode, targetLocation.getPath(), null, namespaceService, false);
    Assert.isTrue(parentNodes.size() == 1, "Target location leads to not 1 unique Node reference");

    final String fileName = resource.getFilename();
    final FileInfo fileInfo = fileFolderService.create(parentNodes.get(0), fileName, nodeType);
    final NodeRef nodeRef = fileInfo.getNodeRef();

    final ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
    writer.putContent(resource.getInputStream());

    if (mimetype == null) {
      mimetype = guessMimetype(resource);
    }

    if (encoding == null) {
      encoding = guessEncoding(resource.getInputStream(), mimetype);
    }

    writer.setMimetype(mimetype);
    writer.setEncoding(encoding);

    return nodeRef;
  }
  /**
   * Write exception transfer report
   *
   * @return NodeRef the node ref of the new transfer report
   */
  public NodeRef createTransferReport(
      Exception e,
      TransferTarget target,
      TransferDefinition definition,
      List<TransferEvent> events,
      File snapshotFile) {
    Map<QName, Serializable> properties = new HashMap<QName, Serializable>();

    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssSSSZ");
    String timeNow = format.format(new Date());

    String title = "Transfer report, error,  " + timeNow;
    String description = "Transfer error report";
    String name = "Transfer error report, " + timeNow;

    properties.put(ContentModel.PROP_NAME, name);
    properties.put(ContentModel.PROP_TITLE, title);
    properties.put(ContentModel.PROP_DESCRIPTION, description);
    ChildAssociationRef ref =
        nodeService.createNode(
            target.getNodeRef(),
            ContentModel.ASSOC_CONTAINS,
            QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name),
            TransferModel.TYPE_TRANSFER_REPORT,
            properties);
    ContentWriter writer =
        contentService.getWriter(ref.getChildRef(), ContentModel.PROP_CONTENT, true);
    writer.setLocale(Locale.getDefault());
    writer.setMimetype(MimetypeMap.MIMETYPE_XML);
    writer.setEncoding(DEFAULT_ENCODING);

    //
    XMLTransferReportWriter reportWriter = new XMLTransferReportWriter();

    BufferedWriter bufferedWriter =
        new BufferedWriter(new OutputStreamWriter(writer.getContentOutputStream()));

    try {
      reportWriter.startTransferReport(DEFAULT_ENCODING, bufferedWriter);

      // Header
      reportWriter.writeTarget(target);

      reportWriter.writeDefinition(definition);

      reportWriter.writeException(e);

      // Detail
      reportWriter.writeTransferEvents(events);

      reportWriter.endTransferReport();

      return ref.getChildRef();
    } catch (SAXException se) {
      return null;
    } finally {
      try {
        bufferedWriter.close();
      } catch (IOException error) {
        error.printStackTrace();
      }
    }
  }
  /**
   * Create a new transfer report of success
   *
   * @return NodeRef the node ref of the new transfer report
   */
  public NodeRef createTransferReport(
      Transfer transfer,
      TransferTarget target,
      TransferDefinition definition,
      List<TransferEvent> events,
      File snapshotFile) {
    Map<QName, Serializable> properties = new HashMap<QName, Serializable>();

    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssSSSZ");
    String timeNow = format.format(new Date());

    String title = "Transfer report, " + timeNow + "success";
    String description = "Transfer report success targetName : " + target.getName();
    String name = "Transfer report, " + timeNow;

    properties.put(ContentModel.PROP_NAME, name);
    properties.put(ContentModel.PROP_TITLE, title);
    properties.put(ContentModel.PROP_DESCRIPTION, description);
    ChildAssociationRef ref =
        nodeService.createNode(
            target.getNodeRef(),
            ContentModel.ASSOC_CONTAINS,
            QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name),
            TransferModel.TYPE_TRANSFER_REPORT,
            properties);
    ContentWriter writer =
        contentService.getWriter(ref.getChildRef(), ContentModel.PROP_CONTENT, true);
    writer.setLocale(Locale.getDefault());
    writer.setMimetype(MimetypeMap.MIMETYPE_XML);
    writer.setEncoding(DEFAULT_ENCODING);

    //
    final XMLTransferReportWriter reportWriter = new XMLTransferReportWriter();

    BufferedWriter bufferedWriter =
        new BufferedWriter(new OutputStreamWriter(writer.getContentOutputStream()));

    try {
      reportWriter.startTransferReport(DEFAULT_ENCODING, bufferedWriter);

      // Header
      reportWriter.writeTarget(target);

      reportWriter.writeDefinition(definition);

      /** Write the node summary details to the transfer report */
      TransferManifestProcessor processor =
          new TransferManifestProcessor() {
            public void processTransferManifestNode(TransferManifestNormalNode node) {

              try {
                reportWriter.writeNodeSummary(node);
              } catch (SAXException error) {
                error.printStackTrace();
              }
            }

            public void processTransferManifestNode(TransferManifestDeletedNode node) {
              try {
                reportWriter.writeNodeSummary(node);
              } catch (SAXException error) {
                error.printStackTrace();
              }
            }

            public void processTransferManifiestHeader(TransferManifestHeader header) {
              /* NO-OP */
            }

            public void startTransferManifest() {
              /* NO-OP */
            }

            public void endTransferManifest() {
              /* NO-OP */
            }
          };

      /** Step 3: wire up the manifest reader to a manifest processor */
      SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
      SAXParser parser;
      parser = saxParserFactory.newSAXParser();
      XMLTransferManifestReader reader = new XMLTransferManifestReader(processor);

      /** Step 4: start the magic Give the manifest file to the manifest reader */
      try {
        parser.parse(snapshotFile, reader);
      } catch (IOException error) {
        // TODO temp code
        error.printStackTrace();
        return null;
      }

      // Detail Events
      reportWriter.writeTransferEvents(events);

      reportWriter.endTransferReport();

      return ref.getChildRef();
    } catch (SAXException se) {
      // TODO Temp code
      return null;
    } catch (ParserConfigurationException error) {
      // TODO temp code
      error.printStackTrace();
      return null;
    } finally {
      try {
        bufferedWriter.close();
      } catch (IOException error) {
        error.printStackTrace();
      }
    }
  }
  private AlfrescoRuntimeException signFile(
      final NodeRef nodeRefToSign,
      final DigitalSigningDTO signingDTO,
      final File alfTempDir,
      final String alias,
      final KeyStore ks,
      final PrivateKey key,
      final Certificate[] chain) {
    final String fileNameToSign = fileFolderService.getFileInfo(nodeRefToSign).getName();

    File fileConverted = null;
    File tempDir = null;
    try {
      ContentReader fileToSignContentReader = getReader(nodeRefToSign);

      if (fileToSignContentReader != null) {
        String newName = null;

        // Check if document is PDF or transform it
        if (!MimetypeMap.MIMETYPE_PDF.equals(fileToSignContentReader.getMimetype())) {
          // Transform document in PDF document
          final ContentTransformer tranformer =
              contentTransformerRegistry.getTransformer(
                  fileToSignContentReader.getMimetype(),
                  fileToSignContentReader.getSize(),
                  MimetypeMap.MIMETYPE_PDF,
                  new TransformationOptions());

          if (tranformer != null) {

            tempDir = new File(alfTempDir.getPath() + File.separatorChar + nodeRefToSign.getId());
            if (tempDir != null) {
              tempDir.mkdir();
              fileConverted =
                  new File(tempDir, fileNameToSign + "_" + System.currentTimeMillis() + ".pdf");
              if (fileConverted != null) {
                final ContentWriter newDoc = new FileContentWriter(fileConverted);
                if (newDoc != null) {
                  newDoc.setMimetype(MimetypeMap.MIMETYPE_PDF);
                  tranformer.transform(fileToSignContentReader, newDoc);
                  fileToSignContentReader = new FileContentReader(fileConverted);

                  final String originalName =
                      (String) nodeService.getProperty(nodeRefToSign, ContentModel.PROP_NAME);

                  newName = originalName.substring(0, originalName.lastIndexOf(".")) + ".pdf";
                }
              }
            }
          } else {
            log.error(
                "["
                    + fileNameToSign
                    + "] No suitable converter found to convert the document in PDF.");
            return new AlfrescoRuntimeException(
                "["
                    + fileNameToSign
                    + "] No suitable converter found to convert the document in PDF.");
          }
        }

        // Convert PDF in PDF/A format
        final File pdfAFile = convertPdfToPdfA(fileToSignContentReader.getContentInputStream());

        final PdfReader reader = new PdfReader(new FileInputStream(pdfAFile));

        if (nodeRefToSign != null) {
          tempDir = new File(alfTempDir.getPath() + File.separatorChar + nodeRefToSign.getId());
          if (tempDir != null) {
            tempDir.mkdir();
            final File file = new File(tempDir, fileNameToSign);

            if (file != null) {
              final FileOutputStream fout = new FileOutputStream(file);
              final PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');

              if (stp != null) {
                final PdfSignatureAppearance sap = stp.getSignatureAppearance();
                if (sap != null) {
                  sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
                  sap.setReason(signingDTO.getSignReason());
                  sap.setLocation(signingDTO.getSignLocation());
                  sap.setContact(signingDTO.getSignContact());
                  sap.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
                  sap.setImageScale(1);

                  // digital signature
                  if (signingDTO.getSigningField() != null
                      && !signingDTO.getSigningField().trim().equalsIgnoreCase("")) {
                    Image img = null;
                    if (signingDTO.getImage() != null) {
                      final ContentReader imageContentReader = getReader(signingDTO.getImage());
                      final AcroFields af = reader.getAcroFields();
                      if (af != null) {
                        final List<FieldPosition> positions =
                            af.getFieldPositions(signingDTO.getSigningField());
                        if (positions != null
                            && positions.size() > 0
                            && positions.get(0) != null
                            && positions.get(0).position != null) {
                          final BufferedImage newImg =
                              scaleImage(
                                  ImageIO.read(imageContentReader.getContentInputStream()),
                                  BufferedImage.TYPE_INT_RGB,
                                  Float.valueOf(positions.get(0).position.getWidth()).intValue(),
                                  Float.valueOf(positions.get(0).position.getHeight()).intValue());
                          img = Image.getInstance(newImg, null);
                        } else {
                          log.error(
                              "["
                                  + fileNameToSign
                                  + "] The field '"
                                  + signingDTO.getSigningField()
                                  + "' doesn't exist in the document.");
                          return new AlfrescoRuntimeException(
                              "["
                                  + fileNameToSign
                                  + "] The field '"
                                  + signingDTO.getSigningField()
                                  + "' doesn't exist in the document.");
                        }
                      }
                      if (img == null) {
                        img =
                            Image.getInstance(
                                ImageIO.read(imageContentReader.getContentInputStream()), null);
                      }
                      sap.setImage(img);
                    }
                    sap.setVisibleSignature(signingDTO.getSigningField());
                  } else {
                    int pageToSign = 1;
                    if (DigitalSigningDTO.PAGE_LAST.equalsIgnoreCase(
                        signingDTO.getPages().trim())) {
                      pageToSign = reader.getNumberOfPages();
                    } else if (DigitalSigningDTO.PAGE_SPECIFIC.equalsIgnoreCase(
                        signingDTO.getPages().trim())) {
                      if (signingDTO.getPageNumber() > 0
                          && signingDTO.getPageNumber() <= reader.getNumberOfPages()) {
                        pageToSign = signingDTO.getPageNumber();
                      } else {
                        throw new AlfrescoRuntimeException("Page number is out of bound.");
                      }
                    }
                    if (signingDTO.getImage() != null) {
                      final ContentReader imageContentReader = getReader(signingDTO.getImage());
                      // Resize image
                      final BufferedImage newImg =
                          scaleImage(
                              ImageIO.read(imageContentReader.getContentInputStream()),
                              BufferedImage.TYPE_INT_RGB,
                              signingDTO.getSignWidth(),
                              signingDTO.getSignHeight());
                      final Image img = Image.getInstance(newImg, null);
                      sap.setImage(img);
                    }
                    if (signingDTO.getPosition() != null
                        && !DigitalSigningDTO.POSITION_CUSTOM.equalsIgnoreCase(
                            signingDTO.getPosition().trim())) {
                      final Rectangle pageRect = reader.getPageSizeWithRotation(1);
                      sap.setVisibleSignature(
                          positionSignature(
                              signingDTO.getPosition(),
                              pageRect,
                              signingDTO.getSignWidth(),
                              signingDTO.getSignHeight(),
                              signingDTO.getxMargin(),
                              signingDTO.getyMargin()),
                          pageToSign,
                          null);
                    } else {
                      sap.setVisibleSignature(
                          new Rectangle(
                              signingDTO.getLocationX(),
                              signingDTO.getLocationY(),
                              signingDTO.getLocationX() + signingDTO.getSignWidth(),
                              signingDTO.getLocationY() - signingDTO.getSignHeight()),
                          pageToSign,
                          null);
                    }
                  }
                  stp.close();

                  NodeRef destinationNode = null;
                  NodeRef originalDoc = null;
                  boolean addAsNewVersion = false;
                  if (signingDTO.getDestinationFolder() == null) {
                    destinationNode = nodeRefToSign;
                    nodeService.addAspect(destinationNode, ContentModel.ASPECT_VERSIONABLE, null);
                    addAsNewVersion = true;
                  } else {
                    originalDoc = nodeRefToSign;
                    destinationNode =
                        createDestinationNode(
                            file.getName(), signingDTO.getDestinationFolder(), nodeRefToSign);
                  }

                  if (destinationNode != null) {

                    final ContentWriter writer =
                        contentService.getWriter(destinationNode, ContentModel.PROP_CONTENT, true);
                    if (writer != null) {
                      writer.setEncoding(fileToSignContentReader.getEncoding());
                      writer.setMimetype("application/pdf");
                      writer.putContent(file);
                      file.delete();

                      if (fileConverted != null) {
                        fileConverted.delete();
                      }

                      nodeService.addAspect(
                          destinationNode,
                          SigningModel.ASPECT_SIGNED,
                          new HashMap<QName, Serializable>());
                      nodeService.setProperty(
                          destinationNode, SigningModel.PROP_REASON, signingDTO.getSignReason());
                      nodeService.setProperty(
                          destinationNode,
                          SigningModel.PROP_LOCATION,
                          signingDTO.getSignLocation());
                      nodeService.setProperty(
                          destinationNode, SigningModel.PROP_SIGNATUREDATE, new java.util.Date());
                      nodeService.setProperty(
                          destinationNode,
                          SigningModel.PROP_SIGNEDBY,
                          AuthenticationUtil.getRunAsUser());

                      if (newName != null) {
                        nodeService.setProperty(destinationNode, ContentModel.PROP_NAME, newName);
                      }

                      final X509Certificate c = (X509Certificate) ks.getCertificate(alias);
                      nodeService.setProperty(
                          destinationNode, SigningModel.PROP_VALIDITY, c.getNotAfter());
                      nodeService.setProperty(
                          destinationNode, SigningModel.PROP_ORIGINAL_DOC, originalDoc);

                      if (!addAsNewVersion) {
                        if (!nodeService.hasAspect(originalDoc, SigningModel.ASPECT_ORIGINAL_DOC)) {
                          nodeService.addAspect(
                              originalDoc,
                              SigningModel.ASPECT_ORIGINAL_DOC,
                              new HashMap<QName, Serializable>());
                        }
                        nodeService.createAssociation(
                            originalDoc, destinationNode, SigningModel.PROP_RELATED_DOC);
                      }
                    }
                  } else {
                    log.error("[" + fileNameToSign + "] Destination node is not a valid NodeRef.");
                    return new AlfrescoRuntimeException(
                        "[" + fileNameToSign + "] Destination node is not a valid NodeRef.");
                  }
                } else {
                  log.error("[" + fileNameToSign + "] Unable to get PDF appearance signature.");
                  return new AlfrescoRuntimeException(
                      "[" + fileNameToSign + "] Unable to get PDF appearance signature.");
                }
              } else {
                log.error("[" + fileNameToSign + "] Unable to create PDF signature.");
                return new AlfrescoRuntimeException(
                    "[" + fileNameToSign + "] Unable to create PDF signature.");
              }
            }
          }
        } else {
          log.error("[" + fileNameToSign + "] Unable to get document to sign content.");
          return new AlfrescoRuntimeException(
              "[" + fileNameToSign + "] Unable to get document to sign content.");
        }

        if (pdfAFile != null) {
          pdfAFile.delete();
        }

        return null;

      } else {
        log.error("[" + fileNameToSign + "] The document has no content.");
        return new AlfrescoRuntimeException(
            "[" + fileNameToSign + "] The document has no content.");
      }
    } catch (KeyStoreException e) {
      log.error("[" + fileNameToSign + "] " + e);
      return new AlfrescoRuntimeException("[" + fileNameToSign + "] " + e.getMessage(), e);
    } catch (ContentIOException e) {
      log.error("[" + fileNameToSign + "] " + e);
      return new AlfrescoRuntimeException("[" + fileNameToSign + "] " + e.getMessage(), e);
    } catch (IOException e) {
      log.error("[" + fileNameToSign + "] " + e);
      return new AlfrescoRuntimeException("[" + fileNameToSign + "] " + e.getMessage(), e);
    } catch (DocumentException e) {
      log.error("[" + fileNameToSign + "] " + e);
      return new AlfrescoRuntimeException("[" + fileNameToSign + "] " + e.getMessage(), e);
    } finally {
      if (tempDir != null) {
        try {
          tempDir.delete();
        } catch (Exception ex) {
          log.error("[" + fileNameToSign + "] " + ex);
          return new AlfrescoRuntimeException("[" + fileNameToSign + "] " + ex.getMessage(), ex);
        }
      }
    }
  }
 @Override
 public void updateNode(Resource resource, NodeRef existingNode) throws IOException {
   contentService
       .getWriter(existingNode, ContentModel.PROP_CONTENT, true)
       .putContent(resource.getInputStream());
 }