[OfficeOnline] How to implement document online preview in Java (3)

Article directory

  • introduction
  • maven introduces dependencies
  • 1. Quick setup
  • 2. Document Converter

Introduction

In the previous article, we introduced how to install the third-party tool LibreOffice. So, this article will introduce how to use JODConverter in Java to achieve document preview.

maven introduces dependencies

pom file

<properties>
     <jodconverter.version>4.4.6</jodconverter.version>
</properties>
<dependency>
     <groupId>org.jodconverter</groupId>
     <artifactId>jodconverter-core</artifactId>
     <version>${jodconverter.version}</version>
 </dependency>
 <dependency>
     <groupId>org.jodconverter</groupId>
     <artifactId>jodconverter-local</artifactId>
     <version>${jodconverter.version}</version>
 </dependency>
 <dependency>
     <groupId>org.jodconverter</groupId>
     <artifactId>jodconverter-remote</artifactId>
     <version>${jodconverter.version}</version>
 </dependency>
 <!-- jodconverter automatic configuration class -->
 <dependency>
     <groupId>org.jodconverter</groupId>
     <artifactId>jodconverter-spring-boot-starter</artifactId>
     <version>${jodconverter.version}</version>
 </dependency>

Among them, jodconverter-spring-boot-starter is only used for rapid construction. There is no need to inject configuration and the integration is fast.

1. Quick setup

application.yml

jodconverter:
  local:
    enabled: true
    office-home: /opt/openoffice4/
    max-tasks-per-process: 10
    port-numbers: 8100

transfer

import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.jodconverter.core.DocumentConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@RestController
@Slf4j
@RequestMapping("/file")
public class FileController {<!-- -->

    // converter
    @Autowired
    private DocumentConverter converter;

    /**
     * Preview file
     *
     * @param request request object
     * @param response response object
     */
    @PostMapping(path = "/previewFile")
    public void previewFile(HttpServletRequest request,
                            HttpServletResponse response) {<!-- -->
        try {<!-- -->
            //input file
            File inputFile = new File("test.docx");
            //output file
            String outputFileName = "test.pdf";
            File outputFile = new File(outputFileName);
            converter.convert(inputFile).to(outputFile).execute();
            downloadFile(response, outputFileName);
        } catch (Exception e) {<!-- -->
            log.warn(e.toString());
        }
    }

    /**
     * download file
     *
     * @param filePath file path
     * @param response response
     * @throws IOException exception
     */
    public static void downloadFile(HttpServletResponse response, String filePath) {<!-- -->
        ServletOutputStream servletOutputStream = null;
        try {<!-- -->
            servletOutputStream = response.getOutputStream();
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + filePath);
            response.setContentType("application/octet-stream");
            //Get the full path
            String fullPath = filePath;
            File file = new File(fullPath);
            if (!file.exists()) {<!-- -->
                throw new FileNotFoundException("This file cannot be found: " + fullPath);
            }
            byte[] bytes = Files.readAllBytes(Paths.get(fullPath));
            servletOutputStream.write(bytes);
            servletOutputStream.flush();
        } catch (Exception e) {<!-- -->
            throw new RuntimeException(e.toString());
        } finally {<!-- -->
            IoUtil.close(servletOutputStream);
        }
    }

}

In this way, conversion functions can be built quickly. However, for some scenarios, we may need to dynamically enable or disable the Office conversion function based on actual needs. In order to meet this demand, it was originally envisioned that jodconverter.local.enabled=false could be used to disable Office. But after setting it up, an error occurred. Therefore, the following introduces a way to enable or disable the Office conversion function by configuring parameters, and combine it with the LibreOffice installation method to dynamically switch the interface calling method.

******************************
APPLICATION FAILED TO START
***************************

Description:

Field converter in service.impl.FileServiceImpl required a bean of type 'org.jodconverter.core.DocumentConverter' that could not be found.

The injection point has the following annotations:
        - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'org.jodconverter.core.DocumentConverter' in your configuration.

2. Document Converter

Through the @PostConstruct annotation, the office component service can be started when the project is started. By setting the enabling mode, you can flexibly call office component services, call the corresponding interfaces, and perform conversions. Part of the code refers to kkFileView.
https://gitee.com/kekingcn/file-online-preview

public class FileServiceImpl {<!-- -->

    private final OfficeToPdfConverter officeToPdfService;

    public FileServiceImpl(OfficeToPdfConverter officeToPdfService) {<!-- -->
        this.officeToPdfService = officeToPdfService;
    }

    /**
     * Preview file
     *
     * @param response
     * @param id
     */
    public void previewFile(HttpServletResponse response, Long id) {<!-- -->
        //Office service, perform conversion
        File file = new File(id);
        officeToPdfService.converterFile(url, outputFilePath);
        //return file stream
        FileUtils.downloadFile(response, outputFilePath);
    }
}

application.yml

office:
  plugin:
    # Enable mode, false-disable, home-local, remote-remote
    enabled: true
    home: /opt/libreoffice7.5/
    remote: http://ip:port/lool/convert-to/pdf
    server:
      ports: 2001,2012
    task:
      timeout: 5m

Document converter

import org.apache.commons.lang3.StringUtils;
import org.jodconverter.core.office.InstalledOfficeManagerHolder;
import org.jodconverter.core.office.OfficeException;
import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.core.office.OfficeUtils;
import org.jodconverter.core.util.OSUtils;
import org.jodconverter.local.office.LocalOfficeManager;
import org.jodconverter.remote.office.RemoteOfficeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Objects;

/**
 * Create document converter
 *
 * @author cy
 * @since 2023-08-02
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OfficePluginManager {<!-- -->

    private final Logger logger = LoggerFactory.getLogger(OfficePluginManager.class);

    private OfficeManager officeManager;

    @Value("${office.plugin.home:null}")
    private String localOfficeHome;

    @Value("${office.plugin.enabled:home}")
    private String officeHomeEnabled;

    @Value("${office.plugin.remote}")
    private String remoteOfficeHome;

    @Value("${office.plugin.server.ports:2001,2002}")
    private String serverPorts;

    @Value("${office.plugin.task.timeout:5m}")
    private String timeOut;

    /**
     * Start the Office component process
     */
    @PostConstruct
    public void startOfficeManager() throws OfficeException {<!-- -->
        //Get office component configuration information
        if (Objects.equals("false", officeHomeEnabled)) {<!-- -->
            logger.warn("The office component cannot be found or the office component is not enabled");
        } else if (Objects.equals("home", officeHomeEnabled)) {<!-- -->
            logger.warn("Local office component is running");
            File officeHome = new File(localOfficeHome);
            boolean killOffice = killProcess();
            if (killOffice) {<!-- -->
                logger.warn("A running office process has been detected and the process has been automatically terminated");
            }
            String[] portsString = serverPorts.split(",");
            int[] ports = Arrays.stream(portsString).mapToInt(Integer::parseInt).toArray();
            long timeout = DurationStyle.detectAndParse(timeOut).toMillis();
            officeManager = LocalOfficeManager.builder()
                    .officeHome(officeHome)
                    .portNumbers(ports)
                    .processTimeout(timeout)
                    .build();
        } else if (Objects.equals("remote", officeHomeEnabled)) {<!-- -->
            logger.warn("Remote office component running");
            officeManager = RemoteOfficeManager.make(remoteOfficeHome);
        }
        try {<!-- -->
            if (officeManager != null) {<!-- -->
                officeManager.start();
                InstalledOfficeManagerHolder.setInstance(officeManager);
            }
        } catch (Exception e) {<!-- -->
            logger.error("Failed to start the office component, please check whether the office component is available");
            throw e;
        }
    }

    /**
     * End the Office component process
     */
    private boolean killProcess() {<!-- -->
        boolean flag = false;
        try {<!-- -->
            if (OSUtils.IS_OS_WINDOWS) {<!-- -->
                Process p = Runtime.getRuntime().exec("cmd /c tasklist ");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream os = p.getInputStream();
                byte[] b = new byte[256];
                while (os.read(b) > 0) {<!-- -->
                    baos.write(b);
                }
                String s = baos.toString();
                if (s.contains("soffice.bin")) {<!-- -->
                    Runtime.getRuntime().exec("taskkill /im " + "soffice.bin" + " /f");
                    flag = true;
                }
            } else if (OSUtils.IS_OS_MAC || OSUtils.IS_OS_MAC_OSX) {<!-- -->
                Process p = Runtime.getRuntime().exec(new String[]{<!-- -->"sh", "-c", "ps -ef | grep " + "soffice. bin"});
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream os = p.getInputStream();
                byte[] b = new byte[256];
                while (os.read(b) > 0) {<!-- -->
                    baos.write(b);
                }
                String s = baos.toString();
                if (StringUtils.ordinalIndexOf(s, "soffice.bin", 3) > 0) {<!-- -->
                    String[] cmd = {<!-- -->"sh", "-c", "kill -15 `ps -ef|grep " + "soffice.bin" + " |awk 'NR==1{print $2}'`"};
                    Runtime.getRuntime().exec(cmd);
                    flag = true;
                }
            } else {<!-- -->
                Process p = Runtime.getRuntime().exec(new String[]{<!-- -->"sh", "-c", "ps -ef | grep " + "soffice. bin" + " |grep -v grep | wc -l"});
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream os = p.getInputStream();
                byte[] b = new byte[256];
                while (os.read(b) > 0) {<!-- -->
                    baos.write(b);
                }
                String s = baos.toString();
                if (!s.startsWith("0")) {<!-- -->
                    String[] cmd = {<!-- -->"sh", "-c", "ps -ef | grep soffice.bin | grep -v grep | awk '{print \\ "kill -9 "$2}' | sh"};
                    Runtime.getRuntime().exec(cmd);
                    flag = true;
                }
            }
        } catch (IOException e) {<!-- -->
            logger.error("Detection of office process exception", e);
        }
        return flag;
    }

    @PreDestroy
    public void destroyOfficeManager() {<!-- -->
        if (null != officeManager & amp; & amp; officeManager.isRunning()) {<!-- -->
            logger.info("Shutting down office process");
            OfficeUtils.stopQuietly(officeManager);
        }
    }

}

Office convert pdf tool class

import org.jodconverter.core.office.InstalledOfficeManagerHolder;
import org.jodconverter.core.office.OfficeException;
import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.local.LocalConverter;
import org.jodconverter.local.office.LocalOfficeManager;
import org.jodconverter.remote.RemoteConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 *Office convert pdf
 *
 * @author cy
 * @since 2023-08-02
 */
@Component
public class OfficeToPdfConverter {<!-- -->

    private final static Logger logger = LoggerFactory.getLogger(OfficeToPdfConverter.class);

    /**
     * Convert files
     *
     * @param inputFile
     * @param outputFilePath_end
     */
    public void converterFile(InputStream inputFile, String outputFilePath_end) {<!-- -->
        logger.info("Executing local conversion task [docx -> pdf]...");
        File outputFile = new File(outputFilePath_end);
        // If the target path does not exist, create a new path
        if (!outputFile.getParentFile().exists() & amp; & amp; !outputFile.getParentFile().mkdirs()) {<!-- -->
            logger.error("Failed to create directory [{}], please check directory permissions!", outputFilePath_end);
        }
        //Set restriction attributes
        Map<String, Object> filterData = new HashMap<>();
        Map<String, Object> customProperties = new HashMap<>();
        //encryption
        filterData.put("EncryptFile", true);
        //Restrict pages
        filterData.put("PageRange", false);
        //watermark
        filterData.put("Watermark", false);
        //Image Compression
        filterData.put("Quality", true);
        //DPI
        filterData.put("MaxImageResolution", true);
        //Export bookmarks
        filterData.put("ExportBookmarks", true);
        //Annotation as PDF annotation
        filterData.put("ExportNotes", true);
        customProperties.put("FilterData", filterData);
        OfficeManager officeManager = null;
        try {<!-- -->
            officeManager = InstalledOfficeManagerHolder.getInstance();
            if (officeManager instanceof LocalOfficeManager) {<!-- -->
                LocalConverter.Builder builder;
                builder = LocalConverter.builder().storeProperties(customProperties);
                builder.build().convert(inputFile).to(outputFile).execute();
            } else {<!-- -->
                RemoteConverter.builder().build().convert(inputFile).to(outputFile).execute();
            }
        } catch (OfficeException e) {<!-- -->
            logger.error("Conversion failed {} ", e.toString());
            throw new RuntimeException("Conversion failed!", e);
        }
    }

}

File tool class, download files, return file stream method

import cn.hutool.core.io.IoUtil;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @author cy
 * @since 2023-07-03
 */
public class FileUtil {<!-- -->
    
    /**
     * download file
     *
     * @param filePath file path
     * @param response response
     * @throws IOException exception
     */
    public static void downloadFile(HttpServletResponse response, String filePath) {<!-- -->
        ServletOutputStream servletOutputStream = null;
        try {<!-- -->
            servletOutputStream = response.getOutputStream();
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + filePath);
            response.setContentType("application/octet-stream");
            //Get the full path
            String fullPath = filePath;
            File file = new File(fullPath);
            if (!file.exists()) {<!-- -->
                throw new FileNotFoundException("This file cannot be found: " + fullPath);
            }
            byte[] bytes = Files.readAllBytes(Paths.get(fullPath));
            servletOutputStream.write(bytes);
            servletOutputStream.flush();
        } catch (Exception e) {<!-- -->
            throw new RuntimeException(e.toString());
        } finally {<!-- -->
            IoUtil.close(servletOutputStream);
        }
    }

}