@Override
  public BlobHolder convert(BlobHolder blobHolder, Map<String, Serializable> parameters)
      throws ConversionException {
    try {
      // Make sure the toThumbnail command is available
      CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class);
      CommandAvailability commandAvailability = cles.getCommandAvailability(THUMBNAIL_COMMAND);
      if (!commandAvailability.isAvailable()) {
        return null;
      }
      // get the input and output of the command
      Blob blob = blobHolder.getBlob();
      File inputFile = null;
      if (blob instanceof FileBlob) {
        inputFile = ((FileBlob) blob).getFile();
      } else if (blob instanceof SQLBlob) {
        StreamSource source = ((SQLBlob) blob).getBinary().getStreamSource();
        inputFile = ((FileSource) source).getFile();
      } else if (blob instanceof StreamingBlob) {
        StreamingBlob streamingBlob = ((StreamingBlob) blob);
        if (!streamingBlob.isPersistent()) {
          streamingBlob.persist();
        }
        StreamSource source = streamingBlob.getStreamSource();
        inputFile = ((FileSource) source).getFile();
      }
      if (inputFile == null) {
        return null;
      }
      CmdParameters params = new CmdParameters();
      File outputFile = File.createTempFile("nuxeoImageTarget", "." + "png");
      String size = THUMBNAIL_DEFAULT_SIZE;
      if (parameters != null) {
        if (parameters.containsKey(THUMBNAIL_SIZE_PARAMETER_NAME)) {
          size = (String) parameters.get(THUMBNAIL_SIZE_PARAMETER_NAME);
        }
      }
      params.addNamedParameter(THUMBNAIL_SIZE_PARAMETER_NAME, size);
      params.addNamedParameter("inputFilePath", inputFile);
      params.addNamedParameter("outputFilePath", outputFile);

      ExecResult res = cles.execCommand(THUMBNAIL_COMMAND, params);
      if (!res.isSuccessful()) {
        throw res.getError();
      }
      Blob targetBlob = new FileBlob(outputFile);
      Framework.trackFile(outputFile, targetBlob);
      return new SimpleCachableBlobHolder(targetBlob);
    } catch (CommandNotAvailable | IOException | ClientException | CommandException e) {
      throw new ConversionException("Thumbnail conversion failed", e);
    }
  }
  @Override
  public Blob renderTemplate(TemplateBasedDocument templateBasedDocument, String templateName)
      throws Exception {

    Blob sourceTemplateBlob = getSourceTemplateBlob(templateBasedDocument, templateName);
    List<TemplateInput> params = templateBasedDocument.getParams(templateName);

    DocumentModel doc = templateBasedDocument.getAdaptedDoc();
    Map<String, Object> ctx = FMContextBuilder.build(doc, false);

    JXLSBindingResolver resolver = new JXLSBindingResolver();

    resolver.resolve(params, ctx, templateBasedDocument);

    File workingDir = getWorkingDir();
    File generated = new File(workingDir, "JXLSresult-" + System.currentTimeMillis());
    generated.createNewFile();

    File input = new File(workingDir, "JXLSInput-" + System.currentTimeMillis());
    input.createNewFile();

    sourceTemplateBlob.transferTo(input);

    XLSTransformer transformer = new XLSTransformer();

    transformer.transformXLS(input.getAbsolutePath(), ctx, generated.getAbsolutePath());

    input.delete();

    Blob newBlob = new FileBlob(generated);

    String templateFileName = sourceTemplateBlob.getFilename();

    // set the output file name
    String targetFileExt = FileUtils.getFileExtension(templateFileName);
    String targetFileName =
        FileUtils.getFileNameNoExt(templateBasedDocument.getAdaptedDoc().getTitle());
    targetFileName = targetFileName + "." + targetFileExt;
    newBlob.setFilename(targetFileName);

    // mark the file for automatic deletion on GC
    Framework.trackFile(generated, newBlob);
    return newBlob;
  }
  @Override
  public Blob renderTemplate(TemplateBasedDocument templateBasedDocument, String templateName)
      throws IOException {

    Blob sourceTemplateBlob = getSourceTemplateBlob(templateBasedDocument, templateName);

    // load the template
    IXDocReport report;
    try {
      report =
          XDocReportRegistry.getRegistry()
              .loadReport(sourceTemplateBlob.getStream(), TemplateEngineKind.Freemarker, false);
    } catch (XDocReportException e) {
      throw new IOException(e);
    }

    // manage parameters
    List<TemplateInput> params = templateBasedDocument.getParams(templateName);
    FieldsMetadata metadata = new FieldsMetadata();
    for (TemplateInput param : params) {
      if (param.getType() == InputType.PictureProperty) {
        metadata.addFieldAsImage(param.getName());
      }
    }
    report.setFieldsMetadata(metadata);

    // fill Freemarker context
    DocumentModel doc = templateBasedDocument.getAdaptedDoc();
    Map<String, Object> ctx = fmContextBuilder.build(doc, templateName);

    XDocReportBindingResolver resolver = new XDocReportBindingResolver(metadata);
    resolver.resolve(params, ctx, templateBasedDocument);

    // add default context vars
    IContext context;
    try {
      context = report.createContext();
    } catch (XDocReportException e) {
      throw new IOException(e);
    }
    for (String key : ctx.keySet()) {
      context.put(key, ctx.get(key));
    }
    // add automatic loop on audit entries
    metadata.addFieldAsList("auditEntries.principalName");
    metadata.addFieldAsList("auditEntries.eventId");
    metadata.addFieldAsList("auditEntries.eventDate");
    metadata.addFieldAsList("auditEntries.docUUID");
    metadata.addFieldAsList("auditEntries.docPath");
    metadata.addFieldAsList("auditEntries.docType");
    metadata.addFieldAsList("auditEntries.category");
    metadata.addFieldAsList("auditEntries.comment");
    metadata.addFieldAsList("auditEntries.docLifeCycle");
    metadata.addFieldAsList("auditEntries.repositoryId");

    File workingDir = getWorkingDir();
    File generated = new File(workingDir, "XDOCReportresult-" + System.currentTimeMillis());
    generated.createNewFile();

    OutputStream out = new FileOutputStream(generated);

    try {
      report.process(context, out);
    } catch (XDocReportException e) {
      throw new IOException(e);
    }

    Blob newBlob = Blobs.createBlob(generated);

    String templateFileName = sourceTemplateBlob.getFilename();

    // set the output file name
    String targetFileExt = FileUtils.getFileExtension(templateFileName);
    String targetFileName =
        FileUtils.getFileNameNoExt(templateBasedDocument.getAdaptedDoc().getTitle());
    targetFileName = targetFileName + "." + targetFileExt;
    newBlob.setFilename(targetFileName);

    // mark the file for automatic deletion on GC
    Framework.trackFile(generated, newBlob);
    return newBlob;
  }
  @Test
  public void testBuildAndParse() throws Exception {

    String termsAndConditions = "You have to be crazy to use this package";

    PackageBuilder builder = new PackageBuilder();
    builder.name("nuxeo-automation").version("5.3.2").type(PackageType.ADDON);
    builder.platform("dm-5.3.2");
    builder.platform("dam-5.3.2");
    builder.dependency("nuxeo-core:5.3.1:5.3.2");
    builder.dependency("nuxeo-runtime:5.3.1");
    builder.title("Nuxeo Automation");
    builder.description(
        "A service that enable building complex business logic on top of Nuxeo services using scriptable operation chains");
    builder.classifier("Open Source");
    builder.vendor("Nuxeo");
    builder.installer(InstallTask.class.getName(), true);
    builder.uninstaller(UninstallTask.class.getName(), true);
    builder.addLicense("My test license. All rights reserved.");
    File file = File.createTempFile("nxinstall-file-", ".tmp");
    Framework.trackFile(file, builder);
    File tofile = File.createTempFile("nxinstall-tofile-", ".tmp");
    Framework.trackFile(tofile, builder);
    builder.addInstallScript(
        "<install>\n  <copy file=\""
            + file.getAbsolutePath()
            + "\" tofile=\""
            + tofile.getAbsolutePath()
            + "\" overwrite=\"true\"/>\n</install>\n");

    builder.addTermsAndConditions(termsAndConditions);
    builder.hotReloadSupport(true);
    builder.supported(true);
    builder.validationState(NuxeoValidationState.INPROCESS);
    builder.productionState(ProductionState.PRODUCTION_READY);
    builder.requireTermsAndConditionsAcceptance(true);
    builder.visibility(PackageVisibility.MARKETPLACE);

    // test on package def
    String manifest = builder.buildManifest();
    // System.out.println(manifest);
    XMap xmap = StandaloneUpdateService.createXmap();
    InputStream xmlIn = new ByteArrayInputStream(manifest.getBytes());
    PackageDefinitionImpl packageDef = (PackageDefinitionImpl) xmap.load(xmlIn);
    assertEquals("nuxeo-automation", packageDef.getName());
    assertEquals("Nuxeo", packageDef.getVendor());
    assertEquals(NuxeoValidationState.INPROCESS, packageDef.getValidationState());
    assertEquals(ProductionState.PRODUCTION_READY, packageDef.getProductionState());
    assertTrue(packageDef.requireTermsAndConditionsAcceptance());
    assertTrue(packageDef.isSupported());
    assertTrue(packageDef.supportsHotReload());
    assertEquals(PackageVisibility.MARKETPLACE, packageDef.getVisibility());

    // test on real unziped package
    File zipFile = builder.build();
    String tmpDirPath =
        System.getProperty("java.io.tmpdir") + "/TestPkg" + System.currentTimeMillis();
    File tmpDir = new File(tmpDirPath);
    tmpDir.mkdirs();
    ZipUtils.unzip(zipFile, tmpDir);
    LocalPackage pkg = new LocalPackageImpl(tmpDir, PackageState.REMOTE, service);
    Framework.trackFile(tmpDir, pkg);
    assertEquals(termsAndConditions, pkg.getTermsAndConditionsContent());
    assertEquals("nuxeo-automation", pkg.getName());
    assertEquals("Nuxeo", pkg.getVendor());
    assertEquals(NuxeoValidationState.INPROCESS, pkg.getValidationState());
    assertEquals(ProductionState.PRODUCTION_READY, pkg.getProductionState());
    assertTrue(pkg.requireTermsAndConditionsAcceptance());
    assertTrue(pkg.isSupported());
    assertTrue(pkg.supportsHotReload());
    assertEquals(PackageVisibility.MARKETPLACE, pkg.getVisibility());
  }