LugsImportService.java

/*
 * Copyright © 2014-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site.services.lugs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.ctan.site.CtanConfiguration.LugsConfig;
import org.ctan.site.domain.site.Lug;
import org.ctan.site.stores.LugStore;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.NonNull;

/**
 * This service deals with the LUG database – Local User's Groups.
 *
 * @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
 */
public class LugsImportService {

    /**
     * The field <code>url</code> contains the URL of the lugs database.
     */
    private String url;

    /**
     * The field <code>store</code> contains the underlying store for LUGs.
     */
    private LugStore store;

    /**
     * This is the constructor for the class <code>LugsImportService</code>.
     *
     * @param config the configuration
     * @param store the store
     */
    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW")
    public LugsImportService(@NonNull LugsConfig config,
        @NonNull LugStore store) {

        this.url = config.getUrl();
        this.store = store;
    }

    /**
     * Download an Apache directory listing and extract the files contained.
     *
     * @param url the URL the query
     *
     * @return the list of files contained
     *
     * @throws IOException in case of an I/O error
     * @throws MalformedURLException in case of an error
     * @throws URISyntaxException in case of an error
     */
    private List<String> fileList(String url)
        throws MalformedURLException,
            IOException,
            URISyntaxException {

        List<String> list = new ArrayList<>();
        var doc = Jsoup.parse(new URI(url).toURL(), 0);
        for (Element el : doc.getElementsByTag("a")) {
            var href = el.attr("href");
            if (href.length() <= 3) {
                list.add(href);
            }
        }
        return list;
    }

    private String normalizeAddress(String a) {

        return a.replaceAll(";", "\\\\");
    }

    private String normalizeEmail(String e) {

        return e.replaceAll(" at ", "@");
    }

    /**
     * Contact the NTG site to download the LUG database and store the entries
     * in the site database.
     *
     * @throws IOException in case of an I/O error
     * @throws InvocationTargetException in case of an error
     * @throws IllegalArgumentException in case of an error
     * @throws IllegalAccessException in case of an error
     * @throws MalformedURLException in case of an error
     * @throws NoSuchMethodException in case of an error
     * @throws SecurityException in case of an error
     * @throws URISyntaxException in case of an error
     */
    public void update()
        throws MalformedURLException,
            IOException,
            NoSuchMethodException,
            SecurityException,
            IllegalAccessException,
            IllegalArgumentException,
            InvocationTargetException,
            URISyntaxException {

        var lugs = store.findAll();
        for (String code : fileList(url)) {
            updateLug(new URI(url + code).toURL());
            lugs.removeIf(lug -> lug.getCode().equals(code));
        }
        store.drop(lugs);
    }

    /**
     * Parse the URL given and make an associated Lug instance.
     *
     * @param url the URL to read from
     * @return the new or updated Lug instance
     *
     * @throws IOException in case of an error
     * @throws SecurityException in case of an error
     * @throws NoSuchMethodException in case of an error
     * @throws InvocationTargetException in case of an error
     * @throws IllegalArgumentException in case of an error
     * @throws IllegalAccessException in case of an error
     */
    private Lug updateLug(URL url)
        throws IOException,
            NoSuchMethodException,
            SecurityException,
            IllegalAccessException,
            IllegalArgumentException,
            InvocationTargetException {

        var lug = Lug.builder().build();
        try (var in = new BufferedReader(
            new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
            String s;
            while ((s = in.readLine()) != null) {
                s = s.trim();
                if (s.length() == 0 || s.startsWith("#")) {
                    continue;
                }
                var kv = s.split("\t");
                if (kv.length < 2 || kv[0].length() == 0
                    || kv[1].length() == 0) {
                    continue;
                }
                var val = kv[1];
                switch (kv[0]) {
                    case "addr":
                        lug.setAddress(normalizeAddress(val));
                        continue;
                    case "bacc":
                        lug.getBank().setAccount(val);
                        continue;
                    case "baddr":
                        lug.getBank().setAddress(normalizeAddress(val));
                        continue;
                    case "bank":
                        lug.getBank().setName(val);
                        continue;
                    case "bcode":
                        lug.getBank().setSwift(val);
                        continue;
                    case "code":
                        lug = store.findOrCreateByCode(val);
                        lug.setCountry(lug.getCode().substring(0, 2));
                        continue;
                    case "brout":
                        lug.getBank().setBic(val);
                        continue;
                    case "editor":
                        lug.getPeriodical().setEditorName(val);
                        continue;
                    case "eemail":
                        lug.getPeriodical().setEditorEmail(normalizeEmail(val));
                        continue;
                    case "email":
                        lug.setEmail(normalizeEmail(val));
                        continue;
                    case "faddr":
                        lug.getFinancial().setAddress(normalizeAddress(val));
                        continue;
                    case "femail":
                        lug.getFinancial().setEmail(normalizeEmail(val));
                        continue;
                    case "ffax":
                        lug.getFinancial().setFax(val);
                        continue;
                    case "fname":
                        lug.getFinancial().setName(val);
                        continue;
                    case "fphone":
                        lug.getFinancial().setPhone(val);
                        continue;
                    case "fpos":
                        lug.getFinancial().setPosition(val);
                        continue;
                    case "full":
                        lug.setFullName(val);
                        continue;
                    case "gaddr":
                        lug.getGeneral().setAddress(normalizeAddress(val));
                        continue;
                    case "gemail":
                        lug.getGeneral().setEmail(normalizeEmail(val));
                        continue;
                    case "gfax":
                        lug.getGeneral().setFax(val);
                        continue;
                    case "gname":
                        lug.getGeneral().setName(val);
                        continue;
                    case "gphone":
                        lug.getGeneral().setPhone(val);
                        continue;
                    case "gpos":
                        lug.getGeneral().setPosition(val);
                        continue;
                    case "lang":
                        lug.setLanguages(val);
                        continue;
                    case "list":
                        lug.setMailinglist(val);
                        continue;
                    case "members":
                        lug.setNumberOfMembers(Integer.parseInt(val));
                        continue;
                    case "paddr":
                        lug.getPolicy().setAddress(normalizeAddress(val));
                        continue;
                    case "pemail":
                        lug.getPolicy().setEmail(normalizeEmail(val));
                        continue;
                    case "pfax":
                        lug.getPolicy().setFax(val);
                        continue;
                    case "pname":
                        lug.getPolicy().setName(val);
                        continue;
                    case "pphone":
                        lug.getPolicy().setPhone(val);
                        continue;
                    case "ppos":
                        lug.getPolicy().setPosition(val);
                        continue;
                    case "publ":
                        lug.getPeriodical().setTitle(val);
                        continue;
                    case "short":
                        lug.setShortName(val);
                        continue;
                    case "subscr":
                        lug.setMailinglistSubscribe(val);
                        continue;
                    case "www":
                        lug.setWebsite(val);
                        continue;
                    default:
                        throw new IOException(
                            "Unknown lug attribute: " + kv[0]);
                }
            }
        }
        return lug;
    }
}