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