mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Auto detect presence of external dependencies (LibreOffice etc) and disable/enable features dynamically (#2082)
* Create ExternalAppDepConfig.java * Update EndpointConfiguration.java * Hardening suggestions for Stirling-PDF / ExternalAppDepConfig (#2083) Switch order of literals to prevent NullPointerException Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> --------- Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									a10d06b693
								
							
						
					
					
						commit
						89da2a5c01
					
				@ -5,6 +5,7 @@ import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
@ -42,7 +43,7 @@ public class EndpointConfiguration {
 | 
			
		||||
 | 
			
		||||
    public void disableEndpoint(String endpoint) {
 | 
			
		||||
        if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
 | 
			
		||||
            logger.info("Disabling {}", endpoint);
 | 
			
		||||
            logger.debug("Disabling {}", endpoint);
 | 
			
		||||
            endpointStatuses.put(endpoint, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -76,6 +77,23 @@ public class EndpointConfiguration {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void logDisabledEndpointsSummary() {
 | 
			
		||||
        List<String> disabledList =
 | 
			
		||||
                endpointStatuses.entrySet().stream()
 | 
			
		||||
                        .filter(entry -> !entry.getValue()) // only get disabled endpoints (value
 | 
			
		||||
                        // is false)
 | 
			
		||||
                        .map(Map.Entry::getKey)
 | 
			
		||||
                        .sorted()
 | 
			
		||||
                        .collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        if (!disabledList.isEmpty()) {
 | 
			
		||||
            logger.info(
 | 
			
		||||
                    "Total disabled endpoints: {}. Disabled endpoints: {}",
 | 
			
		||||
                    disabledList.size(),
 | 
			
		||||
                    String.join(", ", disabledList));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void init() {
 | 
			
		||||
        // Adding endpoints to "PageOps" group
 | 
			
		||||
        addEndpointToGroup("PageOps", "remove-pages");
 | 
			
		||||
@ -163,14 +181,12 @@ public class EndpointConfiguration {
 | 
			
		||||
 | 
			
		||||
        // python
 | 
			
		||||
        addEndpointToGroup("Python", "extract-image-scans");
 | 
			
		||||
        addEndpointToGroup("Python", REMOVE_BLANKS);
 | 
			
		||||
        addEndpointToGroup("Python", "html-to-pdf");
 | 
			
		||||
        addEndpointToGroup("Python", "url-to-pdf");
 | 
			
		||||
        addEndpointToGroup("Python", "pdf-to-img");
 | 
			
		||||
 | 
			
		||||
        // openCV
 | 
			
		||||
        addEndpointToGroup("OpenCV", "extract-image-scans");
 | 
			
		||||
        addEndpointToGroup("OpenCV", REMOVE_BLANKS);
 | 
			
		||||
 | 
			
		||||
        // LibreOffice
 | 
			
		||||
        addEndpointToGroup("LibreOffice", "repair");
 | 
			
		||||
@ -230,6 +246,17 @@ public class EndpointConfiguration {
 | 
			
		||||
        addEndpointToGroup("Javascript", "sign");
 | 
			
		||||
        addEndpointToGroup("Javascript", "compare");
 | 
			
		||||
        addEndpointToGroup("Javascript", "adjust-contrast");
 | 
			
		||||
 | 
			
		||||
        // Ghostscript dependent endpoints
 | 
			
		||||
        addEndpointToGroup("Ghostscript", "compress-pdf");
 | 
			
		||||
        addEndpointToGroup("Ghostscript", "pdf-to-pdfa");
 | 
			
		||||
 | 
			
		||||
        // Weasyprint dependent endpoints
 | 
			
		||||
        addEndpointToGroup("Weasyprint", "html-to-pdf");
 | 
			
		||||
        addEndpointToGroup("Weasyprint", "url-to-pdf");
 | 
			
		||||
 | 
			
		||||
        // Pdftohtml dependent endpoints
 | 
			
		||||
        addEndpointToGroup("Pdftohtml", "pdf-to-html");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void processEnvironmentConfigs() {
 | 
			
		||||
@ -251,5 +278,9 @@ public class EndpointConfiguration {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Set<String> getEndpointsForGroup(String group) {
 | 
			
		||||
        return endpointGroups.getOrDefault(group, new HashSet<>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final String REMOVE_BLANKS = "remove-blanks";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,146 @@
 | 
			
		||||
package stirling.software.SPDF.config;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 | 
			
		||||
@Configuration
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class ExternalAppDepConfig {
 | 
			
		||||
    @Autowired private EndpointConfiguration endpointConfiguration;
 | 
			
		||||
 | 
			
		||||
    private boolean isCommandAvailable(String command) {
 | 
			
		||||
        try {
 | 
			
		||||
            ProcessBuilder processBuilder = new ProcessBuilder();
 | 
			
		||||
            if (System.getProperty("os.name").toLowerCase().contains("windows")) {
 | 
			
		||||
                processBuilder.command("where", command);
 | 
			
		||||
            } else {
 | 
			
		||||
                processBuilder.command("which", command);
 | 
			
		||||
            }
 | 
			
		||||
            Process process = processBuilder.start();
 | 
			
		||||
            int exitCode = process.waitFor();
 | 
			
		||||
            return exitCode == 0;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.debug("Error checking for command {}: {}", command, e.getMessage());
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final Map<String, List<String>> commandToGroupMapping =
 | 
			
		||||
            new HashMap<>() {
 | 
			
		||||
                {
 | 
			
		||||
                    put("gs", List.of("Ghostscript"));
 | 
			
		||||
                    put("soffice", List.of("LibreOffice"));
 | 
			
		||||
                    put("ocrmypdf", List.of("OCRmyPDF"));
 | 
			
		||||
                    put("weasyprint", List.of("Weasyprint"));
 | 
			
		||||
                    put("pdftohtml", List.of("Pdftohtml"));
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
    private List<String> getAffectedFeatures(String group) {
 | 
			
		||||
        return endpointConfiguration.getEndpointsForGroup(group).stream()
 | 
			
		||||
                .map(endpoint -> formatEndpointAsFeature(endpoint))
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String formatEndpointAsFeature(String endpoint) {
 | 
			
		||||
        // First replace common terms
 | 
			
		||||
        String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
 | 
			
		||||
 | 
			
		||||
        // Split into words and capitalize each word
 | 
			
		||||
        return Arrays.stream(feature.split("\\s+"))
 | 
			
		||||
                .map(word -> capitalizeWord(word))
 | 
			
		||||
                .collect(Collectors.joining(" "));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String capitalizeWord(String word) {
 | 
			
		||||
        if (word.isEmpty()) {
 | 
			
		||||
            return word;
 | 
			
		||||
        }
 | 
			
		||||
        if ("pdf".equalsIgnoreCase(word)) {
 | 
			
		||||
            return "PDF";
 | 
			
		||||
        }
 | 
			
		||||
        return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkDependencyAndDisableGroup(String command) {
 | 
			
		||||
        boolean isAvailable = isCommandAvailable(command);
 | 
			
		||||
        if (!isAvailable) {
 | 
			
		||||
            List<String> affectedGroups = commandToGroupMapping.get(command);
 | 
			
		||||
 | 
			
		||||
            if (affectedGroups != null) {
 | 
			
		||||
                for (String group : affectedGroups) {
 | 
			
		||||
                    List<String> affectedFeatures = getAffectedFeatures(group);
 | 
			
		||||
                    endpointConfiguration.disableGroup(group);
 | 
			
		||||
                    log.warn(
 | 
			
		||||
                            "Missing dependency: {} - Disabling group: {} (Affected features: {})",
 | 
			
		||||
                            command,
 | 
			
		||||
                            group,
 | 
			
		||||
                            affectedFeatures != null && !affectedFeatures.isEmpty()
 | 
			
		||||
                                    ? String.join(", ", affectedFeatures)
 | 
			
		||||
                                    : "unknown");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void checkDependencies() {
 | 
			
		||||
 | 
			
		||||
        // Check core dependencies
 | 
			
		||||
        checkDependencyAndDisableGroup("gs");
 | 
			
		||||
        checkDependencyAndDisableGroup("soffice");
 | 
			
		||||
        checkDependencyAndDisableGroup("ocrmypdf");
 | 
			
		||||
        checkDependencyAndDisableGroup("weasyprint");
 | 
			
		||||
        checkDependencyAndDisableGroup("pdftohtml");
 | 
			
		||||
 | 
			
		||||
        // Special handling for Python/OpenCV dependencies
 | 
			
		||||
        boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
 | 
			
		||||
        if (!pythonAvailable) {
 | 
			
		||||
            List<String> pythonFeatures = getAffectedFeatures("Python");
 | 
			
		||||
            List<String> openCVFeatures = getAffectedFeatures("OpenCV");
 | 
			
		||||
 | 
			
		||||
            endpointConfiguration.disableGroup("Python");
 | 
			
		||||
            endpointConfiguration.disableGroup("OpenCV");
 | 
			
		||||
            log.warn(
 | 
			
		||||
                    "Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}",
 | 
			
		||||
                    String.join(", ", pythonFeatures),
 | 
			
		||||
                    String.join(", ", openCVFeatures));
 | 
			
		||||
        } else {
 | 
			
		||||
            // If Python is available, check for OpenCV
 | 
			
		||||
            try {
 | 
			
		||||
                ProcessBuilder processBuilder = new ProcessBuilder();
 | 
			
		||||
                if (System.getProperty("os.name").toLowerCase().contains("windows")) {
 | 
			
		||||
                    processBuilder.command("python", "-c", "import cv2");
 | 
			
		||||
                } else {
 | 
			
		||||
                    processBuilder.command("python3", "-c", "import cv2");
 | 
			
		||||
                }
 | 
			
		||||
                Process process = processBuilder.start();
 | 
			
		||||
                int exitCode = process.waitFor();
 | 
			
		||||
                if (exitCode != 0) {
 | 
			
		||||
                    List<String> openCVFeatures = getAffectedFeatures("OpenCV");
 | 
			
		||||
                    endpointConfiguration.disableGroup("OpenCV");
 | 
			
		||||
                    log.warn(
 | 
			
		||||
                            "OpenCV not available in Python - Disabling OpenCV features: {}",
 | 
			
		||||
                            String.join(", ", openCVFeatures));
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                List<String> openCVFeatures = getAffectedFeatures("OpenCV");
 | 
			
		||||
                endpointConfiguration.disableGroup("OpenCV");
 | 
			
		||||
                log.warn(
 | 
			
		||||
                        "Error checking OpenCV: {} - Disabling OpenCV features: {}",
 | 
			
		||||
                        e.getMessage(),
 | 
			
		||||
                        String.join(", ", openCVFeatures));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        endpointConfiguration.logDisabledEndpointsSummary();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user