##### Coverage Report - com.ctrip.apollo.internals.DefaultConfig

Classes in this File | Line Coverage | Branch Coverage | Complexity
DefaultConfig |
85% | 66/77
85% | 34/40
| 5.333333333333333;5.333
DefaultConfig$1 |
100% | 1/1
N/A | N/A
| 5.333333333333333;5.333

1 |  |
     package com.ctrip.apollo.internals;
2 |  |
3 |  |
     import com.google.common.collect.ImmutableMap;
4 |  |
5 |  |
     import com.ctrip.apollo.core.utils.ClassLoaderUtil;
6 |  |
     import com.ctrip.apollo.enums.PropertyChangeType;
7 |  |
     import com.ctrip.apollo.model.ConfigChange;
8 |  |
     import com.ctrip.apollo.model.ConfigChangeEvent;
9 |  |
     import com.ctrip.apollo.util.ExceptionUtil;
10 |  |
     import com.dianping.cat.Cat;
11 |  |
12 |  |
     import org.slf4j.Logger;
13 |  |
     import org.slf4j.LoggerFactory;
14 |  |
15 |  |
     import java.io.IOException;
16 |  |
     import java.io.InputStream;
17 |  |
     import java.util.List;
18 |  |
     import java.util.Map;
19 |  |
     import java.util.Objects;
20 |  |
     import java.util.Properties;
21 |  |
     import java.util.concurrent.atomic.AtomicReference;
22 |  |
23 |  |
     /**
24 |  |
      * @author Jason Song(song_s@ctrip.com)
25 |  |
      */
26 |  |
     public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
27 | 1 |
       private static final Logger logger = LoggerFactory.getLogger(DefaultConfig.class);
28 |  |
       private final String m_namespace;
29 |  |
       private Properties m_resourceProperties;
30 |  |
       private AtomicReference<Properties> m_configProperties;
31 |  |
       private ConfigRepository m_configRepository;
32 |  |
33 |  |
       /**
34 |  |
        * Constructor.
35 |  |
        *
36 |  |
        * @param namespace        the namespace of this config instance
37 |  |
        * @param configRepository the config repository for this config instance
38 |  |
        */
39 | 11 |
       public DefaultConfig(String namespace, ConfigRepository configRepository) {
40 | 11 |
         m_namespace = namespace;
41 | 11 |
         m_resourceProperties = loadFromResource(m_namespace);
42 | 11 |
         m_configRepository = configRepository;
43 | 11 |
         m_configProperties = new AtomicReference<>();
44 | 11 |
         initialize();
45 | 11 |
       }
46 |  |
47 |  |
       private void initialize() {
48 |  |
         try {
49 | 11 |
           m_configProperties.set(m_configRepository.getConfig());
50 | 1 |
         } catch (Throwable ex) {
51 | 1 |
           Cat.logError(ex);
52 | 2 |
           logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
53 | 1 |
               m_namespace, ExceptionUtil.getDetailMessage(ex));
54 |  |
         } finally {
55 |  |
           //register the change listener no matter config repository is working or not
56 |  |
           //so that whenever config repository is recovered, config could get changed
57 | 11 |
           m_configRepository.addChangeListener(this);
58 | 11 |
         }
59 | 11 |
       }
60 |  |
61 |  |
       @Override
62 |  |
       public String getProperty(String key, String defaultValue) {
63 |  |
         // step 1: check system properties, i.e. -Dkey=value
64 | 30 |
         String value = System.getProperty(key);
65 |  |
66 |  |
         // step 2: check local cached properties file
67 | 30 |
         if (value == null && m_configProperties.get() != null) {
68 | 26 |
           value = m_configProperties.get().getProperty(key);
69 |  |
         }
70 |  |
71 |  |
         /**
72 |  |
          * step 3: check env variable, i.e. PATH=...
73 |  |
          * normally system environment variables are in UPPERCASE, however there might be exceptions.
74 |  |
          * so the caller should provide the key in the right case
75 |  |
          */
76 | 30 |
         if (value == null) {
77 | 6 |
           value = System.getenv(key);
78 |  |
         }
79 |  |
80 |  |
         // step 4: check properties file from classpath
81 | 30 |
         if (value == null && m_resourceProperties != null) {
82 | 4 |
           value = (String) m_resourceProperties.get(key);
83 |  |
         }
84 |  |
85 | 30 |
         if (value == null && m_configProperties.get() == null) {
86 | 1 |
           logger.error("Config initialization failed, always return default value!");
87 |  |
         }
88 |  |
89 | 30 |
         return value == null ? defaultValue : value;
90 |  |
       }
91 |  |
92 |  |
       @Override
93 |  |
       public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
94 | 4 |
         if (newProperties.equals(m_configProperties.get())) {
95 | 0 |
           return;
96 |  |
         }
97 | 4 |
         Properties newConfigProperties = new Properties();
98 | 4 |
         newConfigProperties.putAll(newProperties);
99 |  |
100 | 4 |
         Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties);
101 |  |
102 |  |
         //check double checked result
103 | 4 |
         if (actualChanges.isEmpty()) {
104 | 0 |
           return;
105 |  |
         }
106 |  |
107 | 4 |
         this.fireConfigChange(new ConfigChangeEvent(m_namespace, actualChanges));
108 |  |
109 | 4 |
         Cat.logEvent("Apollo.Client.ConfigChanges", m_namespace);
110 | 4 |
       }
111 |  |
112 |  |
       private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties) {
113 | 4 |
         List<ConfigChange> configChanges =
114 | 4 |
             calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);
115 |  |
116 | 4 |
         ImmutableMap.Builder<String, ConfigChange> actualChanges =
117 |  |
             new ImmutableMap.Builder<>();
118 |  |
119 |  |
         /** === Double check since DefaultConfig has multiple config sources ==== **/
120 |  |
121 |  |
         //1. use getProperty to update configChanges's old value
122 | 4 |
         for (ConfigChange change : configChanges) {
123 | 8 |
           change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
124 | 8 |
         }
125 |  |
126 |  |
         //2. update m_configProperties
127 | 4 |
         m_configProperties.set(newConfigProperties);
128 |  |
129 |  |
         //3. use getProperty to update configChange's new value and calc the final changes
130 | 4 |
         for (ConfigChange change : configChanges) {
131 | 8 |
           change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
132 | 1 |
           switch (change.getChangeType()) {
133 |  |
             case ADDED:
134 | 1 |
               if (Objects.equals(change.getOldValue(), change.getNewValue())) {
135 | 0 |
                 break;
136 |  |
               }
137 | 1 |
               if (change.getOldValue() != null) {
138 | 0 |
                 change.setChangeType(PropertyChangeType.MODIFIED);
139 |  |
               }
140 | 1 |
               actualChanges.put(change.getPropertyName(), change);
141 | 1 |
               break;
142 |  |
             case MODIFIED:
143 | 5 |
               if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
144 | 4 |
                 actualChanges.put(change.getPropertyName(), change);
145 |  |
               }
146 |  |
               break;
147 |  |
             case DELETED:
148 | 2 |
               if (Objects.equals(change.getOldValue(), change.getNewValue())) {
149 | 0 |
                 break;
150 |  |
               }
151 | 2 |
               if (change.getNewValue() != null) {
152 | 1 |
                 change.setChangeType(PropertyChangeType.MODIFIED);
153 |  |
               }
154 | 2 |
               actualChanges.put(change.getPropertyName(), change);
155 | 2 |
               break;
156 |  |
             default:
157 |  |
               //do nothing
158 |  |
               break;
159 |  |
           }
160 | 8 |
         }
161 | 4 |
         return actualChanges.build();
162 |  |
       }
163 |  |
164 |  |
       private Properties loadFromResource(String namespace) {
165 | 11 |
         String name = String.format("META-INF/config/%s.properties", namespace);
166 | 11 |
         InputStream in = ClassLoaderUtil.getLoader().getResourceAsStream(name);
167 | 11 |
         Properties properties = null;
168 |  |
169 | 11 |
         if (in != null) {
170 | 2 |
           properties = new Properties();
171 |  |
172 |  |
           try {
173 | 2 |
             properties.load(in);
174 | 0 |
           } catch (IOException ex) {
175 | 0 |
             Cat.logError(ex);
176 | 0 |
             logger.error("Load resource config for namespace {} failed", namespace, ex);
177 |  |
           } finally {
178 | 0 |
             try {
179 | 2 |
               in.close();
180 | 0 |
             } catch (IOException ex) {
181 |  |
               // ignore
182 | 2 |
             }
183 | 0 |
           }
184 |  |
         }
185 |  |
186 | 11 |
         return properties;
187 |  |
       }
188 |  |
     }