/* Copyright 2012 Google, Inc. * * 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.arbeitspferde.groningen.common; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.Singleton; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.reflections.Reflections; import org.reflections.scanners.FieldAnnotationsScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; import java.lang.reflect.Field; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.logging.Logger; @Singleton public class SettingsProvider implements Provider<Settings> { private static final Logger log = Logger.getLogger(SettingsProvider.class.getCanonicalName()); private static final String PROXIED_FLAGS_PACKAGE_WHITELIST_PREFIX = "org.arbeitspferde"; private final String[] args; private final SupplementalSettingsProcessor supplementalSettingsProcessor; /* * In case Groningen isn't able to hold the load with a single server instance, it will be * sharded. * I.e. multiple server instances (shards) will run simultaneously. They will manage different * pipelines based on their pipeline ids (see {@link PipelineIdGenerator} and * shardIndexForPipelineId). Also see sharding-aware {@link PipelineRestorer} as an example * of how sharding support is implemented. */ @Option( name = "--numShards", usage = "Total number of shards in this Groningen deployment.") public int numShards = 1; @Option( name = "--shardIndex", usage = "Index of this server instance's shard in this Groningen deployment.") public int shardIndex = 0; @Option( name = "--datastore", usage = "Datastore class to use.") public String datastore = "org.arbeitspferde.groningen.datastore.InMemoryDatastore"; @Option( name = "--historyDatastore", usage = "Datastore class to use for history storage.") public String historyDatastore = "org.arbeitspferde.groningen.historydatastore.MemoryHistoryDatastore"; @Option( name = "--port", usage = "The port on which to service HTTP requests.") public Integer port = 8080; @Option( name = "--startupSubservicesDeadlineSeconds", usage = "How long Groningen will wait at most for its subservices to start (seconds).") public Integer startupSubservicesDeadlineSeconds = 60; @Option( name = "--shutdownSubservicesDeadlineSeconds", usage = "How long Groningen will wait at most for its subservices to stop (seconds).") public Integer shutdownSubservicesDeadlineSeconds = 60; @Option( name = "--configFileNames", aliases = {"--f", "--configFileName" /* TOTO(mbushkov): deprecated, remove soon */}, usage = "Comma-separated list of fully-qualified paths to the configuration files (one " + "per pipeline).") public String configFileNames; @Option( name = "--eventLogPrefix", usage = "The path along with base string prefix for the Groningen event log.") public String eventLogPrefix = "alloc/logs/tmp-groningen_events"; @Option( name = "--eventLogRotateBytesSize", usage = "The quantity in bytes that the Groningen event log may grow before being rotated.") public Integer eventLogRotateSizeBytes = 524288000; @Option( name = "--eventLogFlushIntervalSeconds", usage = "The number of seconds that may transpire between Groningen event log flushing.") public Integer eventLogFlushIntervalSeconds = 60; @Inject public SettingsProvider(final String[] args, final SupplementalSettingsProcessor supplementalSettingsProcessor) { this.args = args; this.supplementalSettingsProcessor = supplementalSettingsProcessor; } @Override public Settings get() { log.info("Providing settings."); final Collection<Field> annotated = findAnnotatedFields(); final Collection<String> args4jWhitelist = makeArgs4jWhitelist(annotated); final PartitionedArguments partitioned = separateArg4jArgumentsFromOthers(args, args4jWhitelist); final Collection<String> gnuSanitizedArg4sjArguments = filterGnuStyleArguments(partitioned.getArgs4jArguments()); try { new CmdLineParser(this).parseArgument(gnuSanitizedArg4sjArguments); } catch (final CmdLineException e) { throw new ProvisionException("Error processing arg4j arguments.", e); } final Collection<String> unmatched = partitioned.getOtherArguments(); final String[] matchedAsArray = gnuSanitizedArg4sjArguments.toArray(new String[gnuSanitizedArg4sjArguments.size()]); final String[] unmatchedAsArray = unmatched.toArray(new String[unmatched.size()]); supplementalSettingsProcessor.process(matchedAsArray, unmatchedAsArray); return new Settings() { @Override public Integer getPort() { return port; } @Override public Integer getStartupSubservicesDeadlineSeconds() { return startupSubservicesDeadlineSeconds; } @Override public Integer getShutdownSubservicesDeadlineSeconds() { return shutdownSubservicesDeadlineSeconds; } @Override public String[] getConfigFileNames() { if (Strings.isNullOrEmpty(configFileNames)) { return new String[0]; } else { return configFileNames.split(","); } } @Override public String getEventLogPrefix() { return eventLogPrefix; } @Override public Integer getEventLogRotateSizeBytes() { return eventLogRotateSizeBytes; } @Override public Integer getEventLogFlushIntervalSeconds() { return eventLogFlushIntervalSeconds; } @Override public Integer getNumShards() { return numShards; } @Override public Integer getShardIndex() { return shardIndex; } @Override public String getDatastore() { return datastore; } @Override public String getHistoryDatastore() { return historyDatastore; } }; } private Collection<Field> findAnnotatedFields() { final Reflections reflections = new Reflections(new ConfigurationBuilder() .filterInputsBy(new FilterBuilder() .include(FilterBuilder.prefix(PROXIED_FLAGS_PACKAGE_WHITELIST_PREFIX))) .setUrls(ClasspathHelper.forPackage(PROXIED_FLAGS_PACKAGE_WHITELIST_PREFIX)) .setScanners(new FieldAnnotationsScanner())); return reflections.getFieldsAnnotatedWith(Option.class); } private Collection<String> makeArgs4jWhitelist(final Collection<Field> fields) { final Collection<String> emission = new HashSet<>(); for (final Field field : fields) { final Option optionAnnotation = field.getAnnotation(Option.class); if (optionAnnotation != null) { emission.add(optionAnnotation.name()); } final String[] aliases = optionAnnotation.aliases(); if (aliases != null) { for (final String alias : aliases) { emission.add(alias); } } } return emission; } private PartitionedArguments separateArg4jArgumentsFromOthers(final String[] arguments, final Collection<String> args4jPrefixWhitelist) { final ImmutableList.Builder<String> args4jArguments = ImmutableList.builder(); final ImmutableList.Builder<String> unmatchedArguments = ImmutableList.builder(); for (final String argument : args) { if (argument.startsWith("--")) { boolean wasAdded = false; for (final String args4jCandidate : args4jPrefixWhitelist) { if (!wasAdded && argument.startsWith(args4jCandidate)) { args4jArguments.add(argument); wasAdded = true; } } if (!wasAdded) { unmatchedArguments.add(argument); } } } return new PartitionedArguments() { @Override public ImmutableList<String> getOtherArguments() { return unmatchedArguments.build(); } @Override public ImmutableList<String> getArgs4jArguments() { return args4jArguments.build(); } }; } private ImmutableList<String> filterGnuStyleArguments(final List<String> arguments) { final ImmutableList.Builder<String> gnuSanitizedArguments = ImmutableList.builder(); for (int i = 0; i < arguments.size(); i++) { final String str = arguments.get(i); if (str.equals("--")) { while (i < arguments.size()) { gnuSanitizedArguments.add(arguments.get(i++)); } break; } if (str.startsWith("--")) { final int eq = str.indexOf('='); if (eq > 0) { gnuSanitizedArguments.add(str.substring(0, eq)); gnuSanitizedArguments.add(str.substring(eq + 1)); continue; } else { gnuSanitizedArguments.add(str); } } gnuSanitizedArguments.add(str); } return gnuSanitizedArguments.build(); } private interface PartitionedArguments { ImmutableList<String> getArgs4jArguments(); ImmutableList<String> getOtherArguments(); } }