/**
  * Create a M2Model from a dictionary model node
  *
  * @param nodeRef the dictionary model node reference
  * @return the M2Model
  */
 public M2Model createM2Model(NodeRef nodeRef) {
   M2Model model = null;
   ContentReader contentReader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
   if (contentReader != null) {
     if (contentReader instanceof EmptyContentReader) {
       // belts-and-braces
       logger.error("Failed to create model (due to EmptyContentReader): " + nodeRef);
     } else {
       InputStream is = null;
       try {
         is = contentReader.getContentInputStream();
         model = M2Model.createModel(is);
       } finally {
         if (is != null) {
           try {
             is.close();
           } catch (IOException e) {
             logger.error("Failed to close input stream for " + nodeRef);
           }
         }
       }
     }
   }
   // TODO should we inactivate the model node and put the error somewhere??
   return model;
 }
  /** Asks Tika to translate the contents into HTML */
  private void generateHTML(Parser p, RenderingContext context) {
    ContentReader contentReader = context.makeContentReader();

    // Setup things to parse with
    StringWriter sw = new StringWriter();
    ContentHandler handler = buildContentHandler(sw, context);

    // Tell Tika what we're dealing with
    Metadata metadata = new Metadata();
    metadata.set(Metadata.CONTENT_TYPE, contentReader.getMimetype());
    metadata.set(
        Metadata.RESOURCE_NAME_KEY,
        nodeService.getProperty(context.getSourceNode(), ContentModel.PROP_NAME).toString());

    // Our parse context needs to extract images
    ParseContext parseContext = new ParseContext();
    parseContext.set(Parser.class, new TikaImageExtractingParser(context));

    // Parse
    try {
      p.parse(contentReader.getContentInputStream(), handler, metadata, parseContext);
    } catch (Exception e) {
      throw new RenditionServiceException("Tika HTML Conversion Failed", e);
    }

    // As a string
    String html = sw.toString();

    // If we're doing body-only, remove all the html namespaces
    //  that will otherwise clutter up the document
    boolean bodyOnly = context.getParamWithDefault(PARAM_BODY_CONTENTS_ONLY, false);
    if (bodyOnly) {
      html = html.replaceAll("<\\?xml.*?\\?>", "");
      html = html.replaceAll("<p xmlns=\"http://www.w3.org/1999/xhtml\"", "<p");
      html = html.replaceAll("<h(\\d) xmlns=\"http://www.w3.org/1999/xhtml\"", "<h\\1");
      html = html.replaceAll("<div xmlns=\"http://www.w3.org/1999/xhtml\"", "<div");
      html = html.replaceAll("<table xmlns=\"http://www.w3.org/1999/xhtml\"", "<table");
      html = html.replaceAll("&#13;", "");
    }

    // Save it
    ContentWriter contentWriter = context.makeContentWriter();
    contentWriter.setMimetype("text/html");
    contentWriter.putContent(html);
  }
  /**
   * get document from wcm content
   *
   * @param path
   * @return document
   * @throws ServiceException
   */
  public InputStream getContent(String path) {
    InputStream retStream = null;
    PersistenceManagerService persistenceManagerService =
        _servicesManager.getService(PersistenceManagerService.class);
    NodeRef nodeRef = persistenceManagerService.getNodeRef(path);

    if (nodeRef != null) {
      FileInfo fileInfo = persistenceManagerService.getFileInfo(nodeRef);
      if (fileInfo.isFolder()) {
        logger.info(MSG_CONTENT_FOR_FOLDER_REQUESTED, path);
      } else {
        ContentReader reader = persistenceManagerService.getReader(nodeRef);
        retStream = reader.getContentInputStream();
      }
    } else {
      logger.info(MSG_NODE_REF_IS_NULL_FOR_PATH, path);
    }

    return retStream;
  }
  /**
   * Send an email message
   *
   * @throws AlfrescoRuntimeExeption
   */
  @SuppressWarnings("unchecked")
  @Override
  protected void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef) {
    try {
      MimeMessage message = javaMailSender.createMimeMessage();
      // use the true flag to indicate you need a multipart message
      MimeMessageHelper helper = new MimeMessageHelper(message, true);

      // set recipient
      String to = (String) ruleAction.getParameterValue(PARAM_TO);
      if (to != null && to.length() != 0) {
        helper.setTo(to);
      } else {
        // see if multiple recipients have been supplied - as a list of
        // authorities
        Serializable toManyMails = ruleAction.getParameterValue(PARAM_TO_MANY);
        List<String> recipients = new ArrayList<String>();
        if (toManyMails instanceof List) {
          for (String mailAdress : (List<String>) toManyMails) {
            if (validateAddress(mailAdress)) {
              recipients.add(mailAdress);
            }
          }
        } else if (toManyMails instanceof String) {
          if (validateAddress((String) toManyMails)) {
            recipients.add((String) toManyMails);
          }
        }
        if (recipients != null && recipients.size() > 0) {
          helper.setTo(recipients.toArray(new String[recipients.size()]));
        } else {
          // No recipients have been specified
          logger.error("No recipient has been specified for the mail action");
        }
      }

      // set subject line
      helper.setSubject((String) ruleAction.getParameterValue(PARAM_SUBJECT));

      // See if an email template has been specified
      String text = null;
      NodeRef templateRef = (NodeRef) ruleAction.getParameterValue(PARAM_TEMPLATE);
      if (templateRef != null) {
        // build the email template model
        Map<String, Object> model = createEmailTemplateModel(actionedUponNodeRef, ruleAction);

        // process the template against the model
        text = templateService.processTemplate("freemarker", templateRef.toString(), model);
      }

      // set the text body of the message
      if (text == null) {
        text = (String) ruleAction.getParameterValue(PARAM_TEXT);
      }
      // adding the boolean true to send as HTML
      helper.setText(text, true);
      FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
      /* add inline images.
       * "action.parameters.images is a ,-delimited string, containing a map of images and resources, from this example:
      message.setText("my text <img src='cid:myLogo'>", true);
      message.addInline("myLogo", new ClassPathResource("img/mylogo.gif"));
      so the "images" param can look like this: headerLogo|images/headerLogoNodeRef,footerLogo|footerLogoNodeRef
       */
      String imageList = (String) ruleAction.getParameterValue(PARAM_IMAGES);
      System.out.println(imageList);
      String[] imageMap = imageList.split(","); // comma no spaces
      Map<String, String> images = new HashMap<String, String>();
      for (String image : imageMap) {
        System.out.println(image);
        String map[] = image.split("\\|");
        for (String key : map) {
          System.out.println(key);
        }

        System.out.println(map.length);

        images.put(map[0].trim(), map[1].trim());
        System.out.println(images.size());
        System.out.println("-" + map[0] + " " + map[1] + "-");
      }
      NodeRef imagesFolderNodeRef = (NodeRef) ruleAction.getParameterValue(PARAM_IMAGES_FOLDER);
      if (null != imagesFolderNodeRef) {
        ContentService contentService = serviceRegistry.getContentService();
        System.out.println("mapping");
        for (Map.Entry<String, String> entry : images.entrySet()) {
          System.out.println(
              entry.getKey()
                  + " "
                  + entry.getValue()
                  + " "
                  + ruleAction.getParameterValue(PARAM_IMAGES_FOLDER));
          NodeRef imageFile = fileFolderService.searchSimple(imagesFolderNodeRef, entry.getValue());
          if (null != imageFile) {
            ContentReader reader = contentService.getReader(imageFile, ContentModel.PROP_CONTENT);
            ByteArrayResource resource =
                new ByteArrayResource(IOUtils.toByteArray(reader.getContentInputStream()));
            helper.addInline(entry.getKey(), resource, reader.getMimetype());

          } else {
            logger.error("No image for " + entry.getKey());
          }
        }
      } else {
        logger.error("No images folder");
      }

      // set the from address
      NodeRef person = personService.getPerson(authService.getCurrentUserName());

      String fromActualUser = null;
      if (person != null) {
        fromActualUser = (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL);
      }
      if (fromActualUser != null && fromActualUser.length() != 0) {
        helper.setFrom(fromActualUser);
      } else {
        String from = (String) ruleAction.getParameterValue(PARAM_FROM);
        if (from == null || from.length() == 0) {
          helper.setFrom(fromAddress);
        } else {
          helper.setFrom(from);
        }
      }
      NodeRef attachmentsFolder = (NodeRef) ruleAction.getParameterValue(PARAM_ATTCHMENTS_FOLDER);
      if (attachmentsFolder != null) {

        List<FileInfo> attachFiles = fileFolderService.listFiles(attachmentsFolder);
        if (attachFiles != null && attachFiles.size() > 0) {
          for (FileInfo attachFile : attachFiles) {
            ContentReader contentReader = fileFolderService.getReader(attachFile.getNodeRef());
            ByteArrayResource resource =
                new ByteArrayResource(IOUtils.toByteArray(contentReader.getContentInputStream()));
            helper.addAttachment(attachFile.getName(), resource, contentReader.getMimetype());
          }
        }
      }

      // Send the message unless we are in "testMode"
      javaMailSender.send(message);
    } catch (Exception e) {
      String toUser = (String) ruleAction.getParameterValue(PARAM_TO);
      if (toUser == null) {
        Object obj = ruleAction.getParameterValue(PARAM_TO_MANY);
        if (obj != null) {
          toUser = obj.toString();
        }
      }

      logger.error("Failed to send email to " + toUser, e);

      throw new AlfrescoRuntimeException("Failed to send email to:" + toUser, e);
    }
  }
  public List<VerifyResultDTO> verifySign(final VerifyingDTO verifyingDTO) {
    final List<VerifyResultDTO> result = new ArrayList<VerifyResultDTO>();
    try {
      if (verifyingDTO != null) {
        final String keyType =
            (String) nodeService.getProperty(verifyingDTO.getKeyFile(), SigningModel.PROP_KEYTYPE);

        final KeyStore ks = KeyStore.getInstance(keyType);
        final ContentReader keyContentReader = getReader(verifyingDTO.getKeyFile());
        if (keyContentReader != null && ks != null && verifyingDTO.getKeyPassword() != null) {

          // Get crypted secret key and decrypt it
          final Serializable encryptedPropertyValue =
              nodeService.getProperty(verifyingDTO.getKeyFile(), SigningModel.PROP_KEYCRYPTSECRET);
          final Serializable decryptedPropertyValue =
              metadataEncryptor.decrypt(SigningModel.PROP_KEYCRYPTSECRET, encryptedPropertyValue);

          // Decrypt key content
          final InputStream decryptedKeyContent =
              CryptUtils.decrypt(
                  decryptedPropertyValue.toString(), keyContentReader.getContentInputStream());

          ks.load(
              new ByteArrayInputStream(IOUtils.toByteArray(decryptedKeyContent)),
              verifyingDTO.getKeyPassword().toCharArray());

          final ContentReader fileToVerifyContentReader = getReader(verifyingDTO.getFileToVerify());
          if (fileToVerifyContentReader != null) {
            final PdfReader reader =
                new PdfReader(fileToVerifyContentReader.getContentInputStream());
            if (reader != null) {
              final AcroFields af = reader.getAcroFields();
              if (af != null) {
                final ArrayList<String> names = af.getSignatureNames();
                if (names != null) {
                  for (int k = 0; k < names.size(); ++k) {
                    final VerifyResultDTO verifyResultDTO = new VerifyResultDTO();
                    final String name = (String) names.get(k);
                    verifyResultDTO.setName(name);
                    verifyResultDTO.setSignatureCoversWholeDocument(
                        af.signatureCoversWholeDocument(name));
                    verifyResultDTO.setRevision(af.getRevision(name));
                    verifyResultDTO.setTotalRevision(af.getTotalRevisions());

                    final PdfPKCS7 pk = af.verifySignature(name);
                    if (pk != null) {
                      final Calendar cal = pk.getSignDate();
                      final Certificate[] pkc = pk.getCertificates();
                      Object fails[] = PdfPKCS7.verifyCertificates(pkc, ks, null, cal);
                      if (fails == null) {
                        verifyResultDTO.setIsSignValid(true);
                      } else {
                        verifyResultDTO.setIsSignValid(false);
                        verifyResultDTO.setFailReason(fails[1]);
                      }
                      verifyResultDTO.setSignSubject(
                          PdfPKCS7.getSubjectFields(pk.getSigningCertificate()).toString());
                      verifyResultDTO.setIsDocumentModified(!pk.verify());
                      verifyResultDTO.setSignDate(pk.getSignDate());
                      verifyResultDTO.setSignLocation(pk.getLocation());
                      verifyResultDTO.setSignInformationVersion(pk.getSigningInfoVersion());
                      verifyResultDTO.setSignReason(pk.getReason());
                      verifyResultDTO.setSignVersion(pk.getVersion());
                      verifyResultDTO.setSignName(pk.getSignName());

                      result.add(verifyResultDTO);
                    } else {
                      log.error("Unable to verify signature.");
                      throw new AlfrescoRuntimeException("Unable to verify signature.");
                    }
                  }
                } else {
                  log.error("Unable to get signature names.");
                  throw new AlfrescoRuntimeException("Unable to get signature names.");
                }
              } else {
                log.error("Unable to get PDF fields.");
                throw new AlfrescoRuntimeException("Unable to get PDF fields.");
              }
            }
          } else {
            log.error("Unable to get document to verify content.");
            throw new AlfrescoRuntimeException("Unable to get document to verify content.");
          }
        } else {
          log.error("Unable to get key content, key type or key password.");
          throw new AlfrescoRuntimeException(
              "Unable to get key content, key type or key password.");
        }
      } else {
        log.error("No object with verification informations.");
        throw new AlfrescoRuntimeException("No object with verification informations.");
      }
    } catch (KeyStoreException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (ContentIOException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (NoSuchAlgorithmException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (CertificateException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (IOException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (GeneralSecurityException e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (Throwable e) {
      log.error(e);
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    }

    return result;
  }
  /**
   * Sign file.
   *
   * @param signingDTO sign informations
   * @param pdfSignedFile signed pdf returned
   */
  public void sign(final DigitalSigningDTO signingDTO) {
    if (signingDTO != null) {

      try {
        Security.addProvider(new BouncyCastleProvider());
        final File alfTempDir = TempFileProvider.getTempDir();

        if (alfTempDir != null) {
          final String keyType =
              (String) nodeService.getProperty(signingDTO.getKeyFile(), SigningModel.PROP_KEYTYPE);

          if (SigningConstants.KEY_TYPE_X509.equals(keyType)) {
            // Sign the file
            final KeyStore ks = KeyStore.getInstance("pkcs12");
            final ContentReader keyContentReader = getReader(signingDTO.getKeyFile());

            if (keyContentReader != null && ks != null && signingDTO.getKeyPassword() != null) {

              final List<AlfrescoRuntimeException> errors =
                  new ArrayList<AlfrescoRuntimeException>();

              // Get crypted secret key and decrypt it
              final Serializable encryptedPropertyValue =
                  nodeService.getProperty(
                      signingDTO.getKeyFile(), SigningModel.PROP_KEYCRYPTSECRET);
              final Serializable decryptedPropertyValue =
                  metadataEncryptor.decrypt(
                      SigningModel.PROP_KEYCRYPTSECRET, encryptedPropertyValue);

              // Decrypt key content
              InputStream decryptedKeyContent;
              try {
                decryptedKeyContent =
                    CryptUtils.decrypt(
                        decryptedPropertyValue.toString(),
                        keyContentReader.getContentInputStream());
              } catch (Throwable e) {
                log.error(e);
                throw new AlfrescoRuntimeException(e.getMessage(), e);
              }

              ks.load(
                  new ByteArrayInputStream(IOUtils.toByteArray(decryptedKeyContent)),
                  signingDTO.getKeyPassword().toCharArray());

              final String alias =
                  (String)
                      nodeService.getProperty(signingDTO.getKeyFile(), SigningModel.PROP_KEYALIAS);

              final PrivateKey key =
                  (PrivateKey) ks.getKey(alias, signingDTO.getKeyPassword().toCharArray());
              final Certificate[] chain = ks.getCertificateChain(alias);

              final Iterator<NodeRef> itFilesToSign = signingDTO.getFilesToSign().iterator();
              while (itFilesToSign.hasNext()) {
                final NodeRef nodeRefToSign = itFilesToSign.next();
                final AlfrescoRuntimeException exception =
                    signFile(nodeRefToSign, signingDTO, alfTempDir, alias, ks, key, chain);
                if (exception != null) {
                  // Error on the file process
                  errors.add(exception);
                }
              }

              if (errors != null && errors.size() > 0) {
                final StringBuffer allErrors = new StringBuffer();
                final Iterator<AlfrescoRuntimeException> itErrors = errors.iterator();
                if (errors.size() > 1) {
                  allErrors.append("\n");
                }
                while (itErrors.hasNext()) {
                  final AlfrescoRuntimeException alfrescoRuntimeException = itErrors.next();
                  allErrors.append(alfrescoRuntimeException.getMessage());
                  if (itErrors.hasNext()) {
                    allErrors.append("\n");
                  }
                }
                throw new RuntimeException(allErrors.toString());
              }

            } else {
              log.error("Unable to get key content, key type or key password.");
              throw new AlfrescoRuntimeException(
                  "Unable to get key content, key type or key password.");
            }
          }
        } else {
          log.error("Unable to get temporary directory.");
          throw new AlfrescoRuntimeException("Unable to get temporary directory.");
        }
      } catch (KeyStoreException e) {
        log.error(e);
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      } catch (NoSuchAlgorithmException e) {
        log.error(e);
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      } catch (CertificateException e) {
        log.error(e);
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      } catch (IOException e) {
        log.error(e);
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      } catch (UnrecoverableKeyException e) {
        log.error(e);
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      }
    } else {
      log.error("No object with signing informations.");
      throw new AlfrescoRuntimeException("No object with signing informations.");
    }
  }
  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);
        }
      }
    }
  }
  /**
   * @param reader
   * @param writer
   * @param options
   * @throws Exception
   */
  protected final void action(
      Action ruleAction,
      NodeRef actionedUponNodeRef,
      ContentReader reader,
      Map<String, Object> options) {
    PDDocument pdf = null;
    InputStream is = null;
    File tempDir = null;
    ContentWriter writer = null;

    try {
      // Get the split frequency
      int splitFrequency = 0;

      String splitFrequencyString = options.get(PARAM_SPLIT_AT_PAGE).toString();
      if (!splitFrequencyString.equals("")) {
        try {
          splitFrequency = Integer.valueOf(splitFrequencyString);
        } catch (NumberFormatException e) {
          throw new AlfrescoRuntimeException(e.getMessage(), e);
        }
      }

      // Get contentReader inputStream
      is = reader.getContentInputStream();
      // stream the document in
      pdf = PDDocument.load(is);
      // split the PDF and put the pages in a list
      Splitter splitter = new Splitter();
      // Need to adjust the input value to get the split at the right page
      splitter.setSplitAtPage(splitFrequency - 1);

      // Split the pages
      List<PDDocument> pdfs = splitter.split(pdf);

      // Start page split numbering at
      int page = 1;

      // build a temp dir, name based on the ID of the noderef we are
      // importing
      File alfTempDir = TempFileProvider.getTempDir();
      tempDir = new File(alfTempDir.getPath() + File.separatorChar + actionedUponNodeRef.getId());
      tempDir.mkdir();

      // FLAG: This is ugly.....get the first PDF.
      PDDocument firstPDF = (PDDocument) pdfs.remove(0);

      int pagesInFirstPDF = firstPDF.getNumberOfPages();

      String lastPage = "";
      String pg = "_pg";

      if (pagesInFirstPDF > 1) {
        pg = "_pgs";
        lastPage = "-" + pagesInFirstPDF;
      }

      String fileNameSansExt = getFilenameSansExt(actionedUponNodeRef, FILE_EXTENSION);
      firstPDF.save(
          tempDir
              + ""
              + File.separatorChar
              + fileNameSansExt
              + pg
              + page
              + lastPage
              + FILE_EXTENSION);

      try {
        firstPDF.close();
      } catch (IOException e) {
        throw new AlfrescoRuntimeException(e.getMessage(), e);
      }

      // FLAG: Like I said: "_UGLY_" ..... and it gets worse
      PDDocument secondPDF = null;

      Iterator<PDDocument> its = pdfs.iterator();

      int pagesInSecondPDF = 0;

      while (its.hasNext()) {
        if (secondPDF != null) {
          // Get the split document and save it into the temp dir with
          // new name
          PDDocument splitpdf = (PDDocument) its.next();

          int pagesInThisPDF = splitpdf.getNumberOfPages();
          pagesInSecondPDF = pagesInSecondPDF + pagesInThisPDF;

          PDFMergerUtility merger = new PDFMergerUtility();
          merger.appendDocument(secondPDF, splitpdf);
          merger.mergeDocuments();

          try {
            splitpdf.close();
          } catch (IOException e) {
            throw new AlfrescoRuntimeException(e.getMessage(), e);
          }

        } else {
          secondPDF = (PDDocument) its.next();

          pagesInSecondPDF = secondPDF.getNumberOfPages();
        }
      }

      if (pagesInSecondPDF > 1) {

        pg = "_pgs";
        lastPage = "-" + (pagesInSecondPDF + pagesInFirstPDF);

      } else {
        pg = "_pg";
        lastPage = "";
      }

      // This is where we should save the appended PDF
      // put together the name and save the PDF
      secondPDF.save(
          tempDir
              + ""
              + File.separatorChar
              + fileNameSansExt
              + pg
              + splitFrequency
              + lastPage
              + FILE_EXTENSION);

      for (File file : tempDir.listFiles()) {
        try {
          if (file.isFile()) {
            // Get a writer and prep it for putting it back into the
            // repo
            NodeRef destinationNode =
                createDestinationNode(
                    file.getName(),
                    (NodeRef) ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER),
                    actionedUponNodeRef);
            writer =
                serviceRegistry
                    .getContentService()
                    .getWriter(destinationNode, ContentModel.PROP_CONTENT, true);

            writer.setEncoding(reader.getEncoding()); // original
            // encoding
            writer.setMimetype(FILE_MIMETYPE);

            // Put it in the repo
            writer.putContent(file);

            // Clean up
            file.delete();
          }
        } catch (FileExistsException e) {
          throw new AlfrescoRuntimeException("Failed to process file.", e);
        }
      }
    } catch (COSVisitorException e) {
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } catch (IOException e) {
      throw new AlfrescoRuntimeException(e.getMessage(), e);
    } finally {
      if (pdf != null) {
        try {
          pdf.close();
        } catch (IOException e) {
          throw new AlfrescoRuntimeException(e.getMessage(), e);
        }
      }
      if (is != null) {
        try {
          is.close();
        } catch (IOException e) {
          throw new AlfrescoRuntimeException(e.getMessage(), e);
        }
      }

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