##### Coverage Report - com.ctrip.framework.apollo.internals.LocalFileConfigRepository

Classes in this File | Line Coverage | Branch Coverage | Complexity
LocalFileConfigRepository |
78% | 110/140
71% | 23/32
| 3.8461538461538463;3.846

1 |  |
     package com.ctrip.framework.apollo.internals;
2 |  |
3 |  |
     import com.google.common.base.Joiner;
4 |  |
     import com.google.common.base.Preconditions;
5 |  |
6 |  |
     import com.ctrip.framework.apollo.core.ConfigConsts;
7 |  |
     import com.ctrip.framework.apollo.core.utils.ClassLoaderUtil;
8 |  |
     import com.ctrip.framework.apollo.exceptions.ApolloConfigException;
9 |  |
     import com.ctrip.framework.apollo.util.ConfigUtil;
10 |  |
     import com.ctrip.framework.apollo.util.ExceptionUtil;
11 |  |
     import com.dianping.cat.Cat;
12 |  |
     import com.dianping.cat.message.Message;
13 |  |
     import com.dianping.cat.message.Transaction;
14 |  |
15 |  |
     import org.codehaus.plexus.PlexusContainer;
16 |  |
     import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
17 |  |
     import org.slf4j.Logger;
18 |  |
     import org.slf4j.LoggerFactory;
19 |  |
     import org.unidal.lookup.ContainerLoader;
20 |  |
21 |  |
     import java.io.File;
22 |  |
     import java.io.FileInputStream;
23 |  |
     import java.io.FileOutputStream;
24 |  |
     import java.io.IOException;
25 |  |
     import java.io.InputStream;
26 |  |
     import java.io.OutputStream;
27 |  |
     import java.nio.file.Files;
28 |  |
     import java.nio.file.Path;
29 |  |
     import java.nio.file.Paths;
30 |  |
     import java.util.Properties;
31 |  |
32 |  |
     /**
33 |  |
      * @author Jason Song(song_s@ctrip.com)
34 |  |
      */
35 |  |
     public class LocalFileConfigRepository extends AbstractConfigRepository
36 |  |
         implements RepositoryChangeListener {
37 | 1 |
       private static final Logger logger = LoggerFactory.getLogger(LocalFileConfigRepository.class);
38 |  |
       private static final String CONFIG_DIR = "/config-cache";
39 |  |
       private final PlexusContainer m_container;
40 |  |
       private final String m_namespace;
41 |  |
       private File m_baseDir;
42 |  |
       private final ConfigUtil m_configUtil;
43 |  |
       private volatile Properties m_fileProperties;
44 |  |
       private volatile ConfigRepository m_upstream;
45 |  |
46 |  |
       /**
47 |  |
        * Constructor.
48 |  |
        *
49 |  |
        * @param namespace the namespace
50 |  |
        */
51 | 14 |
       public LocalFileConfigRepository(String namespace) {
52 | 14 |
         m_namespace = namespace;
53 | 14 |
         m_container = ContainerLoader.getDefaultContainer();
54 |  |
         try {
55 | 14 |
           m_configUtil = m_container.lookup(ConfigUtil.class);
56 | 0 |
         } catch (ComponentLookupException ex) {
57 | 0 |
           Cat.logError(ex);
58 | 0 |
           throw new ApolloConfigException("Unable to load component!", ex);
59 | 14 |
         }
60 | 14 |
         this.setLocalCacheDir(findLocalCacheDir());
61 | 14 |
       }
62 |  |
63 |  |
       void setLocalCacheDir(File baseDir) {
64 | 20 |
         m_baseDir = baseDir;
65 | 20 |
         this.checkLocalConfigCacheDir(m_baseDir);
66 | 20 |
         this.trySync();
67 | 20 |
       }
68 |  |
69 |  |
       private File findLocalCacheDir() {
70 |  |
         try {
71 | 14 |
           String defaultCacheDir = m_configUtil.getDefaultLocalCacheDir();
72 | 14 |
           Path path = Paths.get(defaultCacheDir);
73 | 14 |
           if (!Files.exists(path)) {
74 | 6 |
             Files.createDirectories(path);
75 |  |
           }
76 | 8 |
           if (Files.exists(path) && Files.isWritable(path)) {
77 | 8 |
             return new File(defaultCacheDir, CONFIG_DIR);
78 |  |
           }
79 | 6 |
         } catch (Throwable ex) {
80 |  |
           //ignore
81 | 0 |
         }
82 |  |
83 | 6 |
         return new File(ClassLoaderUtil.getClassPath(), CONFIG_DIR);
84 |  |
       }
85 |  |
86 |  |
       @Override
87 |  |
       public Properties getConfig() {
88 | 14 |
         if (m_fileProperties == null) {
89 | 1 |
           sync();
90 |  |
         }
91 | 13 |
         Properties result = new Properties();
92 | 13 |
         result.putAll(m_fileProperties);
93 | 13 |
         return result;
94 |  |
       }
95 |  |
96 |  |
       @Override
97 |  |
       public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
98 |  |
         //clear previous listener
99 | 12 |
         if (m_upstream != null) {
100 | 0 |
           m_upstream.removeChangeListener(this);
101 |  |
         }
102 | 12 |
         m_upstream = upstreamConfigRepository;
103 | 12 |
         trySyncFromUpstream();
104 | 12 |
         upstreamConfigRepository.addChangeListener(this);
105 | 12 |
       }
106 |  |
107 |  |
       @Override
108 |  |
       public void onRepositoryChange(String namespace, Properties newProperties) {
109 | 11 |
         if (newProperties.equals(m_fileProperties)) {
110 | 7 |
           return;
111 |  |
         }
112 | 4 |
         Properties newFileProperties = new Properties();
113 | 4 |
         newFileProperties.putAll(newProperties);
114 | 4 |
         updateFileProperties(newFileProperties);
115 | 4 |
         this.fireRepositoryChange(namespace, newProperties);
116 | 4 |
       }
117 |  |
118 |  |
       @Override
119 |  |
       protected void sync() {
120 |  |
         //sync with upstream immediately
121 | 21 |
        boolean syncFromUpstreamResultSuccess = trySyncFromUpstream();
122 |  |
123 | 21 |
         if (syncFromUpstreamResultSuccess) {
124 | 0 |
           return;
125 |  |
         }
126 |  |
127 | 21 |
         Transaction transaction = Cat.newTransaction("Apollo.ConfigService", "syncLocalConfig");
128 | 21 |
         Throwable exception = null;
129 |  |
         try {
130 | 21 |
           transaction.addData("Basedir", m_baseDir.getAbsolutePath());
131 | 21 |
           m_fileProperties = this.loadFromLocalCacheFile(m_baseDir, m_namespace);
132 | 5 |
           transaction.setStatus(Message.SUCCESS);
133 | 16 |
         } catch (Throwable ex) {
134 | 16 |
           Cat.logError(ex);
135 | 16 |
           transaction.setStatus(ex);
136 | 16 |
           exception = ex;
137 |  |
           //ignore
138 |  |
         } finally {
139 | 21 |
           transaction.complete();
140 | 21 |
         }
141 |  |
142 | 21 |
         if (m_fileProperties == null) {
143 | 16 |
           throw new ApolloConfigException(
144 |  |
               "Load config from local config failed!", exception);
145 |  |
         }
146 | 5 |
       }
147 |  |
148 |  |
       private boolean trySyncFromUpstream() {
149 | 33 |
         if (m_upstream == null) {
150 | 20 |
           return false;
151 |  |
         }
152 |  |
         try {
153 | 13 |
           Properties properties = m_upstream.getConfig();
154 | 10 |
           updateFileProperties(properties);
155 | 10 |
           return true;
156 | 3 |
         } catch (Throwable ex) {
157 | 3 |
           Cat.logError(ex);
158 | 3 |
           logger
159 | 6 |
               .warn("Sync config from upstream repository {} failed, reason: {}", m_upstream.getClass(),
160 | 3 |
                   ExceptionUtil.getDetailMessage(ex));
161 |  |
         }
162 | 3 |
         return false;
163 |  |
       }
164 |  |
165 |  |
       private synchronized void updateFileProperties(Properties newProperties) {
166 | 14 |
         if (newProperties.equals(m_fileProperties)) {
167 | 0 |
           return;
168 |  |
         }
169 | 14 |
         this.m_fileProperties = newProperties;
170 | 14 |
         persistLocalCacheFile(m_baseDir, m_namespace);
171 | 14 |
       }
172 |  |
173 |  |
       private Properties loadFromLocalCacheFile(File baseDir, String namespace) throws IOException {
174 | 21 |
         Preconditions.checkNotNull(baseDir, "Basedir cannot be null");
175 |  |
176 | 21 |
         File file = assembleLocalCacheFile(baseDir, namespace);
177 | 21 |
         Properties properties = null;
178 |  |
179 | 21 |
         if (file.isFile() && file.canRead()) {
180 | 5 |
           InputStream in = null;
181 |  |
182 |  |
           try {
183 | 5 |
             in = new FileInputStream(file);
184 |  |
185 | 5 |
             properties = new Properties();
186 | 5 |
             properties.load(in);
187 | 0 |
           } catch (IOException ex) {
188 | 0 |
             Cat.logError(ex);
189 | 0 |
             throw new ApolloConfigException(String
190 | 0 |
                 .format("Loading config from local cache file %s failed", file.getAbsolutePath()), ex);
191 |  |
           } finally {
192 | 0 |
             try {
193 | 5 |
               if (in != null) {
194 | 5 |
                 in.close();
195 |  |
               }
196 | 0 |
             } catch (IOException ex) {
197 |  |
               // ignore
198 | 5 |
             }
199 | 0 |
           }
200 | 5 |
         } else {
201 | 16 |
           throw new ApolloConfigException(
202 | 16 |
               String.format("Cannot read from local cache file %s", file.getAbsolutePath()));
203 |  |
         }
204 |  |
205 | 5 |
         return properties;
206 |  |
       }
207 |  |
208 |  |
       void persistLocalCacheFile(File baseDir, String namespace) {
209 | 14 |
         if (baseDir == null) {
210 | 0 |
           return;
211 |  |
         }
212 | 14 |
         File file = assembleLocalCacheFile(baseDir, namespace);
213 |  |
214 | 14 |
         OutputStream out = null;
215 |  |
216 | 14 |
         Transaction transaction = Cat.newTransaction("Apollo.ConfigService", "persistLocalConfigFile");
217 | 14 |
         transaction.addData("LocalConfigFile", file.getAbsolutePath());
218 |  |
         try {
219 | 14 |
           out = new FileOutputStream(file);
220 | 14 |
           m_fileProperties.store(out, "Persisted by DefaultConfig");
221 | 14 |
           transaction.setStatus(Message.SUCCESS);
222 | 0 |
         } catch (IOException ex) {
223 | 0 |
           ApolloConfigException exception =
224 |  |
               new ApolloConfigException(
225 | 0 |
                   String.format("Persist local cache file %s failed", file.getAbsolutePath()), ex);
226 | 0 |
           Cat.logError(exception);
227 | 0 |
           transaction.setStatus(exception);
228 | 0 |
           logger.warn("Persist local cache file {} failed, reason: {}.", file.getAbsolutePath(),
229 | 0 |
               ExceptionUtil.getDetailMessage(ex));
230 |  |
         } finally {
231 | 14 |
           if (out != null) {
232 |  |
             try {
233 | 14 |
               out.close();
234 | 0 |
             } catch (IOException ex) {
235 |  |
               //ignore
236 | 14 |
             }
237 |  |
           }
238 | 14 |
           transaction.complete();
239 | 14 |
         }
240 | 14 |
       }
241 |  |
242 |  |
       private void checkLocalConfigCacheDir(File baseDir) {
243 | 20 |
         if (baseDir.exists()) {
244 | 19 |
           return;
245 |  |
         }
246 | 1 |
         Transaction transaction = Cat.newTransaction("Apollo.ConfigService", "createLocalConfigDir");
247 | 1 |
         transaction.addData("BaseDir", baseDir.getAbsolutePath());
248 |  |
         try {
249 | 1 |
           Files.createDirectory(baseDir.toPath());
250 | 1 |
           transaction.setStatus(Message.SUCCESS);
251 | 0 |
         } catch (IOException ex) {
252 | 0 |
           ApolloConfigException exception =
253 |  |
               new ApolloConfigException(
254 | 0 |
                   String.format("Create local config directory %s failed", baseDir.getAbsolutePath()), ex);
255 | 0 |
           Cat.logError(exception);
256 | 0 |
           transaction.setStatus(exception);
257 | 0 |
           logger.warn(
258 |  |
               "Unable to create local config cache directory {}, reason: {}. Will not able to cache config file.",
259 | 0 |
               baseDir.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex));
260 |  |
         } finally {
261 | 1 |
           transaction.complete();
262 | 1 |
         }
263 | 1 |
       }
264 |  |
265 |  |
       File assembleLocalCacheFile(File baseDir, String namespace) {
266 | 35 |
         String fileName =
267 | 70 |
             String.format("%s.properties", Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
268 | 35 |
                 .join(m_configUtil.getAppId(), m_configUtil.getCluster(), namespace));
269 | 35 |
         return new File(baseDir, fileName);
270 |  |
       }
271 |  |
     }