Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

ConfigManager.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.config;
  18. import com.dmdirc.config.binding.ConfigBinder;
  19. import com.dmdirc.config.provider.AggregateConfigProvider;
  20. import com.dmdirc.config.provider.ConfigChangeListener;
  21. import com.dmdirc.config.provider.ConfigProvider;
  22. import com.dmdirc.config.provider.ConfigProviderMigrator;
  23. import com.dmdirc.util.validators.Validator;
  24. import com.google.common.collect.ArrayListMultimap;
  25. import com.google.common.collect.Multimap;
  26. import java.util.ArrayList;
  27. import java.util.Collection;
  28. import java.util.HashMap;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Set;
  33. import java.util.TreeMap;
  34. import org.slf4j.Logger;
  35. import org.slf4j.LoggerFactory;
  36. /**
  37. * The config manager manages the various config sources for each entity.
  38. */
  39. class ConfigManager implements ConfigChangeListener, ConfigProviderListener,
  40. AggregateConfigProvider {
  41. private static final Logger LOG = LoggerFactory.getLogger(ConfigManager.class);
  42. /** Temporary map for lookup stats. */
  43. private static final Map<String, Integer> STATS = new TreeMap<>();
  44. /** Magical domain to redirect to the version identity. */
  45. private static final String VERSION_DOMAIN = "version";
  46. /** A list of sources for this config manager. */
  47. private final List<ConfigFileBackedConfigProvider> sources = new ArrayList<>();
  48. /** The listeners registered for this manager. */
  49. private final Multimap<String, ConfigChangeListener> listeners = ArrayListMultimap.create();
  50. /** The config binder to use for this manager. */
  51. private final ConfigBinder binder;
  52. /** The manager to use to fetch global state. */
  53. private final IdentityManager manager;
  54. /** The protocol this manager is for. */
  55. private String protocol;
  56. /** The ircd this manager is for. */
  57. private String ircd;
  58. /** The network this manager is for. */
  59. private String network;
  60. /** The server this manager is for. */
  61. private String server;
  62. /** The channel this manager is for. */
  63. private String channel;
  64. /**
  65. * Creates a new instance of ConfigManager.
  66. *
  67. * @param manager The manager to use to retrieve global state horribly.
  68. * @param protocol The protocol for this manager
  69. * @param ircd The name of the ircd for this manager
  70. * @param network The name of the network for this manager
  71. * @param server The name of the server for this manager
  72. *
  73. * @since 0.6.3
  74. */
  75. ConfigManager(
  76. final IdentityManager manager,
  77. final String protocol, final String ircd,
  78. final String network, final String server) {
  79. this(manager, protocol, ircd, network, server, "<Unknown>");
  80. }
  81. /**
  82. * Creates a new instance of ConfigManager.
  83. *
  84. * @param manager The manager to use to retrieve global state horribly.
  85. * @param protocol The protocol for this manager
  86. * @param ircd The name of the ircd for this manager
  87. * @param network The name of the network for this manager
  88. * @param server The name of the server for this manager
  89. * @param channel The name of the channel for this manager
  90. *
  91. * @since 0.6.3
  92. */
  93. ConfigManager(
  94. final IdentityManager manager,
  95. final String protocol, final String ircd,
  96. final String network, final String server, final String channel) {
  97. final String chanName = channel + '@' + network;
  98. this.manager = manager;
  99. this.protocol = protocol;
  100. this.ircd = ircd;
  101. this.network = network;
  102. this.server = server;
  103. this.channel = chanName;
  104. binder = new ConfigBinderImpl(this);
  105. }
  106. @Override
  107. public ConfigBinder getBinder() {
  108. return binder;
  109. }
  110. @Override
  111. public String getOption(final String domain, final String option,
  112. final Validator<String> validator) {
  113. doStats(domain, option);
  114. if (VERSION_DOMAIN.equals(domain)) {
  115. final String response = manager.getVersionSettings().getOption(VERSION_DOMAIN, option);
  116. if (response == null || validator.validate(response).isFailure()) {
  117. return null;
  118. }
  119. return response;
  120. }
  121. synchronized (sources) {
  122. for (ConfigProvider source : sources) {
  123. if (source.hasOption(domain, option, validator)) {
  124. return source.getOption(domain, option, validator);
  125. }
  126. }
  127. }
  128. return null;
  129. }
  130. @Override
  131. public boolean hasOption(final String domain, final String option,
  132. final Validator<String> validator) {
  133. doStats(domain, option);
  134. if (VERSION_DOMAIN.equals(domain)) {
  135. final String response = manager.getVersionSettings().getOption(VERSION_DOMAIN, option);
  136. return response != null && !validator.validate(response).isFailure();
  137. }
  138. synchronized (sources) {
  139. for (ConfigProvider source : sources) {
  140. if (source.hasOption(domain, option, validator)) {
  141. return true;
  142. }
  143. }
  144. }
  145. return false;
  146. }
  147. @Override
  148. public Map<String, String> getOptions(final String domain) {
  149. if (VERSION_DOMAIN.equals(domain)) {
  150. return manager.getVersionSettings().getOptions(domain);
  151. }
  152. final Map<String, String> res = new HashMap<>();
  153. synchronized (sources) {
  154. for (int i = sources.size() - 1; i >= 0; i--) {
  155. res.putAll(sources.get(i).getOptions(domain));
  156. }
  157. }
  158. return res;
  159. }
  160. /**
  161. * Removes the specified identity from this manager.
  162. *
  163. * @param identity The identity to be removed
  164. */
  165. public void removeIdentity(final ConfigProvider identity) {
  166. if (!sources.contains(identity)) {
  167. return;
  168. }
  169. final Collection<String[]> changed = new ArrayList<>();
  170. // Determine which settings will have changed
  171. for (String domain : identity.getDomains()) {
  172. identity.getOptions(domain).keySet().stream()
  173. .filter(option -> identity.equals(getScope(domain, option)))
  174. .forEach(option -> changed.add(new String[]{domain, option}));
  175. }
  176. synchronized (sources) {
  177. identity.removeListener(this);
  178. sources.remove(identity);
  179. }
  180. // Fire change listeners
  181. for (String[] setting : changed) {
  182. configChanged(setting[0], setting[1]);
  183. }
  184. }
  185. /**
  186. * Retrieves the identity that currently defines the specified domain and option.
  187. *
  188. * @param domain The domain to search for
  189. * @param option The option to search for
  190. *
  191. * @return The identity that defines that setting, or null on failure
  192. */
  193. protected ConfigProvider getScope(final String domain, final String option) {
  194. if (VERSION_DOMAIN.equals(domain)) {
  195. return manager.getVersionSettings();
  196. }
  197. synchronized (sources) {
  198. for (ConfigProvider source : sources) {
  199. if (source.hasOptionString(domain, option)) {
  200. return source;
  201. }
  202. }
  203. }
  204. return null;
  205. }
  206. /**
  207. * Checks whether the specified identity applies to this config manager.
  208. *
  209. * @param identity The identity to test
  210. *
  211. * @return True if the identity applies, false otherwise
  212. */
  213. public boolean identityApplies(final ConfigFileBackedConfigProvider identity) {
  214. final String comp;
  215. switch (identity.getTarget().getType()) {
  216. case PROTOCOL:
  217. comp = protocol;
  218. break;
  219. case IRCD:
  220. comp = ircd;
  221. break;
  222. case NETWORK:
  223. comp = network;
  224. break;
  225. case SERVER:
  226. comp = server;
  227. break;
  228. case CHANNEL:
  229. comp = channel;
  230. break;
  231. case CUSTOM:
  232. // We don't want custom identities
  233. comp = null;
  234. break;
  235. default:
  236. comp = "";
  237. break;
  238. }
  239. final boolean result = comp != null
  240. && identityTargetMatches(identity.getTarget().getData(), comp);
  241. LOG.trace("Checking if identity {} applies. Comparison: {}, target: {}, result: {}",
  242. identity, comp, identity.getTarget().getData(), result);
  243. return result;
  244. }
  245. /**
  246. * Determines whether the specified identity target matches the desired target. If the desired
  247. * target is prefixed with "re:", it is treated as a regular expression; otherwise the strings
  248. * are compared lexicographically to determine a match.
  249. *
  250. * @param desired The target string required by this config manager
  251. * @param actual The target string supplied by the identity
  252. *
  253. * @return True if the identity should be applied, false otherwise
  254. *
  255. * @since 0.6.3m2
  256. */
  257. protected boolean identityTargetMatches(final String actual, final String desired) {
  258. return actual.startsWith("re:") ? desired.matches(actual.substring(3))
  259. : actual.equalsIgnoreCase(desired);
  260. }
  261. /**
  262. * Called whenever there is a new identity available. Checks if the identity is relevant for
  263. * this manager, and adds it if it is.
  264. *
  265. * @param identity The identity to be checked
  266. */
  267. public void checkIdentity(final ConfigFileBackedConfigProvider identity) {
  268. if (!sources.contains(identity) && identityApplies(identity)) {
  269. synchronized (sources) {
  270. sources.add(identity);
  271. identity.addListener(this);
  272. sources.sort(new ConfigProviderTargetComparator());
  273. }
  274. // Determine which settings will have changed
  275. for (String domain : identity.getDomains()) {
  276. identity.getOptions(domain).keySet().stream()
  277. .filter(option -> identity.equals(getScope(domain, option)))
  278. .forEach(option -> configChanged(domain, option));
  279. }
  280. }
  281. }
  282. @Override
  283. public Set<String> getDomains() {
  284. final Set<String> res = new HashSet<>();
  285. synchronized (sources) {
  286. for (ConfigProvider source : sources) {
  287. res.addAll(source.getDomains());
  288. }
  289. }
  290. return res;
  291. }
  292. @Override
  293. public List<ConfigProvider> getSources() {
  294. return new ArrayList<>(sources);
  295. }
  296. /**
  297. * Migrates this manager from its current configuration to the appropriate one for the specified
  298. * new parameters, firing listeners where settings have changed.
  299. *
  300. * <p>
  301. * This is package private - only callers with access to a {@link ConfigProviderMigrator}
  302. * should be able to migrate managers.
  303. *
  304. * @param protocol The protocol for this manager
  305. * @param ircd The new name of the ircd for this manager
  306. * @param network The new name of the network for this manager
  307. * @param server The new name of the server for this manager
  308. * @param channel The new name of the channel for this manager
  309. */
  310. void migrate(final String protocol, final String ircd,
  311. final String network, final String server, final String channel) {
  312. LOG.debug("Migrating from {{}, {}, {}, {}, {}} to {{}, {}, {}, {}, {}}", this.protocol,
  313. this.ircd, this.network, this.server, this.channel, protocol, ircd, network, server,
  314. channel);
  315. this.protocol = protocol;
  316. this.ircd = ircd;
  317. this.network = network;
  318. this.server = server;
  319. this.channel = channel + '@' + network;
  320. new ArrayList<>(sources).stream().filter(identity -> !identityApplies(identity))
  321. .forEach(identity -> {
  322. LOG.debug("Removing identity that no longer applies: {}", identity);
  323. removeIdentity(identity);
  324. });
  325. final List<ConfigFileBackedConfigProvider> newSources = manager.getIdentitiesForManager(this);
  326. for (ConfigFileBackedConfigProvider identity : newSources) {
  327. LOG.trace("Testing new identity: {}", identity);
  328. checkIdentity(identity);
  329. }
  330. LOG.debug("New identities: {}", sources);
  331. }
  332. /**
  333. * Records the lookup request for the specified domain and option.
  334. *
  335. * @param domain The domain that is being looked up
  336. * @param option The option that is being looked up
  337. */
  338. @SuppressWarnings("PMD.AvoidCatchingNPE")
  339. protected static void doStats(final String domain, final String option) {
  340. final String key = domain + '.' + option;
  341. try {
  342. STATS.put(key, 1 + (STATS.containsKey(key) ? STATS.get(key) : 0));
  343. } catch (NullPointerException ex) {
  344. // JVM bugs ftl.
  345. }
  346. }
  347. /**
  348. * Retrieves the statistic map.
  349. *
  350. * @return A map of config options to lookup counts
  351. */
  352. public static Map<String, Integer> getStats() {
  353. return STATS;
  354. }
  355. @Override
  356. public void addChangeListener(final String domain,
  357. final ConfigChangeListener listener) {
  358. addListener(domain, listener);
  359. }
  360. @Override
  361. public void addChangeListener(final String domain, final String key,
  362. final ConfigChangeListener listener) {
  363. addListener(domain + '.' + key, listener);
  364. }
  365. @Override
  366. public void removeListener(final ConfigChangeListener listener) {
  367. synchronized (listeners) {
  368. final Iterable<String> keys = new HashSet<>(listeners.keySet());
  369. keys.forEach(k -> listeners.remove(k, listener));
  370. }
  371. }
  372. /**
  373. * Adds the specified listener to the internal map/list.
  374. *
  375. * @param key The key to use (domain or domain.key)
  376. * @param listener The listener to register
  377. */
  378. private void addListener(final String key,
  379. final ConfigChangeListener listener) {
  380. synchronized (listeners) {
  381. listeners.put(key, listener);
  382. }
  383. }
  384. @Override
  385. public void configChanged(final String domain, final String key) {
  386. final Collection<ConfigChangeListener> targets = new ArrayList<>();
  387. if (listeners.containsKey(domain)) {
  388. targets.addAll(listeners.get(domain));
  389. }
  390. if (listeners.containsKey(domain + '.' + key)) {
  391. targets.addAll(listeners.get(domain + '.' + key));
  392. }
  393. for (ConfigChangeListener listener : targets) {
  394. listener.configChanged(domain, key);
  395. }
  396. }
  397. @Override
  398. public void configProviderAdded(final ConfigFileBackedConfigProvider configProvider) {
  399. checkIdentity(configProvider);
  400. }
  401. @Override
  402. public void configProviderRemoved(final ConfigFileBackedConfigProvider configProvider) {
  403. removeIdentity(configProvider);
  404. }
  405. }