com.mgmtp.jfunk.common.util.Configuration.java Source code

Java tutorial

Introduction

Here is the source code for com.mgmtp.jfunk.common.util.Configuration.java

Source

/*
 * Copyright (c) 2015 mgm technology partners GmbH
 *
 * 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 com.mgmtp.jfunk.common.util;

import static com.google.common.collect.Lists.newArrayList;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.annotation.concurrent.NotThreadSafe;

import org.apache.commons.io.IOUtils;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.mgmtp.jfunk.common.JFunkConstants;
import com.mgmtp.jfunk.common.exception.JFunkException;

/**
 * {@link ExtendedProperties} subclass adding zip file handling.
 * 
 */
@NotThreadSafe
public final class Configuration extends ExtendedProperties {

    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^},]+)(?:\\s*,[^}]*)?\\}");
    private static final long serialVersionUID = 1L;

    private transient ZipFile zipArchive;

    private final List<String> extraFileProperties = newArrayList();

    private final Charset charset;

    private int loadingDepth;

    /**
     * Creates an empty instance.
     */
    public Configuration(final Charset charset) {
        this(null, charset);
    }

    /**
     * Creates an empty instance with the specified defaults.
     * 
     * @param defaults
     *            The defaults
     */
    public Configuration(final Map<String, String> defaults, final Charset charset) {
        super(defaults);
        this.charset = charset;
    }

    /**
     * Loads a properties file. If the file is a zip file, the file {@code testing.properties} is
     * loaded from the zip file root directory. Otherwise,
     * {@link ResourceLoader#getConfigInputStream(String)} is used to load the file.
     * 
     * @param fileName
     *            The file name
     */
    public void load(final String fileName) {
        load(fileName, false);
    }

    /**
     * Loads a properties file. If the file is a zip file, the file {@code testing.properties} is
     * loaded from the zip file root directory. Otherwise,
     * {@link ResourceLoader#getConfigInputStream(String)} is used to load the file.
     * 
     * @param fileName
     *            The file name
     * @param preserveExisting
     *            If {@code true}, existing properties will not be overwritten from the specified
     *            properties file.
     */
    public void load(final String fileName, final boolean preserveExisting) {
        logger.info("Loading config file: {} (preserveExisting={})", fileName, preserveExisting);

        InputStream is = null;
        try {
            loadingDepth++;
            if (fileName.endsWith(JFunkConstants.ZIP_FILE_SUFFIX)) {
                logger.info("Loading zip archive '{}'...", fileName);
                zipArchive = new ZipFile(fileName);
                ZipEntry entry = zipArchive.getEntry(JFunkConstants.SCRIPT_PROPERTIES);
                if (entry == null) {
                    entry = zipArchive.getEntry("script.properties");
                }
                is = zipArchive.getInputStream(entry);
                // keeping preExisting properties is activated with preserveExisting set to true
                doLoad(is, preserveExisting);
            } else {
                is = ResourceLoader.getConfigInputStream(fileName);
                logger.info("Loading file '{}'...", fileName);

                // System properties must always be loaded before extra properties are,
                // so they can use System properties in placeholders
                putAll(ExtendedProperties.fromProperties(System.getProperties()));
                doLoad(is, preserveExisting);
                loadExtraFiles(JFunkConstants.SYSTEM_PROPERTIES, preserveExisting);
            }

            if (loadingDepth == 1) {
                // Add System properties again because they also take precedence over extra properties
                putAll(ExtendedProperties.fromProperties(System.getProperties()));

                // Archiving is always necessary when execution mode is 'start'.
                // Otherwise continuing after the breakpoint would not be possible.
                if (JFunkConstants.EXECUTION_MODE_START.equals(get(JFunkConstants.EXECUTION_MODE))) {
                    put(JFunkConstants.ARCHIVING_MODE, JFunkConstants.ARCHIVING_MODE_ALL);
                }

                Properties props = System.getProperties();
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_KEY_STORE_PASSWORD);
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_KEY_STORE);
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_KEY_STORE_TYPE);
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_TRUST_STORE_PASSWORD);
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_TRUST_STORE);
                copyProperty(props, this, JFunkConstants.JAVAX_NET_SSL_TRUST_STORE_TYPE);
            }
            loadingDepth--;
        } catch (IOException ex) {
            throw new JFunkException("Error loading properties: " + fileName, ex);
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    private static void copyProperty(final Properties target, final Configuration source, final String key) {
        String value = source.get(key);
        if (!Strings.isNullOrEmpty(value)) {
            target.setProperty(key, value);
        }
    }

    private void doLoad(final InputStream is, final boolean preserveExisting) throws IOException {
        if (!isEmpty() && preserveExisting) {
            Configuration copy = clone();
            super.load(is, charset.name());
            putAll(copy);
        } else {
            super.load(is, charset.name());
        }
    }

    /**
     * If properties are present which start with {@link JFunkConstants#SYSTEM_PROPERTIES} the
     * corresponding values are taken as property files and loaded here.
     */
    private void loadExtraFiles(final String filterPrefix, final boolean preserveExisting) {
        Map<String, String> view = Maps.filterKeys(this, Predicates.startsWith(filterPrefix));
        while (true) {
            if (view.isEmpty()) {
                break;
            }

            Queue<String> fileKeys = Queues.newArrayDeque(view.values());

            // we need to keep them separately in order to be able to reload them (see put method)
            extraFileProperties.addAll(fileKeys);

            // Remove original keys in order to prevent a stack overflow
            view.clear();

            for (String fileNameKey = null; (fileNameKey = fileKeys.poll()) != null;) {
                // Recursion
                String fileName = processPropertyValue(fileNameKey);
                if (PLACEHOLDER_PATTERN.matcher(fileName).find()) {
                    // not all placeholders were resolved, so we enqueue it again to process another file first
                    fileKeys.offer(fileName);
                } else {
                    load(fileName, preserveExisting);
                }
            }
        }
    }

    @Override
    public String put(final String key, final String value) {
        String result = super.put(key, value);

        if (loadingDepth == 0) {
            // put is also called during loading, so we need to exclude this re-loading mechanism there,
            // it is not necessary and may lead to errors because not all placeholders might have been resolved yet
            if (!Strings.isNullOrEmpty(value)) {
                // reloads extra files if the contains placeholders and
                // the properties the placeholders refer to are changed
                // --> temp copy of list in order to avoid ConcurrentModificationException
                List<String> tmpExtraFileProps = newArrayList(extraFileProperties);
                for (String extraFile : tmpExtraFileProps) {
                    Matcher matcher = PLACEHOLDER_PATTERN.matcher(extraFile);
                    while (matcher.find()) {
                        if (matcher.group(1).equals(key)) {
                            load(processPropertyValue(extraFile));
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * Returns an input stream the file with the specified name. If this {@link Configuration} was
     * loaded from a zip file, and {@link InputStream} to zip entry with the specified name is
     * return. Otherwise, {@link ResourceLoader#getInputStream(String)} is used.
     * 
     * @return The stream
     */
    public InputStream openStream(final String filename) throws IOException {
        if (zipArchive != null) {
            // lade aus zip datei
            ZipEntry entry = null;
            int index = 0;
            String name = filename;
            do {
                name = name.substring(index);
                entry = zipArchive.getEntry(name);
                index = name.indexOf('/') + 1;
                if (index == 0) {
                    index = name.indexOf('\\') + 1;
                }
            } while (entry == null && index > 0);
            if (entry != null) {
                logger.info("Opening stream to '{}' in zip archive...", name);
                return zipArchive.getInputStream(entry);
            }
        }
        return ResourceLoader.getInputStream(filename);
    }

    /**
     * Extracts the specified file from this configuration's zip file, if applicable.
     * 
     * @param targetFile
     *            The target file. The file name without the path information is taken in order to
     *            identify the zip entry to extract.
     * @param forceOverwrite
     *            If {@code true}, an existing file is overwritten.
     */
    public void extractFromArchive(final File targetFile, final boolean forceOverwrite) throws IOException {
        if (zipArchive != null) {
            ZipEntry entry = zipArchive.getEntry(targetFile.getName());
            if (entry != null) {
                if (!targetFile.exists()) {
                    try {
                        targetFile.createNewFile();
                    } catch (IOException ex) {
                        throw new JFunkException("Error creating file: " + targetFile, ex);
                    }
                } else if (!forceOverwrite) {
                    return;
                }
                logger.info("Loading file '{}' from zip archive...", targetFile);
                OutputStream out = null;
                InputStream in = null;
                try {
                    out = new FileOutputStream(targetFile);
                    in = zipArchive.getInputStream(entry);
                    IOUtils.copy(in, out);
                } finally {
                    IOUtils.closeQuietly(in);
                    IOUtils.closeQuietly(out);
                }
            } else {
                logger.error("Could not find file '{}' in zip archive", targetFile);
            }
        }
    }

    @Override
    public Configuration clone() {
        return (Configuration) super.clone();
    }

    /**
     * @serialData The name of the zip file.
     */
    private void writeObject(final ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeUTF(zipArchive.getName());
    }

    private void readObject(final ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        String zipFileName = ois.readUTF();
        zipArchive = new ZipFile(zipFileName);
    }
}