1 5 package com.tc.config.schema.setup; 6 7 import org.apache.commons.io.CopyUtils; 8 import org.apache.xmlbeans.XmlError; 9 import org.apache.xmlbeans.XmlException; 10 import org.apache.xmlbeans.XmlInteger; 11 import org.xml.sax.SAXException ; 12 13 import com.tc.config.schema.beanfactory.BeanWithErrors; 14 import com.tc.config.schema.beanfactory.ConfigBeanFactory; 15 import com.tc.config.schema.defaults.DefaultValueProvider; 16 import com.tc.config.schema.defaults.FromSchemaDefaultValueProvider; 17 import com.tc.config.schema.dynamic.ParameterSubstituter; 18 import com.tc.config.schema.repository.ApplicationsRepository; 19 import com.tc.config.schema.repository.MutableBeanRepository; 20 import com.tc.config.schema.setup.sources.ConfigurationSource; 21 import com.tc.config.schema.setup.sources.FileConfigurationSource; 22 import com.tc.config.schema.setup.sources.ResourceConfigurationSource; 23 import com.tc.config.schema.setup.sources.ServerConfigurationSource; 24 import com.tc.config.schema.setup.sources.URLConfigurationSource; 25 import com.tc.logging.CustomerLogging; 26 import com.tc.logging.TCLogger; 27 import com.tc.logging.TCLogging; 28 import com.tc.util.Assert; 29 import com.terracottatech.config.Server; 30 import com.terracottatech.config.Servers; 31 import com.terracottatech.config.TcConfigDocument; 32 import com.terracottatech.config.TcConfigDocument.TcConfig; 33 34 import java.io.ByteArrayInputStream ; 35 import java.io.ByteArrayOutputStream ; 36 import java.io.File ; 37 import java.io.IOException ; 38 import java.io.InputStream ; 39 import java.io.StringWriter ; 40 import java.util.regex.Matcher ; 41 import java.util.regex.Pattern ; 42 43 import javax.xml.parsers.ParserConfigurationException ; 44 45 48 public class StandardXMLFileConfigurationCreator implements ConfigurationCreator { 49 50 private static final TCLogger consoleLogger = CustomerLogging.getConsoleLogger(); 51 52 private static final long GET_CONFIGURATION_TOTAL_TIMEOUT = 5 * 60 * 1000; private static final long GET_CONFIGURATION_ONE_SOURCE_TIMEOUT = 30 * 1000; private static final long MIN_RETRY_TIMEOUT = 5 * 1000; 59 protected final String configurationSpec; 60 protected final File defaultDirectory; 61 protected final ConfigBeanFactory beanFactory; 62 63 private TCLogger logger; 64 private boolean loadedFromTrustedSource; 65 private String configDescription; 66 private File directoryLoadedFrom; 67 68 public StandardXMLFileConfigurationCreator(String configurationSpec, File defaultDirectory, 69 ConfigBeanFactory beanFactory) { 70 this(TCLogging.getLogger(StandardXMLFileConfigurationCreator.class), configurationSpec, defaultDirectory, 71 beanFactory); 72 } 73 74 public StandardXMLFileConfigurationCreator(TCLogger logger, String configurationSpec, File defaultDirectory, 75 ConfigBeanFactory beanFactory) { 76 Assert.assertNotBlank(configurationSpec); 77 Assert.assertNotNull(defaultDirectory); 78 Assert.assertNotNull(beanFactory); 79 80 this.logger = logger; 81 this.configurationSpec = configurationSpec; 82 this.defaultDirectory = defaultDirectory; 83 this.beanFactory = beanFactory; 84 this.configDescription = null; 85 } 86 87 private static final Pattern SERVER_PATTERN = Pattern.compile("(.*):(.*)", Pattern.CASE_INSENSITIVE); 88 89 private static final Pattern RESOURCE_PATTERN = Pattern.compile("resource://(.*)", Pattern.CASE_INSENSITIVE); 90 91 private static final Pattern URL_PATTERN = Pattern.compile("[A-Za-z][A-Za-z]+://.*"); 93 94 private ConfigurationSource[] createConfigurationSources() throws ConfigurationSetupException { 95 String [] components = configurationSpec.split(","); 96 ConfigurationSource[] out = new ConfigurationSource[components.length]; 97 98 for (int i = 0; i < components.length; ++i) { 99 String thisComponent = components[i]; 100 ConfigurationSource source = null; 101 102 if (source == null) source = attemptToCreateServerSource(thisComponent); 103 if (source == null) source = attemptToCreateResourceSource(thisComponent); 104 if (source == null) source = attemptToCreateURLSource(thisComponent); 105 if (source == null) source = attemptToCreateFileSource(thisComponent); 106 107 if (source == null) { 108 throw new ConfigurationSetupException("The location '" + thisComponent 110 + "' is not in any recognized format -- it doesn't " + "seem to be a server, resource, URL, or file."); 111 } 112 113 out[i] = source; 114 } 115 116 return out; 117 } 118 119 private ConfigurationSource attemptToCreateServerSource(String text) { 120 Matcher matcher = SERVER_PATTERN.matcher(text); 121 if (matcher.matches()) { 122 String host = matcher.group(1); 123 String portText = matcher.group(2); 124 125 try { 126 return new ServerConfigurationSource(host, Integer.parseInt(portText)); 127 } catch (Exception e) { 128 } 129 } 130 return null; 131 } 132 133 private ConfigurationSource attemptToCreateResourceSource(String text) { 134 Matcher matcher = RESOURCE_PATTERN.matcher(text); 135 if (matcher.matches()) return new ResourceConfigurationSource(matcher.group(1), getClass()); 136 else return null; 137 } 138 139 private ConfigurationSource attemptToCreateFileSource(String text) { 140 return new FileConfigurationSource(text, defaultDirectory); 141 } 142 143 private ConfigurationSource attemptToCreateURLSource(String text) { 144 Matcher matcher = URL_PATTERN.matcher(text); 145 if (matcher.matches()) return new URLConfigurationSource(text); 146 else return null; 147 } 148 149 public void createConfigurationIntoRepositories(MutableBeanRepository l1BeanRepository, 150 MutableBeanRepository l2sBeanRepository, 151 MutableBeanRepository systemBeanRepository, 152 ApplicationsRepository applicationsRepository) 153 throws ConfigurationSetupException { 154 Assert.assertNotNull(l1BeanRepository); 155 Assert.assertNotNull(l2sBeanRepository); 156 Assert.assertNotNull(systemBeanRepository); 157 Assert.assertNotNull(applicationsRepository); 158 159 ConfigurationSource[] sources = createConfigurationSources(); 160 long startTime = System.currentTimeMillis(); 161 ConfigurationSource[] remainingSources = new ConfigurationSource[sources.length]; 162 ConfigurationSource loadedSource = null; 163 System.arraycopy(sources, 0, remainingSources, 0, sources.length); 164 long lastLoopStartTime = 0; 165 int iteration = 0; 166 InputStream out = null; 167 boolean trustedSource = false; 168 String descrip = null; 169 170 while (iteration == 0 || (System.currentTimeMillis() - startTime < GET_CONFIGURATION_TOTAL_TIMEOUT)) { 171 sleepIfNecessaryToAvoidPoundingSources(lastLoopStartTime); 172 lastLoopStartTime = System.currentTimeMillis(); 173 174 for (int i = 0; i < remainingSources.length; ++i) { 175 if (remainingSources[i] == null) continue; 176 out = trySource(remainingSources, i); 177 178 if (out != null) { 179 loadedSource = remainingSources[i]; 180 trustedSource = loadedSource.isTrusted(); 181 descrip = loadedSource.toString(); 182 break; 183 } 184 } 185 186 if (out != null) break; 187 188 ++iteration; 189 190 boolean haveSources = false; 191 for (int i = 0; i < remainingSources.length; ++i) 192 haveSources = haveSources || remainingSources[i] != null; 193 if (!haveSources) { 194 break; 196 } 197 } 198 199 if (out == null) configurationFetchFailed(sources, startTime); 200 201 loadConfigurationData(out, trustedSource, descrip, l1BeanRepository, l2sBeanRepository, systemBeanRepository, 202 applicationsRepository); 203 consoleLogger.info("Configuration loaded from the " + descrip + "."); 204 } 205 206 private void configurationFetchFailed(ConfigurationSource[] sources, long startTime) 207 throws ConfigurationSetupException { 208 String text = "Could not fetch configuration data from "; 209 if (sources.length > 1) text += "" + sources.length + " different configuration sources"; 210 else text += "the " + sources[0]; 211 text += ". "; 212 213 if (sources.length > 1) { 214 text += " The sources we tried are: "; 215 for (int i = 0; i < sources.length; ++i) { 216 if (i > 0) text += ", "; 217 if (i == sources.length - 1) text += "and "; 218 text += "the " + sources[i].toString(); 219 } 220 text += ". "; 221 } 222 223 if (System.currentTimeMillis() - startTime >= GET_CONFIGURATION_TOTAL_TIMEOUT) { 224 text += " Fetch attempt duration: " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds."; 225 } 226 227 text += "\n\nTo correct this problem specify a valid configuration location using the "; 228 text += "-f/--config command-line options."; 229 230 consoleLogger.error(text); 231 throw new ConfigurationSetupException(text); 232 } 233 234 private InputStream trySource(ConfigurationSource[] remainingSources, int i) { 235 InputStream out = null; 236 237 try { 238 logger.info("Attempting to load configuration from the " + remainingSources[i] + "..."); 239 out = remainingSources[i].getInputStream(GET_CONFIGURATION_ONE_SOURCE_TIMEOUT); 240 directoryLoadedFrom = remainingSources[i].directoryLoadedFrom(); 241 } catch (ConfigurationSetupException cse) { 242 String text = "We couldn't load configuration data from the " + remainingSources[i]; 243 text += "; this error is permanent, so this source will not be retried."; 244 245 if (remainingSources.length > 1) text += " Skipping this source and going to the next one."; 246 247 text += " (Error: " + cse.getLocalizedMessage() + ".)"; 248 249 consoleLogger.warn(text); 250 251 remainingSources[i] = null; 252 } catch (IOException ioe) { 253 String text = "We couldn't load configuration data from the " + remainingSources[i]; 254 255 if (remainingSources.length > 1) { 256 text += "; this error is temporary, so this source will be retried later if configuration can't be loaded elsewhere. "; 257 text += "Skipping this source and going to the next one."; 258 } else { 259 text += "; retrying."; 260 } 261 262 text += " (Error: " + ioe.getLocalizedMessage() + ".)"; 263 consoleLogger.warn(text); 264 } 265 266 return out; 267 } 268 269 private void sleepIfNecessaryToAvoidPoundingSources(long lastLoopStartTime) { 270 long delay = MIN_RETRY_TIMEOUT - (System.currentTimeMillis() - lastLoopStartTime); 271 if (delay > 0) { 272 try { 273 logger.info("Waiting " + delay + " ms until we try to get configuration data again..."); 274 Thread.sleep(delay); 275 } catch (InterruptedException ie) { 276 } 278 } 279 } 280 281 private void logCopyOfConfig(InputStream in, String descrip) throws IOException { 282 StringWriter sw = new StringWriter (); 283 CopyUtils.copy(in, sw); 284 285 logger.info("Successfully loaded configuration from the " + descrip + ". Config is:\n\n" + sw.toString()); 286 } 287 288 private void loadConfigurationData(InputStream in, boolean trustedSource, String descrip, 289 MutableBeanRepository clientBeanRepository, 290 MutableBeanRepository serversBeanRepository, 291 MutableBeanRepository systemBeanRepository, 292 ApplicationsRepository applicationsRepository) throws ConfigurationSetupException { 293 try { 294 loadedFromTrustedSource = trustedSource; 295 configDescription = descrip; 296 297 ByteArrayOutputStream dataCopy = new ByteArrayOutputStream (); 298 CopyUtils.copy(in, dataCopy); 299 300 logCopyOfConfig(new ByteArrayInputStream (dataCopy.toByteArray()), descrip); 301 302 InputStream copyIn = new ByteArrayInputStream (dataCopy.toByteArray()); 303 BeanWithErrors beanWithErrors = beanFactory.createBean(copyIn, descrip); 304 305 if (beanWithErrors.errors() != null && beanWithErrors.errors().length > 0) { 306 logger.debug("Configuration didn't parse; it had " + beanWithErrors.errors().length + " error(s)."); 307 308 StringBuffer buf = new StringBuffer (); 309 for (int i = 0; i < beanWithErrors.errors().length; ++i) { 310 XmlError error = beanWithErrors.errors()[i]; 311 buf.append(" [" + i + "]: Line " + error.getLine() + ", column " + error.getColumn() + ": " 312 + error.getMessage() + "\n"); 313 } 314 315 throw new ConfigurationSetupException("The configuration data in the " + descrip + " does not obey the " 316 + "Terracotta schema:\n" + buf); 317 } else { 318 logger.debug("Configuration is valid."); 319 } 320 321 TcConfig config = ((TcConfigDocument) beanWithErrors.bean()).getTcConfig(); 322 Servers servers = config.getServers(); 323 if (servers != null) { 324 Server server; 325 for (int i = 0; i < servers.sizeOfServerArray(); i++) { 326 server = servers.getServerArray(i); 327 if (!server.isSetHost() || server.getHost().trim().length() == 0) { 330 server.setHost("%i"); 331 } 332 if (!server.isSetName() || server.getName().trim().length() == 0) { 333 int dsoPort = server.getDsoPort(); 334 if (dsoPort == 0) { 335 final DefaultValueProvider defaultValueProvider = new FromSchemaDefaultValueProvider(); 337 if (defaultValueProvider.hasDefault(server.schemaType(), "dso-port")) { 338 final XmlInteger defaultValue = (XmlInteger) defaultValueProvider.defaultFor(server.schemaType(), 339 "dso-port"); 340 dsoPort = defaultValue.getBigIntegerValue().intValue(); 341 } 342 } 343 server.setName(server.getHost() + (dsoPort > 0 ? ":" + dsoPort : "")); 344 } 345 server.setHost(ParameterSubstituter.substitute(server.getHost())); 347 server.setName(ParameterSubstituter.substitute(server.getName())); 348 } 349 } 350 351 clientBeanRepository.setBean(config.getClients(), descrip); 352 serversBeanRepository.setBean(config.getServers(), descrip); 353 systemBeanRepository.setBean(config.getSystem(), descrip); 354 355 if (config.isSetApplication()) { 356 applicationsRepository.repositoryFor(TVSConfigurationSetupManagerFactory.DEFAULT_APPLICATION_NAME).setBean( 357 config.getApplication(), descrip); 358 } 359 } catch (IOException ioe) { 360 throw new ConfigurationSetupException("We were unable to read configuration data from the " + descrip + ": " 361 + ioe.getLocalizedMessage(), ioe); 362 } catch (SAXException saxe) { 363 throw new ConfigurationSetupException("The configuration data in the " + descrip + " is not well-formed XML: " 364 + saxe.getLocalizedMessage(), saxe); 365 } catch (ParserConfigurationException pce) { 366 throw Assert.failure("The XML parser can't be configured correctly; this should not happen.", pce); 367 } catch (XmlException xmle) { 368 throw new ConfigurationSetupException("The configuration data in the " + descrip + " does not obey the " 369 + "Terracotta schema: " + xmle.getLocalizedMessage(), xmle); 370 } 371 } 372 373 public File directoryConfigurationLoadedFrom() { 374 return directoryLoadedFrom; 375 } 376 377 public boolean loadedFromTrustedSource() { 378 return loadedFromTrustedSource; 379 } 380 381 public String describeSources() { 382 if (configDescription == null) { 383 return "The configuration specified by '" + configurationSpec + "'"; 384 } else { 385 return configDescription; 386 } 387 } 388 389 } 390 | Popular Tags |