public class UIFactory {

    private static final int LABEL_MIN_WIDTH = 150;
    private static final double TEXTFIELD_MIN_WIDTH = 170f;
    private static final double FONT_BIG = 14f;
    private static final int MARGIN_0 = 0;
    private static final int MARGIN_5 = 5;
    private static final int MARGIN_15 = 15;
    private static final int MARGIN_20 = 20;
    private static final int PREF_HEIGHT = 31;
    private static final int BGCOLOR = 244;
    private static final double FONTSCALE = 0.8;
    private static final int WEBVIEW_PREF_WIDTH = 200;
    private static final int WEBVIEW_MIN_WIDTH = 200;
    private static final int WEBVIEW_PREF_HEIGHT = 50;
    private static final int WEBVIEW_MIN_HEIGHT = 0;
    private static final int OUTPUTFORMAT_PREF_WIDTH = LABEL_MIN_WIDTH;
    private static final String DATAFORMAT_ID = "simpleDataformat";
    private static final String PROCESS_VAR = "process_var";

    public static String getDataFormatID() {
        return DATAFORMAT_ID;
    }

    /**
     * Creates a stack pane with content based on the selected service.
     *
     * @param type The selected service type
     * @param container The container node
     * @param outputFormats the Outputformats of the selected service
     *
     */
    public void fillSimpleWFS(
        VBox container,
        WFSMeta.StoredQuery type,
        List <OutputFormatModel> outputFormats
    ) {
        createSimpleWFS(
            type,
            container,
            outputFormats);
    }

    private void createSimpleWFS(
        WFSMeta.StoredQuery type,
        VBox container,
        List <OutputFormatModel> outputFormats
    ) {
        container.getChildren().clear();
        Label descriptionHead = new Label();
        descriptionHead.setText(I18n.getMsg("gui.description"));
        Font font = descriptionHead.getFont();
        descriptionHead.setFont(
            Font.font(font.getFamily(),
                FontWeight.BOLD,
                FONT_BIG));
        WebView description = new WebView();
        description.setFontScale(FONTSCALE);
        description.setPrefHeight(WEBVIEW_PREF_HEIGHT);
        description.setPrefWidth(WEBVIEW_PREF_WIDTH);
        description.setMinHeight(WEBVIEW_MIN_HEIGHT);
        description.setMinWidth(WEBVIEW_MIN_WIDTH);
        WebEngine engine = description.getEngine();
        java.lang.reflect.Field f;
        try {
            f = engine.getClass().getDeclaredField("page");
            f.setAccessible(true);
            com.sun.webkit.WebPage page =
                (com.sun.webkit.WebPage) f.get(engine);
            page.setBackgroundColor(
                (new java.awt.Color(BGCOLOR, BGCOLOR, BGCOLOR)).getRGB());
        } catch (NoSuchFieldException
            | SecurityException
            | IllegalArgumentException
            | IllegalAccessException e) {
            // Displays the webview with white background...
        }
        engine.loadContent(type.getAbstractDescription());

        container.getChildren().add(descriptionHead);
        container.getChildren().add(description);
        container.setMargin(descriptionHead,
            new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_5));
        container.setMargin(description,
            new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_15));

        for (Field entry : type.getParameters()) {
            HBox attributeItem = createAttributeItem(entry);
            container.getChildren().add(attributeItem);
        }
        Separator sep = new Separator();
        HBox outputFormatBox = new HBox();
        Label outputFormatLabel = new Label();
        outputFormatLabel.setText(I18n.format("gui.data-format"));
        ComboBox outputFormat = new ComboBox();
        outputFormat.setId(DATAFORMAT_ID);
        ObservableList<OutputFormatModel> out =
                FXCollections.observableArrayList();
        out.addAll(outputFormats);
        outputFormat.setItems(out);
        outputFormatLabel.setLabelFor(outputFormat);
        outputFormatLabel.setPrefWidth(OUTPUTFORMAT_PREF_WIDTH);
        outputFormatBox.getChildren().add(outputFormatLabel);
        outputFormatBox.getChildren().add(outputFormat);
        outputFormatBox.setMargin(outputFormatLabel,
                new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_15));
        outputFormatBox.setMargin(outputFormatLabel,
                new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_15));
        container.getChildren().add(sep);
        container.getChildren().add(outputFormatBox);
    }

    private HBox createAttributeItem(Field field) {
        HBox root = new HBox();
        root.setPrefHeight(PREF_HEIGHT);
        root.setMaxHeight(PREF_HEIGHT);
        Label label = new Label();
        label.setText(field.getName());
        label.setMinWidth(LABEL_MIN_WIDTH);
        TextField textField = new TextField();
        textField.setUserData(field.getName());
        textField.setId("parameter");
        textField.setMinWidth(TEXTFIELD_MIN_WIDTH);
        Label type = new Label();
        //TODO - Save the type w/o the replace
        type.setText(field.getType().replace("xsd:", "").replace("xs:", ""));
        type.setMinWidth(LABEL_MIN_WIDTH);
        root.setMargin(label,
            new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_15));
        root.setMargin(textField,
            new Insets(MARGIN_0, MARGIN_5, MARGIN_0, MARGIN_5));
        root.setMargin(type,
            new Insets(MARGIN_5, MARGIN_5, MARGIN_5, MARGIN_5));
        root.getChildren().add(label);
        root.getChildren().add(textField);
        root.getChildren().add(type);
        return root;
    }

    /**
     * Add new post process chain item.
     *
     * @param container The container
     */
    public void addChainAttribute(VBox container) {
        addChainAttribute(container, null);
    }

    private static void addRemoveButton(
        final VBox container,
        final VBox root,
        VBox dynroot,
        HBox subroot,
        ComboBox box) {

        Button remove = new Button(I18n.getMsg("gui.remove"));
        remove.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent e) {
                ObservableList<Node> items = container.getChildren();
                items.remove(root);
            }
        });
        subroot.getChildren().addAll(box, remove);
        subroot.setMargin(box, new Insets(MARGIN_5,
            MARGIN_5, MARGIN_5, MARGIN_20));
        subroot.setMargin(remove, new Insets(MARGIN_5,
            MARGIN_5, MARGIN_5, MARGIN_5));
        Separator sep = new Separator();
        root.getChildren().addAll(subroot, dynroot, sep);
        root.setId("process_parameter");
    }

    /**
     * Add new post process chain item.
     *
     * @param container The container
     * @param onChange A function called if the ComboBox value changed
     */
    public void addChainAttribute(VBox container, Runnable onChange) {
        VBox root = new VBox();
        VBox dynroot = new VBox();
        HBox subroot = new HBox();
        ComboBox box = new ComboBox();

        box.setCellFactory(new Callback <ListView<ProcessingStepConfiguration>,
                ListCell<ProcessingStepConfiguration>>() {
            @Override
            public ListCell<ProcessingStepConfiguration> call(
                    ListView<ProcessingStepConfiguration> list) {
                return new CellTypes.ProcessCfgCell();
            }
        });

        ProcessingConfiguration config =
            Config.getInstance().getProcessingConfig();
        List<ProcessingStepConfiguration> steps = config.getProcessingSteps();
        ObservableList<ProcessingStepConfiguration> conf =
            FXCollections.observableArrayList(steps);
        box.setItems(conf);
        box.setId("process_name");
        box.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent e) {
                generateChainItem(
                    (ProcessingStepConfiguration)box.getValue(),
                    dynroot,
                    config);
                if (onChange != null) {
                    onChange.run();
                }
            }
        });
        addRemoveButton(container, root, dynroot, subroot, box);
        container.getChildren().add(root);
    }

    /**
     * Add new post process chain item with pre-selected process name
     * and parameters.
     *
     * @param container The container
     * @param processName The process' name
     * @param params The parameters
     */
    public void addChainAttribute(VBox container,
            String processName, Map<String, String> params) {
        VBox root = new VBox();
        VBox dynroot = new VBox();
        HBox subroot = new HBox();
        ComboBox<ProcessingStepConfiguration> box = new ComboBox<>();
        ProcessingConfiguration config =
            Config.getInstance().getProcessingConfig();
        List<ProcessingStepConfiguration> steps = config.getProcessingSteps();
        ObservableList<ProcessingStepConfiguration> conf =
            FXCollections.observableArrayList(steps);
        box.setItems(conf);
        box.setId("process_name");
        box.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent e) {
                generateChainItem(
                    (ProcessingStepConfiguration)box.getValue(),
                    dynroot,
                    config);
            }
        });
        addRemoveButton(container, root, dynroot, subroot, box);

        for (ProcessingStepConfiguration cfg : box.getItems()) {
            if (cfg.getName().equals(processName)) {
                box.getSelectionModel().select(cfg);
            }
        }
        generateChainItem(
                (ProcessingStepConfiguration)box.getValue(),
                dynroot,
                config);

        for (Node node: dynroot.getChildren()) {
            HBox n = (HBox) node;
            ComboBox<Option> cb = (ComboBox<Option>) n.getChildren().get(1);
            Label paramLabel = (Label) n.getChildren().get(0);
            String targetvalue = params.get(paramLabel.getText());
            for (Option o: cb.getItems()) {
                if (o.getValue().equals(targetvalue)) {
                    cb.getSelectionModel().select(o);
                }
            }
        }
        container.getChildren().add(root);
    }


    /**
     * removes all Processing attributes.
     * @param container the container they should be removed from
     */
    public void removeAllChainAttributes(VBox container) {
        Collection<Node> nodeColl = new ArrayList<>();
        for (Node node : container.getChildren()) {
            if (node.getId().equals("process_parameter")) {
                nodeColl.add(node);
            }
        }
        container.getChildren().removeAll(nodeColl);
    }

    private void generateChainItem(
        ProcessingStepConfiguration item,
        VBox container,
        ProcessingConfiguration config
    ) {
        container.getChildren().clear();
        List<ConfigurationParameter> parameters = item.getParameters();
        for (ConfigurationParameter p : parameters) {
            if (p.getExt() != null || p.getGlob() != null) {
                // Ignore automatic parameters.
                continue;
            }
            if (p.getInputElement() != null) {
                String ie = p.getInputElement();
                List<InputElement> inputs = config.getInputElements();
                for (InputElement input : inputs) {
                    HBox root = new HBox();
                    List<String> var = p.extractVariables();
                    Label label = new Label();
                    label.setText(var.get(0));
                    label.setMinWidth(LABEL_MIN_WIDTH);
                    if (input.getName().equals(ie)
                        && "ComboBox".equals(input.getType())) {
                        ComboBox inputEl = new ComboBox();
                        inputEl.setUserData(var.get(0));
                        inputEl.setId(PROCESS_VAR);
                        List<Option> opts = input.getOptions();
                        inputEl.setItems(
                            FXCollections.observableArrayList(opts));
                        root.getChildren().add(label);
                        root.getChildren().add(inputEl);
                        root.setPrefHeight(PREF_HEIGHT);
                        root.setMaxHeight(PREF_HEIGHT);
                        root.setMargin(label, new Insets(MARGIN_5,
                            MARGIN_5, MARGIN_5, MARGIN_15));
                        container.getChildren().add(root);
                        container.setMargin(root, new Insets(MARGIN_5,
                            MARGIN_5, MARGIN_5, MARGIN_5));
                        break;
                    }
                    if (input.getName().equals(ie)
                        && "TextField".equals(input.getType())) {
                        TextField inputEl = new TextField();
                        inputEl.setUserData(var.get(0));
                        inputEl.setId(PROCESS_VAR);
                        root.getChildren().add(label);
                        root.getChildren().add(inputEl);
                        root.setPrefHeight(PREF_HEIGHT);
                        root.setMaxHeight(PREF_HEIGHT);
                        root.setMargin(label, new Insets(MARGIN_5,
                            MARGIN_5, MARGIN_5, MARGIN_15));
                        container.getChildren().add(root);
                        container.setMargin(root, new Insets(MARGIN_5,
                            MARGIN_5, MARGIN_5, MARGIN_5));
                        break;
                    }
                }
            } else {
                List<String> vars = p.extractVariables();
                for (String var : vars) {
                    HBox root = new HBox();
                    root.setPrefHeight(PREF_HEIGHT);
                    root.setMaxHeight(PREF_HEIGHT);
                    Label label = new Label();
                    label.setText(var);
                    label.setMinWidth(LABEL_MIN_WIDTH);
                    TextField textField = new TextField();
                    textField.setUserData(var);
                    textField.setId(PROCESS_VAR);
                    textField.setMinWidth(TEXTFIELD_MIN_WIDTH);
                    root.getChildren().addAll(label, textField);
                    root.setMargin(label, new Insets(MARGIN_5,
                        MARGIN_5, MARGIN_5, MARGIN_15));
                    container.getChildren().add(root);
                    container.setMargin(root, new Insets(MARGIN_5,
                        MARGIN_5, MARGIN_5, MARGIN_5));
                }
            }
        }
    }
} @XmlRootElement(name = "MIMETypes")
@XmlAccessorType(XmlAccessType.FIELD)
public class MIMETypes {

    private static final Logger LOG = LoggerFactory.getLogger(MIMETypes.class.getName());

    /** Name of the config file. */
    public static final String MIME_TYPES_FILE =
        "mimetypes.xml";

    /** Name of the config file in ressources. */
    public static final String MIME_TYPES_FILE_RES =
        "de/bayern/gdi/model/mimetypes.xml";

    @XmlElement(name = "Type")
    private List<MIMEType> types;

    private static final String NAME = "MimeTypeConfig";

    public MIMETypes() {
        types = new ArrayList<>();
    }

    /**
     * @return the types
     */
    public List<MIMEType> getTypes() {
        return types;
    }

    /**
     * @param types the types to set
     */
    public void setTypes(List<MIMEType> types) {
        this.types = types;
    }

    @Override
    public String toString() {
        return "{" + StringUtils.join(types, ", ") + "}";
    }

    /**
     * Find the extension for a given type.
     * @param typeName The name of the type.
     * @param def Default if not found.
     * @return The extension if found def otherwise.
     */
    public String findExtension(String typeName, String def) {
        String ext = findExtension(typeName);
        return ext != null ? ext : def;
    }

    /**
     * Find the extension for a given type.
     * @param typeName The name of the type.
     * @return The extension if found null otherwise.
     */
    public String findExtension(String typeName) {

        if (typeName == null) {
            return null;
        }

        for (MIMEType type: types) {
            String name = type.getName();
            if (name != null && name.equalsIgnoreCase(typeName)) {
                return type.getExt();
            }
        }

        return null;
    }

    /**
     * Find the MIME type for a given type name.
     * @param name The name of the type.
     * @return The MIME type if found null otherwise.
     */
    public MIMEType findByName(String name) {

        if (name == null) {
            return null;
        }

        for (MIMEType type: types) {
            String n = type.getName();
            if (n != null && n.equalsIgnoreCase(name)) {
                return type;
            }
        }

        return null;
    }

    /**
     * Loads MIME from a file.
     * @param file The file to load the MIMETypes from.
     * @return The restored MIME.
     * @throws IOException Something went wrong.
     */
    public static MIMETypes read(File file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            return read(fis);
        }
    }

    /**
     * Loads MIME from an input stream.
     * @param is The input stream to load the MIME from.
     * @return The restored MIME.
     * @throws IOException Something went wrong.
     */
    private static MIMETypes read(InputStream is) throws IOException {
        try {
            JAXBContext context = JAXBContext.newInstance(MIMETypes.class);
            Unmarshaller um = context.createUnmarshaller();
            BufferedInputStream bis = new BufferedInputStream(is);
            return (MIMETypes)um.unmarshal(bis);
        } catch (JAXBException je) {
            throw new IOException(je.getMessage(), je);
        }
    }

    /**
     * Load mime types from ressources.
     * @return The default mime types.
     */
    public static MIMETypes loadDefault() {
        InputStream in = null;
        try {
            in = Misc.getResource(MIME_TYPES_FILE_RES);
            if (in == null) {
                LOG.error("{} not found", MIME_TYPES_FILE);
                return new MIMETypes();
            }
            return read(in);
        } catch (IOException ioe) {
            LOG.error("Failed to load mimetypes", ioe);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioe) {
                    LOG.error("Failed to close mimetypes", ioe);
                }
            }
        }
        return new MIMETypes();
    }

    /**
     * gets the name of the configuration.
     * @return name of the config
     */
    public static String getName() {
        return NAME;
    }
} @XmlRootElement(name = "Verarbeitungsschritt")
@XmlAccessorType(XmlAccessType.FIELD)
public class ProcessingStepConfiguration {

    @XmlAttribute(name = "name")
    private String name;

    @XmlAttribute(name = "formattyp")
    private String formatType;

    @XmlElement(name = "Titel")
    private String title;

    @XmlElement(name = "Beschreibung")
    private String description;

    @XmlElement(name = "Befehl")
    private String command;

    @XmlElementWrapper(name = "ParameterSet")
    @XmlElement(name = "Parameter")
    private List<ConfigurationParameter> parameters;

    private boolean compatible;

    public ProcessingStepConfiguration() {
        parameters = new ArrayList<>();
        compatible = true;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * Set compatible value.
     * @param compatible New value
     */
    public void setCompatible(boolean compatible) {
        this.compatible = compatible;
    }

    /**
     * Return compatible value.
     * @return compatible value. True if step is compatible to format,
     * else false.
     */
    public boolean isCompatible() {
        return this.compatible;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the formatType
     */
    public String getFormatType() {
        return formatType;
    }

    /**
     * @param formatType the formatType to set
     */
    public void setFormatType(String formatType) {
        this.formatType = formatType;
    }

    /**
     * @return the title
     */
    public String getTitle() {
        return title;
    }

    /**
     * @param title the title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * @return the description
     */
    public String getDescription() {
        return description;
    }

    /**
     * @param description the description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * @return the command
     */
    public String getCommand() {
        return command;
    }

    /**
     * @param command the command to set
     */
    public void setCommand(String command) {
        this.command = command;
    }

    /**
     * @return the parameters
     */
    public List<ConfigurationParameter> getParameters() {
        return parameters;
    }

    /**
     * @param parameters the parameters to set
     */
    public void setParameters(List<ConfigurationParameter> parameters) {
        this.parameters = parameters;
    }

    @Override
    public String toString() {
        return this.title;
    }

    /**
     * Checks if the format type of this processing step is compatible with
     * a given type.
     * @param format the format to check.
     * @return true if the format is compatible false otherwise.
     */
    public boolean isCompatibleWithFormat(String format) {
        if (format == null || this.formatType == null) {
            return false;
        }
        for (String a: format.split(",")) {
            a = a.trim();
            for (String b: this.formatType.split(",")) {
                if (a.equals(b.trim())) {
                    return true;
                }
            }
        }
        return false;
    }
} public final class Config {

    private Config() {
    }

    private static final Logger LOG = LoggerFactory.getLogger(Config.class.getName());

    /** Inner class to implicit synchronize the instance access. */
    private static final class Holder {
        private Holder() {
        }
        static final Config INSTANCE = new Config();
    }

    private boolean initialized;

    private Settings settings;

    private MIMETypes mimeTypes;

    private ProcessingConfiguration processingConfig;

    private ProxyConfiguration proxyConfig;

    private static final long FIVEHUNDRED = 500;

    /**
     * Access the instance of the configuration.
     * Use #load before to initialize it.
     * @return The configuration instance.
     */
    public static Config getInstance() {
        synchronized (Holder.INSTANCE) {
            try {
                while (!Holder.INSTANCE.initialized) {
                    Holder.INSTANCE.wait(FIVEHUNDRED);
                }
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
            return Holder.INSTANCE;
        }
    }

    /**
     * @return The application Settings
     */
    public ApplicationSettings getApplicationSettings() {
        return settings.getApplicationSettings();
    }

    /**
     * @return the services
     */
    public ServiceSettings getServices() {
        return settings.getServiceSettings();
    }

    /**
     * @return the mimeTypes
     */
    public MIMETypes getMimeTypes() {
        return mimeTypes;
    }

    /**
     * @return the processingConfig
     */
    public ProcessingConfiguration getProcessingConfig() {
        return processingConfig;
    }

    /**
     * @return the proxy config
     */
    public ProxyConfiguration getProxyConfig() {
        return proxyConfig;
    }

    @Override
    public String toString() {
        return (proxyConfig == null ? "Proxy Settings: {}" : getProxyConfig())
            + ",\n " + getApplicationSettings()
            + ",\n " + getProcessingConfig()
            + ",\n " + getServices()
            + ",\n MIME-Types: " + getMimeTypes();
    }

    private static Settings loadSettings(File file)
        throws IOException {
        try {
            if (file != null && file.isFile() && file.canRead()) {
                return new Settings(file);
            }
            return new Settings();
        } catch (SAXException
                | ParserConfigurationException
                | IOException e) {
            LOG.error(e.getMessage(), Holder.INSTANCE);
            throwConfigFailureException(Settings.getName());
        }
        // Not reached.
        return null;
    }

    /**
     * Mark global config as unused.
     * @throws IOException when anythong goes wrong
     */
    private static void uninitialized()
        throws IOException {
        synchronized (Holder.INSTANCE) {
            LOG.info("No config directory given, starting with standard values...");
            Holder.INSTANCE.settings = loadSettings(null);
            Holder.INSTANCE.processingConfig =
                ProcessingConfiguration.loadDefault();
            Holder.INSTANCE.mimeTypes = MIMETypes.loadDefault();
            Holder.INSTANCE.initialized = true;
            Holder.INSTANCE.notifyAll();
        }
    }

    /**
     * Load configurations.
     * @param dirname The directory with the configuration files.
     * @throws IOException If something went wrong.
     */
    public static void initialize(String dirname) throws IOException {
        Controller.logToAppLog(I18n.format("dlc.start", Info.getVersion()));
        System.setProperty("java.util.logging.manager",
            "org.apache.logging.log4j.jul.LogManager");
        try {
            Logging.GEOTOOLS.setLoggerFactory(
                "org.geotools.util.logging.Log4JLoggerFactory");
        } catch (ClassNotFoundException e) {
            LOG.warn("Failed to initialize GeoTools logging subsystem: "
                + e.getLocalizedMessage());
        }
        if (dirname == null) {
            uninitialized();
        } else {
            synchronized (Holder.INSTANCE) {
                try {
                    load(dirname);
                } finally {
                    Holder.INSTANCE.initialized = true;
                    Holder.INSTANCE.notifyAll();
                }
            }
        }
        LOG.trace("System Properties:");
        System.getProperties().keySet().stream().map(
            k -> (String)k).sorted().forEach(k -> {
            LOG.trace(String.format("\t%s=%s", k,
                System.getProperty(k)));
        });
        Controller.logToAppLog(I18n.format("dlc.config", Config.getInstance()));
    }

    private static void load(String dirname) throws IOException {
        LOG.info("config directory: " + dirname);

        File dir = new File(dirname);

        if (!dir.isDirectory()) {
            throw new IOException("'" + dirname + "' is not a directory.");
        }

        File proxy = new File(dir, ProxyConfiguration.PROXY_CONFIG_FILE);
        if (proxy.isFile() && proxy.canRead()) {
            Holder.INSTANCE.proxyConfig = ProxyConfiguration.read(proxy);
            Holder.INSTANCE.proxyConfig.apply();
        } else {
            LOG.info("No Proxy config found, starting without proxy settings");
        }

        File services = new File(dir, Settings.SETTINGS_FILE);
        if (services.isFile() && services.canRead()) {
            Holder.INSTANCE.settings = loadSettings(services);
        } else {
            Holder.INSTANCE.settings = loadSettings(null);
            LOG.info("Settings config not found, using fallback...");
        }

        File procConfig = new File(
            dir, ProcessingConfiguration.PROCESSING_CONFIG_FILE);
        if (procConfig.isFile() && procConfig.canRead()) {
            Holder.INSTANCE.processingConfig =
                ProcessingConfiguration.read(procConfig);
        } else {
            Holder.INSTANCE.processingConfig =
                ProcessingConfiguration.loadDefault();
            LOG.info("Processing config not found, using fallback...");
        }

        if (Holder.INSTANCE.getProcessingConfig() == null) {
            throwConfigFailureException(ProcessingConfiguration.getName());
        }

        File mimeTypes = new File(dir, MIMETypes.MIME_TYPES_FILE);
        if (mimeTypes.isFile() && mimeTypes.canRead()) {
            Holder.INSTANCE.mimeTypes =
                MIMETypes.read(mimeTypes);
        } else {
            Holder.INSTANCE.mimeTypes = MIMETypes.loadDefault();
            LOG.info("MimeTypes config not found, using fallback...");
        }

        if (Holder.INSTANCE.getMimeTypes() == null) {
            throwConfigFailureException(MIMETypes.getName());
        }
    }

    private static void throwConfigFailureException(String configName)
        throws IOException {
        throw new IOException(
                "Failed on XML file for '"
                        + configName
                        + "'");
    }
} public class DownloadConfig {

    private Document configDoc;
    private File configFile;
    private String dataset;
    private String downloadPath;
    private String serviceType;
    private String serviceURL;
    private Map<String, String> parameters;
    private List<ProcessingStep> procSteps;

   /**
    * Exception, thrown if no service URL is found in the config file.
    */
    public class NoServiceURLException extends Exception { }

   /**
    * Default Constructor.
    */
    public DownloadConfig() { }

    /**
    * Constructor.
    *
    * @param configFile The config xml file.
    */
    public DownloadConfig(File configFile)
            throws IOException, ParserConfigurationException,
            SAXException, NoServiceURLException {

        if (configFile == null) {
            return;
        }

        this.configFile = configFile;
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        configDoc = db.parse(configFile);

        //Read service type, URL and Dataset
        Element root = configDoc.getDocumentElement();

        serviceType = getValueByTagName("ServiceTyp", root);
        try {
            serviceURL = getValueByTagName("URL", root);
        } catch (Exception e) {
            throw new NoServiceURLException();
        }
        try {
            dataset = parseDataset(root);
        } catch (Exception ex) {
            dataset = null;
        }
        try {
            downloadPath = getValueByTagName("DownloadPfad", root);
        } catch (Exception exc) {
            downloadPath = null;
        }

        //Read parameters
        parameters = new HashMap<>();
        NodeList paramNodes = configDoc.getElementsByTagName("Parameter");

        for (int i = 0; i < paramNodes.getLength(); i++) {
            Node iNode = paramNodes.item(i);
            if (iNode.getParentNode().getParentNode()
                    .getNodeName() != "Verarbeitungsschritt") {
                parameters.put(
                    getValueByTagName("Name", (Element) iNode),
                    getValueByTagName("Wert", (Element) iNode));
            }
        }

        //Read Processing steps
        NodeList stepNodes = configDoc.
                getElementsByTagName("Verarbeitungsschritt");

        if (stepNodes.getLength() == 0) {
            procSteps = null;
        } else {
            procSteps = new ArrayList<>();
            //Verarbeitungsschritt-Nodes
            for (int i = 0; i < stepNodes.getLength(); i++) {
                ProcessingStep newStep = new ProcessingStep();
                newStep.name = getValueByTagName("Name",
                        (Element) stepNodes.item(i));
                Element step = (Element) stepNodes.item(i);
                NodeList stepParams = step.getElementsByTagName("Parameter");
                for (int j = 0; j < stepParams.getLength(); j++) {
                    newStep.params.put(
                        getValueByTagName("Name", (Element) stepParams.item(j)),
                        getValueByTagName("Wert", (Element) stepParams.item(j))
                    );
                }
                procSteps.add(newStep);
            }
        }
    }

    /**
    * Returns the atom variation type as string.
    *
    * @return The variation string
    */
    public String getAtomVariation() {
        return parameters.get("VARIATION");
    }

   /**
    * Returns the bounding box as string.
    *
    * @return The bound box
    */
    public String getBoundingBox() {
        return parameters.get("bbox");
    }

   /**
    * Returns the dataset as string.
    *
    * @return The dataset
    */
    public String getDataset() {
        return dataset;
    }

   /**
    * Returns the download path as string.
    *
    * @return The download path
    */
    public String getDownloadPath() {
        return downloadPath;
    }

   /**
    * Returns the config file object.
    *
    * @return The file object
    */
    public File getFile() {
        return configFile;
    }

   /**
    * Returns the output format as string.
    *
    * @return The output format
    */
    public String getOutputFormat() {
        return parameters.get("outputformat");
    }

   /**
    * Returns all parameters as a HashMap.
    *
    * @return The params
    */
    public Map<String, String> getParams() {
        return parameters;
    }

   /**
    * Returns all processing steps as an ArrayList.
    *
    * @return The processing step list
    */
    public List<ProcessingStep> getProcessingSteps() {
        return procSteps;
    }

   /**
    * Returns the service type as string.
    *
    * @return The service type as string
    */
    public String getServiceType() {
        return serviceType;
    }

   /**
    * Returns the service URL as string.
    *
    * @return The URL as string
    */
    public String getServiceURL() {
        return serviceURL;
    }

   /**
    * Returns the SRS name.
    *
    * @return The SRS name
    */
    public String getSRSName() {
        return parameters.get("srsName");
    }

    /**
     * Returns the CQL.
     *
     * @return The CQL
     */
    public String getCql() {
        return parameters.get("CQL");
    }

    @Override
    public String toString() {
        return "DownloadConfig ["
            + "configDoc=" + configDoc
            + ", configFile=" + configFile
            + ", dataset='" + dataset + '\''
            + ", downloadPath='" + downloadPath + '\''
            + ", serviceType='" + serviceType + '\''
            + ", serviceURL='" + serviceURL + '\''
            + ", parameters=" + parameters
            + ", procSteps=" + procSteps
            + ']';
    }

    /**
    * Returns the value of a tag by the name of the Tag.
    *
    * @param tagName The name of the tag.
    * @param parent The parent element from which the query shall be started.
    * @return The value as string
    */
    private String getValueByTagName(String tagName, Element parent) {
        try {
            NodeList nodes = parent.getElementsByTagName(tagName).item(0)
                    .getChildNodes();
            Node valueNode = nodes.item(0);
            return valueNode.getNodeValue();
        } catch (Exception e) {
            return null;
        }
    }

    private String parseDataset(Element root) {
        String datasetValue = getValueByTagName("Dataset", root);
        if (datasetValue != null
            && !I18n.getMsg("typ.overall.query").equals(datasetValue)) {
            for (FilterType filterType : FilterType.values()) {
                String labelSuffix = filterType.getLabelSuffix();
                if (datasetValue.endsWith(labelSuffix)) {
                    int endIndex = datasetValue.indexOf(labelSuffix) - 1;
                    return datasetValue.substring(0, endIndex);
                }
            }
        }
        return datasetValue;
    }

    /**
      * Class, serving as a simple model for processing chain steps.
      */
    public static final class ProcessingStep {
       /**
        * The name of the processing step.
        */
        private String name;

       /**
        * Name and value of all given parameters for this step.
        */
        private HashMap<String, String> params;

        /**
         * Returns the map of the parameters used by
         * this processing step.
         * @return the map with the parameters otf this step.
         */
        public Map<String, String> getParams() {
            return this.params;
        }

        /**
         * Returns the name of this processing step.
         * @return the name of the processing step.
         */
        public String getName() {
            return name;
        }

        public ProcessingStep() {
            params = new HashMap<>();
        }
    }
} public static final String FX_BORDER_COLOR_NULL = "-fx-border-color: null;"; public static final String FX_BORDER_COLOR_RED = "-fx-border-color: red;"; public static final String GUI_PROCESS_FORMAT_NOT_FOUND = "gui.process.format.not.found"; public static final String GUI_PROCESS_NOT_COMPATIBLE = "gui.process.not.compatible"; public static final String GUI_PROCESS_NO_FORMAT = "gui.process.no.format"; public static final String OUTPUTFORMAT = "outputformat"; public static final String STATUS_READY = "status.ready";
import de.bayern.gdi.gui.UIFactory;
import de.bayern.gdi.model.MIMEType;
import de.bayern.gdi.model.MIMETypes;
import de.bayern.gdi.model.ProcessingStepConfiguration;
import de.bayern.gdi.config.Config;
import de.bayern.gdi.config.DownloadConfig;
import de.bayern.gdi.utils.I18n;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static de.bayern.gdi.gui.GuiConstants.FX_BORDER_COLOR_NULL;
import static de.bayern.gdi.gui.GuiConstants.FX_BORDER_COLOR_RED;
import static de.bayern.gdi.gui.GuiConstants.GUI_PROCESS_FORMAT_NOT_FOUND;
import static de.bayern.gdi.gui.GuiConstants.GUI_PROCESS_NOT_COMPATIBLE;
import static de.bayern.gdi.gui.GuiConstants.GUI_PROCESS_NO_FORMAT;
import static de.bayern.gdi.gui.GuiConstants.OUTPUTFORMAT;
import static de.bayern.gdi.gui.GuiConstants.STATUS_READY;
/*
 * DownloadClient Geodateninfrastruktur Bayern
 *
 * (c) 2016 GSt. GDI-BY (gdi.bayern.de)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.bayern.gdi.gui.controller;




/**
 * Processing chain controller.
 * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a>
 */
@Named
@Singleton
public class ProcessingChainController {

    @Inject
    private Controller controller;

    @Inject
    private StatusLogController statusLogController;

    @FXML
    private VBox chainContainer;

    @FXML
    private CheckBox chkChain;

    @FXML
    private HBox processStepContainter;

    private UIFactory factory = new UIFactory();

    /**
     * Handle events on the process Chain Checkbox.
     *
     * @param event
     *     the event
     */
    @FXML
    protected void handleChainCheckbox(ActionEvent event) {
        if (chkChain.isSelected()) {
            processStepContainter.setVisible(true);
        } else {
            factory.removeAllChainAttributes(chainContainer);
            processStepContainter.setVisible(false);
        }
    }

    /**
     * Handle the dataformat selection.
     *
     * @param event
     *     The event
     */
    @FXML
    protected void handleAddChainItem(ActionEvent event) {
        factory.addChainAttribute(chainContainer,
                                   this::validateChainContainerItems);
        validateChainContainerItems();
    }

    /**
     * Sets visibility.
     * @param isVisible shows container when true otherwise is hidden
     */
    public void setVisible(boolean isVisible) {
        this.processStepContainter.setVisible(isVisible);
    }

    /**
     * Sets processing steps.
     * @param steps list of steps
     */
    public void setProcessingSteps(List<DownloadConfig.ProcessingStep> steps) {
        factory.removeAllChainAttributes(chainContainer);
        if (steps != null) {
            chkChain.setSelected(true);
            handleChainCheckbox(new ActionEvent());

            for (DownloadConfig.ProcessingStep iStep : steps) {
                factory.addChainAttribute(chainContainer,
                                           iStep.getName(), iStep.getParams());
            }
        } else {
            chkChain.setSelected(false);
            handleChainCheckbox(new ActionEvent());
        }
    }

    /**
     * Returns processing chain parameter.
     * @return set of parameter
     */
    public Set<Node> getProcessingChainParameter() {
        if (!this.chkChain.isSelected()) {
            Collections.emptyList();
        }
        return this.chainContainer.lookupAll("#process_parameter");
    }

    /**
     * Resets all marks at the processing chain container, items are kept.
     */
    public void resetProcessingChainContainer() {
        for (Node o : chainContainer.getChildren()) {
            if (o instanceof VBox) {
                VBox v = (VBox) o;
                HBox hbox = (HBox) v.getChildren().get(0);
                Node cBox = hbox.getChildren().get(0);
                if (cBox instanceof ComboBox) {
                    cBox.setStyle(FX_BORDER_COLOR_NULL);
                    ComboBox box = (ComboBox) cBox;
                    ObservableList<ProcessingStepConfiguration> confs =