Java tutorial
/* * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com * * 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 org.opensingular.lib.commons.base; import org.apache.commons.io.input.NullInputStream; import org.apache.commons.lang3.StringUtils; import org.opensingular.lib.commons.lambda.IConsumerEx; import org.opensingular.lib.commons.util.PropertiesUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.function.Supplier; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; public final class SingularPropertiesImpl implements SingularProperties { private static final SingularPropertiesImpl INSTANCE = new SingularPropertiesImpl(); private static final Logger LOGGER = LoggerFactory.getLogger(SingularPropertiesImpl.class); private static final String DEFAULT_PROPERTIES_FILENAME = "singular-defaults.properties"; private static final String[] PROPERTIES_FILES_NAME = { "singular-form-service.properties", "singular.properties" }; private volatile Properties properties; private Supplier<Properties> singularDefaultPropertiesSupplier = this::getSingularDefaultProperties; public static SingularPropertiesImpl get() { return INSTANCE; } private static File findConfDir() { String path = System.getProperty(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME); if (path != null) { if (LOGGER.isInfoEnabled()) { LOGGER.info(" Encontrado a propriedade singular.server.home={}", path); } File confDir = new File(path, "conf"); if (confDir.exists()) { if (!confDir.isDirectory() && LOGGER.isWarnEnabled()) { LOGGER.warn(" Era esperado que \"[singular.server.home]\\conf\" fosse um diretrio"); } return confDir; } else if (LOGGER.isWarnEnabled()) { LOGGER.warn(" No exite o diretrio {}", confDir); } } return null; } /** * Limpa as propriedades da memoria e fora recarga a partir da memria e classPath. */ public synchronized void reload() { LOGGER.info("Carregando configuraes do Singular"); Properties newProperties = readClasspathDefaults(); readPropertiesFilesOverrides(newProperties); properties = newProperties; } public void setSingularServerHome(String serverHome) { if (serverHome != null) System.setProperty(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME, serverHome); else System.clearProperty(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME); } /** * Verifica se a propriedade de nome informado existe. */ @Override public boolean containsKey(String key) { return getProperties().containsKey(key) || System.getProperties().containsKey(key); } /** * Retorna o valor da propriedade solicitada. Pode retornar null. */ @Override public String getProperty(String key) { //se contm a chave ainda que esta seja com valor nulo if (getProperties().containsKey(key)) { return getProperties().getProperty(key); } else { return System.getProperties().getProperty(key); } } @Override public boolean isTrue(String key) { return "true".equals(Optional.ofNullable(getProperty(key)).map(String::toLowerCase).orElse(null)); } @Override public boolean isFalse(String key) { return "false".equals(Optional.ofNullable(getProperty(key)).map(String::toLowerCase).orElse(null)); } public String setProperty(String key, String value) { return (String) getProperties().setProperty(key, value); } private synchronized Properties getProperties() { //Faz leitura lazy das propriedades, pois no construtor da enum, as variveis estticas no esto disponveis if (properties == null) { reload(); } return properties; } private void readPropertiesFilesOverrides(Properties newProperties) { File confDir = findConfDir(); if (confDir != null) { for (String name : PROPERTIES_FILES_NAME) { File arq = new File(confDir, name); loadOverriding(newProperties, arq); } } } private Properties readClasspathDefaults() { Properties newProperties = null; for (String name : PROPERTIES_FILES_NAME) { URL url = findProperties(name); if (url == null) { LOGGER.warn(" No foi encontrado o arquivo no classpath: {}", name); } else { LOGGER.info(" Lendo arquivo de propriedades '{}' em {}", name, url); newProperties = loadNotOverriding(newProperties, name, url); } } Properties resolvedProperties = newProperties == null ? new Properties() : newProperties; appendDefaultProperties(resolvedProperties); return resolvedProperties; } /** * Adiciona as propriedades default que j no foram carregadas dos arquivos em PROPERTIES_FILES_NAME. * * @param resolvedProperties */ private void appendDefaultProperties(Properties resolvedProperties) { Properties defaults = singularDefaultPropertiesSupplier.get(); for (String key : defaults.stringPropertyNames()) { if (!resolvedProperties.containsKey(key)) { resolvedProperties.setProperty(key, StringUtils.defaultString(defaults.getProperty(key))); } } } public Properties getSingularDefaultProperties() { Properties defaults = new Properties(); try (InputStream input = defaultIfNull( SingularProperties.class.getResourceAsStream(DEFAULT_PROPERTIES_FILENAME), new NullInputStream(0)); Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8.name())) { defaults.load(reader); } catch (IOException ex) { throw new IllegalStateException("", ex); } return defaults; } private Properties loadNotOverriding(Properties newProperties, String propertiesName, URL propertiesUrl) { Properties props; try { props = PropertiesUtils.load(propertiesUrl); } catch (IOException e) { throw SingularException.rethrow("Erro lendo arquivo de propriedade", e).add("url", propertiesUrl); } if (newProperties == null) { return props; } for (Map.Entry<Object, Object> entry : props.entrySet()) { if (newProperties.containsKey(entry.getKey())) { throw SingularException .rethrow("O arquivo de propriedade '" + propertiesName + "' no classpath define novamente a propriedade '" + entry.getKey() + "' definida anteriormente em outro arquivo de propriedade no class path.") .add("url", propertiesUrl); } else { newProperties.setProperty((String) entry.getKey(), (String) entry.getValue()); } } return newProperties; } private URL findProperties(String name) { try { return SingularPropertiesImpl.class.getClassLoader().getResource(name); } catch (Exception e) { throw SingularException.rethrow("Erro procurando arquivo de properties '" + name + "' no class path", e); } } private URL add(URL current, String name, Enumeration<URL> resources) throws URISyntaxException { URL selected = current; while (resources.hasMoreElements()) { URL u = resources.nextElement(); if (selected == null) { selected = u; } else if (!selected.toURI().equals(u.toURI())) { throw SingularException .rethrow("Foram encontrados dois arquivos com mesmo nome '" + name + "' no class path") .add("arquivo 1", selected).add("arquivo 2", u); } } return selected; } private void loadOverriding(Properties newProperties, File arq) { if (arq.exists()) { if (LOGGER.isInfoEnabled()) { LOGGER.info(" Lendo arquivo de propriedades {}", arq); } try (FileInputStream in = new FileInputStream(arq)) { loadOverriding(newProperties, in); } catch (Exception e) { throw SingularException.rethrow("Erro lendo arquivo de propriedades", e).add("arquivo", arq); } } } private void loadOverriding(Properties newProperties, URL resoruce) { if (LOGGER.isInfoEnabled()) { LOGGER.info(" Lendo arquivo de propriedades {}", resoruce); } try { loadOverriding(newProperties, resoruce.openStream()); } catch (Exception e) { throw SingularException.rethrow("Erro lendo arquivo de propriedades", e).add("url", resoruce); } } private void loadOverriding(Properties newProperties, InputStream in) throws IOException { Properties p = new Properties(); p.load(in); for (Map.Entry<Object, Object> entry : p.entrySet()) { newProperties.setProperty((String) entry.getKey(), (String) entry.getValue()); } } /** * Copia as propriedades do arquivo para as properties internas. As propriedades previamente existentes sero * sobrepostas. Esse mtodo utilizado para testes unitrios com difererentes contextos. */ public synchronized void reloadAndOverrideWith(URL propertiesURL) { reload(); loadOverriding(getProperties(), propertiesURL); } public static class Tester { private final Properties props; public Tester(Properties props) { this.props = props; } public static <EX extends Exception> void runInSandbox(IConsumerEx<SingularPropertiesImpl, EX> callable) throws EX { Object state = saveState(); try { callable.accept(SingularPropertiesImpl.get()); } finally { restoreState(state); } } protected static void restoreState(Object stateObject) { State state = (State) stateObject; String serverHome = state.systemBackup.get(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME); SingularPropertiesImpl.get().setSingularServerHome(serverHome); SingularPropertiesImpl.INSTANCE.properties = state.propertiesBackup; } public static Object saveState() { State state = new State(); PropertiesUtils.copyTo(SingularPropertiesImpl.INSTANCE.properties, state.propertiesBackup); state.systemBackup.put(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME, System.getProperty(SYSTEM_PROPERTY_SINGULAR_SERVER_HOME)); return state; } public String getProperty(String key) { return props.getProperty(key); } public String setProperty(String key, String value) { return (String) props.setProperty(key, value); } private static class State implements Serializable { private final Properties propertiesBackup = new Properties(); private final Map<String, String> systemBackup = new HashMap<>(); } } }