001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.q2.install;
020
021import java.io.File;
022import java.io.IOException;
023import java.net.JarURLConnection;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URL;
027import java.security.MessageDigest;
028import java.security.NoSuchAlgorithmException;
029import java.util.*;
030import java.util.jar.JarEntry;
031import java.util.jar.JarFile;
032import java.util.stream.Collectors;
033
034/**
035 * @author vsalaman
036 */
037public class ModuleUtils
038{
039    private static final String MODULES_UUID_DIR = "META-INF/modules/uuids/";
040    private static final String MODULES_RKEYS_DIR = "META-INF/modules/rkeys/";
041
042    public static List<String> getModuleEntries(String prefix) throws IOException {
043        List<String> result = new ArrayList<>();
044
045        Enumeration<URL> urls = ModuleUtils.class.getClassLoader().getResources(prefix);
046        while (urls.hasMoreElements()) {
047            URL url = urls.nextElement();
048            if (url == null) continue;
049
050            try {
051                List<String> entries;
052                String protocol = url.getProtocol();
053                if ("jar".equals(protocol)) {
054                    entries = resolveModuleEntriesFromJar(url, prefix);
055                } else if ("file".equals(protocol)) {
056                    entries = resolveModuleEntriesFromFiles(url, prefix);
057                } else {
058                    // Unsupported protocol, skip with optional logging
059                    continue;
060                }
061                result.addAll(entries);
062            } catch (URISyntaxException e) {
063                throw new IOException("Bad URL: " + url, e);
064            }
065        }
066        return result;
067    }
068
069    public static List<String> getModulesUUIDs() throws IOException {
070        return getModuleEntries(MODULES_UUID_DIR).stream()
071          .sorted()
072          .map(p -> p.substring(MODULES_UUID_DIR.length()))
073          .collect(Collectors.toList());
074    }
075
076    public static List<String> getRKeys () throws IOException {
077        return ModuleUtils.getModuleEntries(MODULES_RKEYS_DIR)
078          .stream()
079          .sorted()
080          .map(p -> p.substring(MODULES_RKEYS_DIR.length()))
081          .collect(Collectors.toList());
082    }
083
084    public static String getSystemHash() throws IOException, NoSuchAlgorithmException {
085        MessageDigest digest = MessageDigest.getInstance("SHA-256");
086        List<String> uuids = getModulesUUIDs();
087        if (uuids.isEmpty()) return "";
088
089        uuids.forEach(uuid -> digest.update(uuid.getBytes()));
090        return Base64.getEncoder().encodeToString(digest.digest());
091    }
092
093    private static List<String> resolveModuleEntriesFromFiles(URL url, String prefix)
094      throws URISyntaxException {
095        String normalizedPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
096        List<String> resourceList = new ArrayList<>();
097        File dir = new File(url.toURI());
098        addFiles(dir, normalizedPrefix, resourceList);
099        return resourceList;
100    }
101
102    private static void addFiles(File dir, String prefix, List<String> resourceList) {
103        File[] files = dir.listFiles();
104        if (files == null) return;
105
106        for (File file : files) {
107            if (file.isDirectory()) {
108                addFiles(file, prefix + file.getName() + "/", resourceList);
109            } else {
110                resourceList.add(prefix + file.getName());
111            }
112        }
113    }
114
115    private static List<String> resolveModuleEntriesFromJar(URL url, String prefix)
116      throws IOException {
117        String normalizedPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
118        List<String> resourceList = new ArrayList<>();
119
120        JarURLConnection conn = (JarURLConnection) url.openConnection();
121        try (JarFile jarFile = conn.getJarFile()) {
122            Enumeration<JarEntry> entries = jarFile.entries();
123            while (entries.hasMoreElements()) {
124                JarEntry entry = entries.nextElement();
125                String name = entry.getName();
126                if (name.startsWith(normalizedPrefix) && !entry.isDirectory()) {
127                    resourceList.add(name);
128                }
129            }
130        }
131        return resourceList;
132    }
133}