From 15748e5c426321fb0b07feee32630e32d95d3c4f Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 28 Nov 2011 16:05:22 +0100 Subject: [PATCH 01/30] Execute scheduled tasks in system default dispatcher. See #1380 --- .../src/main/scala/akka/actor/ActorRefProvider.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 764838cdb8..144bb5dc9f 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -429,8 +429,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createSingleTask(runnable: Runnable): TimerTask = new TimerTask() { def run(timeout: org.jboss.netty.akka.util.Timeout) { - // FIXME: consider executing runnable inside main dispatcher to prevent blocking of scheduler - runnable.run() + system.dispatcher.dispatchTask(() ⇒ runnable.run()) } } @@ -444,7 +443,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createSingleTask(f: () ⇒ Unit): TimerTask = new TimerTask { def run(timeout: org.jboss.netty.akka.util.Timeout) { - f() + system.dispatcher.dispatchTask(f) } } @@ -465,7 +464,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createContinuousTask(f: () ⇒ Unit, delay: Duration): TimerTask = { new TimerTask { def run(timeout: org.jboss.netty.akka.util.Timeout) { - f() + system.dispatcher.dispatchTask(f) timeout.getTimer.newTimeout(this, delay) } } From 4d920916bbfdb9d78b15a8d7930bcdbab837afec Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 29 Nov 2011 08:07:41 +0100 Subject: [PATCH 02/30] Added dispatcher to constructor of DefaultDispatcher. See #1380 * There is circular initialization dependencies between DefaultScheduler and Dispatchers, therefore I used (lazy) by-name parameter. --- .../scala/akka/actor/ActorRefProvider.scala | 15 +++++++---- .../main/scala/akka/actor/ActorSystem.scala | 27 +++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 144bb5dc9f..f33dd3f0c5 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -409,7 +409,12 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { } } -class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) extends Scheduler { +/** + * Scheduled tasks (Runnable and functions) are executed with the supplied dispatcher. + * Note that dispatcher is by-name parameter, because dispatcher might not be initialized + * when the scheduler is created. + */ +class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler { def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable = new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay)) @@ -429,7 +434,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createSingleTask(runnable: Runnable): TimerTask = new TimerTask() { def run(timeout: org.jboss.netty.akka.util.Timeout) { - system.dispatcher.dispatchTask(() ⇒ runnable.run()) + dispatcher.dispatchTask(() ⇒ runnable.run()) } } @@ -443,7 +448,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createSingleTask(f: () ⇒ Unit): TimerTask = new TimerTask { def run(timeout: org.jboss.netty.akka.util.Timeout) { - system.dispatcher.dispatchTask(f) + dispatcher.dispatchTask(f) } } @@ -455,7 +460,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) receiver ! message timeout.getTimer.newTimeout(this, delay) } else { - system.eventStream.publish(Warning(this.getClass.getSimpleName, "Could not reschedule message to be sent because receiving actor has been terminated.")) + log.warning("Could not reschedule message to be sent because receiving actor has been terminated.") } } } @@ -464,7 +469,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, system: ActorSystem) private def createContinuousTask(f: () ⇒ Unit, delay: Duration): TimerTask = { new TimerTask { def run(timeout: org.jboss.netty.akka.util.Timeout) { - system.dispatcher.dispatchTask(f) + dispatcher.dispatchTask(f) timeout.getTimer.newTimeout(this, delay) } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index d66a66d3b1..83f88c1a61 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -302,7 +302,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A eventStream.startStdoutLogger(settings) val log = new BusLogging(eventStream, "ActorSystem") // “this” used only for .getClass in tagging messages - val scheduler = new DefaultScheduler(new HashedWheelTimer(log, Executors.defaultThreadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel), this) + val scheduler = createScheduler() val provider: ActorRefProvider = { val providerClass = ReflectiveAccess.getClassFor(ProviderClass) match { @@ -336,6 +336,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A } val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler)) + // TODO why implicit val dispatcher? implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher //FIXME Set this to a Failure when things bubble to the top @@ -376,10 +377,32 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A // TODO shutdown all that other stuff, whatever that may be def stop() { guardian.stop() - terminationFuture onComplete (_ ⇒ scheduler.stop()) + terminationFuture onComplete (_ ⇒ stopScheduler()) terminationFuture onComplete (_ ⇒ dispatcher.shutdown()) } + protected def createScheduler(): Scheduler = { + val threadFactory = new MonitorableThreadFactory("DefaultScheduler") + val hwt = new HashedWheelTimer(log, threadFactory, settings.SchedulerTickDuration, settings.SchedulerTicksPerWheel) + // note that dispatcher is by-name parameter in DefaultScheduler constructor, + // because dispatcher is not initialized when the scheduler is created + def safeDispatcher = { + if (dispatcher eq null) { + val exc = new IllegalStateException("Scheduler is using dispatcher before it has been initialized") + log.error(exc, exc.getMessage) + throw exc + } else { + dispatcher + } + } + new DefaultScheduler(hwt, log, safeDispatcher) + } + + protected def stopScheduler(): Unit = scheduler match { + case x: DefaultScheduler ⇒ x.stop() + case _ ⇒ + } + private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef] /** From b56201ab7f9a22cc0e9372b5b1a8fd99884e0417 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 29 Nov 2011 11:50:22 +0100 Subject: [PATCH 03/30] Updated to latest config lib and changed how reference config files are loaded. * Config lib 4f3a91f * All reference files named reference.conf, all will be loaded * Usage of ConfigFactor.load as default way * Extensions use same config as ActorSystem.settings.config --- .../test/java/akka/actor/JavaExtension.java | 13 +- .../scala/akka/actor/LoggingReceiveSpec.scala | 1 - .../akka/actor/dispatch/DispatchersSpec.scala | 3 +- .../test/scala/akka/config/ConfigSpec.scala | 3 +- .../scala/akka/event/EventStreamSpec.scala | 3 +- .../akka/performance/workbench/Report.scala | 2 +- .../akka/serialization/SerializeSpec.scala | 3 +- .../main/java/com/typesafe/config/Config.java | 332 ++++++++++--- .../com/typesafe/config/ConfigException.java | 107 ++++- .../com/typesafe/config/ConfigFactory.java | 443 +++++++++++++----- .../typesafe/config/ConfigIncludeContext.java | 7 +- .../com/typesafe/config/ConfigIncluder.java | 7 +- .../java/com/typesafe/config/ConfigList.java | 27 +- .../com/typesafe/config/ConfigMergeable.java | 43 +- .../com/typesafe/config/ConfigObject.java | 86 ++-- .../com/typesafe/config/ConfigOrigin.java | 61 ++- .../typesafe/config/ConfigParseOptions.java | 30 +- .../com/typesafe/config/ConfigParseable.java | 35 +- .../typesafe/config/ConfigResolveOptions.java | 66 ++- .../java/com/typesafe/config/ConfigRoot.java | 33 -- .../com/typesafe/config/ConfigSyntax.java | 26 +- .../java/com/typesafe/config/ConfigValue.java | 41 +- .../typesafe/config/ConfigValueFactory.java | 56 ++- .../com/typesafe/config/ConfigValueType.java | 3 +- .../config/impl/AbstractConfigObject.java | 87 ++-- .../config/impl/AbstractConfigValue.java | 35 +- .../config/impl/ConfigDelayedMerge.java | 73 ++- .../config/impl/ConfigDelayedMergeObject.java | 13 +- .../com/typesafe/config/impl/ConfigImpl.java | 143 +++--- .../com/typesafe/config/impl/ConfigNull.java | 5 + .../typesafe/config/impl/ConfigString.java | 5 + .../config/impl/ConfigSubstitution.java | 86 ++-- .../com/typesafe/config/impl/OriginType.java | 8 + .../com/typesafe/config/impl/Parseable.java | 258 +++++----- .../java/com/typesafe/config/impl/Parser.java | 14 +- .../java/com/typesafe/config/impl/Path.java | 2 +- .../com/typesafe/config/impl/RootConfig.java | 61 --- .../typesafe/config/impl/SimpleConfig.java | 347 +++++++++++--- .../config/impl/SimpleConfigList.java | 49 +- .../config/impl/SimpleConfigObject.java | 6 +- .../config/impl/SimpleConfigOrigin.java | 279 ++++++++++- .../config/impl/SubstitutionExpression.java | 46 ++ .../config/impl/SubstitutionResolver.java | 10 +- .../com/typesafe/config/impl/Tokenizer.java | 15 +- .../java/com/typesafe/config/impl/Tokens.java | 21 +- .../java/com/typesafe/config/package.html | 42 ++ .../akka-serialization-reference.conf | 32 -- ...ka-actor-reference.conf => reference.conf} | 25 +- .../main/scala/akka/actor/ActorSystem.scala | 42 +- .../src/main/scala/akka/actor/Deployer.scala | 2 +- .../akka/serialization/Serialization.scala | 15 +- akka-docs/general/code/ConfigDocSpec.scala | 3 +- akka-docs/general/configuration.rst | 43 +- ...-mailbox-reference.conf => reference.conf} | 0 .../BeanstalkBasedMailboxExtension.scala | 11 +- ...-mailbox-reference.conf => reference.conf} | 4 +- .../mailbox/FileBasedMailboxExtension.scala | 21 +- ...-mailbox-reference.conf => reference.conf} | 0 .../mailbox/MongoBasedMailboxExtension.scala | 11 +- ...-mailbox-reference.conf => reference.conf} | 0 .../mailbox/RedisBasedMailboxExtension.scala | 11 +- ...-mailbox-reference.conf => reference.conf} | 0 .../ZooKeeperBasedMailboxExtension.scala | 11 +- ...a-remote-reference.conf => reference.conf} | 4 +- .../scala/akka/remote/RemoteExtension.scala | 15 +- .../scala/akka/remote/AkkaRemoteSpec.scala | 21 +- .../scala/akka/remote/RemoteConfigSpec.scala | 2 +- ...akka-stm-reference.conf => reference.conf} | 0 .../test/scala/akka/stm/test/ConfigSpec.scala | 3 +- ...-testkit-reference.conf => reference.conf} | 0 .../scala/akka/testkit/TestKitExtension.scala | 11 +- .../test/scala/akka/testkit/AkkaSpec.scala | 15 +- 72 files changed, 2361 insertions(+), 977 deletions(-) delete mode 100644 akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java create mode 100644 akka-actor/src/main/java/com/typesafe/config/impl/OriginType.java delete mode 100644 akka-actor/src/main/java/com/typesafe/config/impl/RootConfig.java create mode 100644 akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionExpression.java create mode 100644 akka-actor/src/main/java/com/typesafe/config/package.html delete mode 100644 akka-actor/src/main/resources/akka-serialization-reference.conf rename akka-actor/src/main/resources/{akka-actor-reference.conf => reference.conf} (91%) rename akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/{akka-beanstalk-mailbox-reference.conf => reference.conf} (100%) rename akka-durable-mailboxes/akka-file-mailbox/src/main/resources/{akka-file-mailbox-reference.conf => reference.conf} (89%) rename akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/{akka-mongo-mailbox-reference.conf => reference.conf} (100%) rename akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/{akka-redis-mailbox-reference.conf => reference.conf} (100%) rename akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/{akka-zookeeper-mailbox-reference.conf => reference.conf} (100%) rename akka-remote/src/main/resources/{akka-remote-reference.conf => reference.conf} (96%) rename akka-stm/src/main/resources/{akka-stm-reference.conf => reference.conf} (100%) rename akka-testkit/src/main/resources/{akka-testkit-reference.conf => reference.conf} (100%) diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java index fefec7640a..a08060f52d 100644 --- a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java +++ b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java @@ -7,7 +7,6 @@ import org.junit.Test; import com.typesafe.config.ConfigFactory; import com.typesafe.config.Config; -import com.typesafe.config.ConfigParseOptions; import static org.junit.Assert.*; @@ -26,14 +25,14 @@ public class JavaExtension { } static class TestExtension implements Extension { - public final ActorSystemImpl system; - public TestExtension(ActorSystemImpl i) { - system = i; - } + public final ActorSystemImpl system; + + public TestExtension(ActorSystemImpl i) { + system = i; + } } - private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]", - ConfigParseOptions.defaults()); + private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]"); private ActorSystem system = ActorSystem.create("JavaExtension", c); diff --git a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala index 0e5602a899..fe5c7642bf 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala @@ -10,7 +10,6 @@ import org.scalatest.WordSpec import akka.event.Logging import akka.util.Duration import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions import scala.collection.JavaConverters._ import java.util.Properties diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala index 53fa4ea5bf..d23bc8ce57 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/DispatchersSpec.scala @@ -9,7 +9,6 @@ import akka.dispatch._ import akka.testkit.AkkaSpec import scala.collection.JavaConverters._ import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DispatchersSpec extends AkkaSpec { @@ -41,7 +40,7 @@ class DispatchersSpec extends AkkaSpec { throughput = 17 } } - """, ConfigParseOptions.defaults) + """) lazy val allDispatchers: Map[String, Option[MessageDispatcher]] = { validTypes.map(t ⇒ (t, from(ConfigFactory.parseMap(Map(tipe -> t).asJava).withFallback(defaultDispatcherConfig)))).toMap diff --git a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala index b594ea0f19..80d1ca30e5 100644 --- a/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/config/ConfigSpec.scala @@ -6,13 +6,12 @@ package akka.config import akka.testkit.AkkaSpec import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions import scala.collection.JavaConverters._ import akka.util.duration._ import akka.util.Duration @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-actor-reference.conf", ConfigParseOptions.defaults)) { +class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) { "The default configuration file (i.e. akka-actor-reference.conf)" must { "contain all configuration properties for akka-actor that are used in code with their correct defaults" in { diff --git a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala index 14158e7454..031fb1ccb3 100644 --- a/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/EventStreamSpec.scala @@ -7,7 +7,6 @@ import akka.testkit.AkkaSpec import akka.util.duration._ import akka.actor.{ Actor, ActorRef, ActorSystemImpl } import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions import scala.collection.JavaConverters._ import akka.actor.ActorSystem @@ -19,7 +18,7 @@ object EventStreamSpec { loglevel = INFO event-handlers = ["akka.event.EventStreamSpec$MyLog", "%s"] } - """.format(Logging.StandardOutLoggerName), ConfigParseOptions.defaults) + """.format(Logging.StandardOutLoggerName)) case class M(i: Int) diff --git a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala index 764afefe3c..36ba40c2e9 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala @@ -222,7 +222,7 @@ class Report( sb.append("Akka version: ").append(system.settings.ConfigVersion) sb.append("\n") sb.append("Akka config:") - for ((key, value) ← system.settings.config.toObject) { + for ((key, value) ← system.settings.config.toValue) { sb.append("\n ").append(key).append("=").append(value) } diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 0e2f43a3d8..73afd0dc0a 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -11,7 +11,6 @@ import akka.actor.{ ActorSystem, ActorSystemImpl } import java.io.{ ObjectInputStream, ByteArrayInputStream, ByteArrayOutputStream, ObjectOutputStream } import akka.actor.DeadLetterActorRef import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions object SerializeSpec { @@ -32,7 +31,7 @@ object SerializeSpec { } } } - """, ConfigParseOptions.defaults) + """) @BeanInfo case class Address(no: String, street: String, city: String, zip: String) { def this() = this("", "", "", "") } diff --git a/akka-actor/src/main/java/com/typesafe/config/Config.java b/akka-actor/src/main/java/com/typesafe/config/Config.java index 3a820033a8..0d83504fe3 100644 --- a/akka-actor/src/main/java/com/typesafe/config/Config.java +++ b/akka-actor/src/main/java/com/typesafe/config/Config.java @@ -6,51 +6,82 @@ package com.typesafe.config; import java.util.List; /** - * This class represents an immutable map from config paths to config values. It - * also contains some static methods for creating configs. + * An immutable map from config paths to config values. * + *

+ * Contrast with {@link ConfigObject} which is a map from config keys, + * rather than paths, to config values. A {@code Config} contains a tree of + * {@code ConfigObject}, and {@link Config#root()} returns the tree's root + * object. + * + *

* Throughout the API, there is a distinction between "keys" and "paths". A key * is a key in a JSON object; it's just a string that's the key in a map. A * "path" is a parseable expression with a syntax and it refers to a series of - * keys. Path expressions are described in the spec for "HOCON", which can be - * found at https://github.com/havocp/config/blob/master/HOCON.md; in brief, a - * path is period-separated so "a.b.c" looks for key c in object b in object a - * in the root object. Sometimes double quotes are needed around special - * characters in path expressions. + * keys. Path expressions are described in the spec for + * Human-Optimized Config Object Notation. In brief, a path is + * period-separated so "a.b.c" looks for key c in object b in object a in the + * root object. Sometimes double quotes are needed around special characters in + * path expressions. * - * The API for a Config is in terms of path expressions, while the API for a - * ConfigObject is in terms of keys. Conceptually, Config is a one-level map - * from paths to values, while a ConfigObject is a tree of maps from keys to - * values. + *

+ * The API for a {@code Config} is in terms of path expressions, while the API + * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} + * is a one-level map from paths to values, while a + * {@code ConfigObject} is a tree of nested maps from keys to values. * - * Another difference between Config and ConfigObject is that conceptually, - * ConfigValue with valueType() of ConfigValueType.NULL exist in a ConfigObject, - * while a Config treats null values as if they were missing. + *

+ * Another difference between {@code Config} and {@code ConfigObject} is that + * conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType() + * valueType()} of {@link ConfigValueType#NULL NULL} exist in a + * {@code ConfigObject}, while a {@code Config} treats null values as if they + * were missing. * - * Config is an immutable object and thus safe to use from multiple threads. + *

+ * {@code Config} is an immutable object and thus safe to use from multiple + * threads. There's never a need for "defensive copies." * - * The "getters" on a Config all work in the same way. They never return null, - * nor do they return a ConfigValue with valueType() of ConfigValueType.NULL. - * Instead, they throw ConfigException.Missing if the value is completely absent - * or set to null. If the value is set to null, a subtype of - * ConfigException.Missing called ConfigException.Null will be thrown. - * ConfigException.WrongType will be thrown anytime you ask for a type and the - * value has an incompatible type. Reasonable type conversions are performed for - * you though. + *

+ * The "getters" on a {@code Config} all work in the same way. They never return + * null, nor do they return a {@code ConfigValue} with + * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL + * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is + * completely absent or set to null. If the value is set to null, a subtype of + * {@code ConfigException.Missing} called {@link ConfigException.Null} will be + * thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for + * a type and the value has an incompatible type. Reasonable type conversions + * are performed for you though. * - * If you want to iterate over the contents of a Config, you have to get its - * ConfigObject with toObject, and then iterate over the ConfigObject. + *

+ * If you want to iterate over the contents of a {@code Config}, you have to get + * its {@code ConfigObject} with {@link #root()}, and then iterate over the + * {@code ConfigObject}. * + * + *

+ * Do not implement {@code Config}; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. */ public interface Config extends ConfigMergeable { /** - * Gets the config as a tree of ConfigObject. This is a constant-time - * operation (it is not proportional to the number of values in the Config). + * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a + * constant-time operation (it is not proportional to the number of values + * in the {@code Config}). * - * @return + * @return the root object in the configuration */ - ConfigObject toObject(); + ConfigObject root(); + /** + * Gets the origin of the {@code Config}, which may be a file, or a file + * with a line number, or just a descriptive phrase. + * + * @return the origin of the {@code Config} for use in error messages + */ ConfigOrigin origin(); @Override @@ -60,15 +91,157 @@ public interface Config extends ConfigMergeable { ConfigObject toValue(); /** - * Checks whether a value is present and non-null at the given path. This - * differs in two ways from ConfigObject.containsKey(): it looks for a path - * expression, not a key; and it returns false for null values, while - * containsKey() returns true indicating that the object contains a null - * value for the key. + * Returns a replacement config with all substitutions (the + * ${foo.bar} syntax, see the + * spec) resolved. Substitutions are looked up using this + * Config as the root object, that is, a substitution + * ${foo.bar} will be replaced with the result of + * getValue("foo.bar"). * - * If a path exists according to hasPath(), then getValue() will never throw - * an exception. However, the typed getters, such as getInt(), will still - * throw if the value is not convertible to the requested type. + *

+ * This method uses {@link ConfigResolveOptions#defaults()}, there is + * another variant {@link Config#resolve(ConfigResolveOptions)} which lets + * you specify non-default options. + * + *

+ * A given {@link Config} must be resolved before using it to retrieve + * config values, but ideally should be resolved one time for your entire + * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some + * substitutions that could have resolved with all fallbacks available may + * not resolve, which will be a user-visible oddity. + * + *

+ * resolve() should be invoked on root config objects, rather + * than on a subtree (a subtree is the result of something like + * config.getConfig("foo")). The problem with + * resolve() on a subtree is that substitutions are relative to + * the root of the config and the subtree will have no way to get values + * from the root. For example, if you did + * config.getConfig("foo").resolve() on the below config file, + * it would not work: + * + *

+     *   common-value = 10
+     *   foo {
+     *      whatever = ${common-value}
+     *   }
+     * 
+ * + * @return an immutable object with substitutions resolved + * @throws ConfigException.UnresolvedSubstitution + * if any substitutions refer to nonexistent paths + * @throws ConfigException + * some other config exception if there are other problems + */ + Config resolve(); + + /** + * Like {@link Config#resolve()} but allows you to specify non-default + * options. + * + * @param options + * resolve options + * @return the resolved Config + */ + Config resolve(ConfigResolveOptions options); + + /** + * Validates this config against a reference config, throwing an exception + * if it is invalid. The purpose of this method is to "fail early" with a + * comprehensive list of problems; in general, anything this method can find + * would be detected later when trying to use the config, but it's often + * more user-friendly to fail right away when loading the config. + * + *

+ * Using this method is always optional, since you can "fail late" instead. + * + *

+ * You must restrict validation to paths you "own" (those whose meaning are + * defined by your code module). If you validate globally, you may trigger + * errors about paths that happen to be in the config but have nothing to do + * with your module. It's best to allow the modules owning those paths to + * validate them. Also, if every module validates only its own stuff, there + * isn't as much redundant work being done. + * + *

+ * If no paths are specified in checkValid()'s parameter list, + * validation is for the entire config. + * + *

+ * If you specify paths that are not in the reference config, those paths + * are ignored. (There's nothing to validate.) + * + *

+ * Here's what validation involves: + * + *

    + *
  • All paths found in the reference config must be present in this + * config or an exception will be thrown. + *
  • + * Some changes in type from the reference config to this config will cause + * an exception to be thrown. Not all potential type problems are detected, + * in particular it's assumed that strings are compatible with everything + * except objects and lists. This is because string types are often "really" + * some other type (system properties always start out as strings, or a + * string like "5ms" could be used with {@link #getMilliseconds}). Also, + * it's allowed to set any type to null or override null with any type. + *
  • + * Any unresolved substitutions in this config will cause a validation + * failure; both the reference config and this config should be resolved + * before validation. If the reference config is unresolved, it's a bug in + * the caller of this method. + *
+ * + *

+ * If you want to allow a certain setting to have a flexible type (or + * otherwise want validation to be looser for some settings), you could + * either remove the problematic setting from the reference config provided + * to this method, or you could intercept the validation exception and + * screen out certain problems. Of course, this will only work if all other + * callers of this method are careful to restrict validation to their own + * paths, as they should be. + * + *

+ * If validation fails, the thrown exception contains a list of all problems + * found. See {@link ConfigException.ValidationFailed#problems}. The + * exception's getMessage() will have all the problems + * concatenated into one huge string, as well. + * + *

+ * Again, checkValid() can't guess every domain-specific way a + * setting can be invalid, so some problems may arise later when attempting + * to use the config. checkValid() is limited to reporting + * generic, but common, problems such as missing settings and blatant type + * incompatibilities. + * + * @param reference + * a reference configuration + * @param restrictToPaths + * only validate values underneath these paths that your code + * module owns and understands + * @throws ConfigException.ValidationFailed + * if there are any validation issues + * @throws ConfigException.NotResolved + * if this config is not resolved + * @throws ConfigException.BugOrBroken + * if the reference config is unresolved or caller otherwise + * misuses the API + */ + void checkValid(Config reference, String... restrictToPaths); + + /** + * Checks whether a value is present and non-null at the given path. This + * differs in two ways from {@code Map.containsKey()} as implemented by + * {@link ConfigObject}: it looks for a path expression, not a key; and it + * returns false for null values, while {@code containsKey()} returns true + * indicating that the object contains a null value for the key. + * + *

+ * If a path exists according to {@link #hasPath(String)}, then + * {@link #getValue(String)} will never throw an exception. However, the + * typed getters, such as {@link #getInt(String)}, will still throw if the + * value is not convertible to the requested type. * * @param path * the path expression @@ -78,12 +251,19 @@ public interface Config extends ConfigMergeable { */ boolean hasPath(String path); + /** + * Returns true if the {@code Config}'s root object contains no key-value + * pairs. + * + * @return true if the configuration is empty + */ boolean isEmpty(); /** * * @param path - * @return + * path expression + * @return the boolean value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -93,7 +273,8 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the numeric value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -103,17 +284,20 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the 32-bit integer value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType - * if value is not convertible to an int + * if value is not convertible to an int (for example it is out + * of range, or it's a boolean value) */ int getInt(String path); /** * @param path - * @return + * path expression + * @return the 64-bit long value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -123,7 +307,8 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the floating-point value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -133,7 +318,8 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the string value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -143,7 +329,8 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the {@link ConfigObject} value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -153,7 +340,8 @@ public interface Config extends ConfigMergeable { /** * @param path - * @return + * path expression + * @return the nested {@code Config} value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -162,9 +350,13 @@ public interface Config extends ConfigMergeable { Config getConfig(String path); /** - * Gets the value at the path as an unwrapped Java boxed value (Boolean, - * Integer, Long, etc.) + * Gets the value at the path as an unwrapped Java boxed value ( + * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and + * so on - see {@link ConfigValue#unwrapped()}). * + * @param path + * path expression + * @return the unwrapped value at the requested path * @throws ConfigException.Missing * if value is absent or null */ @@ -172,35 +364,47 @@ public interface Config extends ConfigMergeable { /** * Gets the value at the given path, unless the value is a null value or - * missing, in which case it throws just like the other getters. Use get() - * from the Map interface if you want an unprocessed value. + * missing, in which case it throws just like the other getters. Use + * {@code get()} from the {@link java.util.Map Map} interface if you want an + * unprocessed value. * * @param path - * @return + * path expression + * @return the value at the requested path * @throws ConfigException.Missing * if value is absent or null */ ConfigValue getValue(String path); /** - * Get value as a size in bytes (parses special strings like "128M"). The - * size units are interpreted as for memory, not as for disk space, so they - * are in powers of two. + * Gets a value as a size in bytes (parses special strings like "128M"). If + * the value is already a number, then it's left alone; if it's a string, + * it's parsed understanding unit suffixes such as "128K", as documented in + * the the + * spec. * + * @param path + * path expression + * @return the value at the requested path, in bytes * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue - * if value cannot be parsed as a memory size + * if value cannot be parsed as a size in bytes */ - Long getMemorySizeInBytes(String path); + Long getBytes(String path); /** * Get value as a duration in milliseconds. If the value is already a * number, then it's left alone; if it's a string, it's parsed understanding - * units suffixes like "10m" or "5ns" + * units suffixes like "10m" or "5ns" as documented in the the + * spec. * + * @param path + * path expression + * @return the duration value at the requested path, in milliseconds * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -213,8 +417,12 @@ public interface Config extends ConfigMergeable { /** * Get value as a duration in nanoseconds. If the value is already a number * it's taken as milliseconds and converted to nanoseconds. If it's a - * string, it's parsed understanding unit suffixes. + * string, it's parsed understanding unit suffixes, as for + * {@link #getMilliseconds(String)}. * + * @param path + * path expression + * @return the duration value at the requested path, in nanoseconds * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType @@ -225,13 +433,13 @@ public interface Config extends ConfigMergeable { Long getNanoseconds(String path); /** - * Gets a list value (with any element type) as a ConfigList, which - * implements java.util.List. Throws if the path is unset or - * null. + * Gets a list value (with any element type) as a {@link ConfigList}, which + * implements {@code java.util.List}. Throws if the path is + * unset or null. * * @param path * the path to the list value. - * @return the ConfigList at the path + * @return the {@link ConfigList} at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java index 7763231108..3973116064 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java @@ -3,15 +3,19 @@ */ package com.typesafe.config; + /** * All exceptions thrown by the library are subclasses of ConfigException. */ public class ConfigException extends RuntimeException { private static final long serialVersionUID = 1L; + final private ConfigOrigin origin; + protected ConfigException(ConfigOrigin origin, String message, Throwable cause) { super(origin.description() + ": " + message, cause); + this.origin = origin; } protected ConfigException(ConfigOrigin origin, String message) { @@ -20,12 +24,26 @@ public class ConfigException extends RuntimeException { protected ConfigException(String message, Throwable cause) { super(message, cause); + this.origin = null; } protected ConfigException(String message) { this(message, null); } + /** + * Returns an "origin" (such as a filename and line number) for the + * exception, or null if none is available. If there's no sensible origin + * for a given exception, or the kind of exception doesn't meaningfully + * relate to a particular origin file, this returns null. Never assume this + * will return non-null, it can always return null. + * + * @return origin of the problem, or null if unknown/inapplicable + */ + public ConfigOrigin origin() { + return origin; + } + /** * Exception indicating that the type of a value does not match the type you * requested. @@ -163,10 +181,11 @@ public class ConfigException extends RuntimeException { } /** - * Exception indicating that there's a bug in something or the runtime - * environment is broken. This exception should never be handled; instead, - * something should be fixed to keep the exception from occurring. - * + * Exception indicating that there's a bug in something (possibly the + * library itself) or the runtime environment is broken. This exception + * should never be handled; instead, something should be fixed to keep the + * exception from occurring. This exception can be thrown by any method in + * the library. */ public static class BugOrBroken extends ConfigException { private static final long serialVersionUID = 1L; @@ -212,12 +231,29 @@ public class ConfigException extends RuntimeException { } } + /** + * Exception indicating that a substitution did not resolve to anything. + * Thrown by {@link Config#resolve}. + */ + public static class UnresolvedSubstitution extends Parse { + private static final long serialVersionUID = 1L; + + public UnresolvedSubstitution(ConfigOrigin origin, String expression, Throwable cause) { + super(origin, "Could not resolve substitution to a value: " + expression, cause); + } + + public UnresolvedSubstitution(ConfigOrigin origin, String expression) { + this(origin, expression, null); + } + } + /** * Exception indicating that you tried to use a function that requires - * substitutions to be resolved, but substitutions have not been resolved. - * This is always a bug in either application code or the library; it's - * wrong to write a handler for this exception because you should be able to - * fix the code to avoid it. + * substitutions to be resolved, but substitutions have not been resolved + * (that is, {@link Config#resolve} was not called). This is always a bug in + * either application code or the library; it's wrong to write a handler for + * this exception because you should be able to fix the code to avoid it by + * adding calls to {@link Config#resolve}. */ public static class NotResolved extends BugOrBroken { private static final long serialVersionUID = 1L; @@ -230,4 +266,59 @@ public class ConfigException extends RuntimeException { this(message, null); } } + + public static class ValidationProblem { + + final private String path; + final private ConfigOrigin origin; + final private String problem; + + public ValidationProblem(String path, ConfigOrigin origin, String problem) { + this.path = path; + this.origin = origin; + this.problem = problem; + } + + public String path() { + return path; + } + + public ConfigOrigin origin() { + return origin; + } + + public String problem() { + return problem; + } + } + + public static class ValidationFailed extends ConfigException { + private static final long serialVersionUID = 1L; + + final private Iterable problems; + + public ValidationFailed(Iterable problems) { + super(makeMessage(problems), null); + this.problems = problems; + } + + public Iterable problems() { + return problems; + } + + private static String makeMessage(Iterable problems) { + StringBuilder sb = new StringBuilder(); + for (ValidationProblem p : problems) { + sb.append(p.origin().description()); + sb.append(": "); + sb.append(p.path()); + sb.append(": "); + sb.append(p.problem()); + sb.append(", "); + } + sb.setLength(sb.length() - 2); // chop comma and space + + return sb.toString(); + } + } } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java index b55c9abdf8..36ad5c54b4 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java @@ -13,203 +13,430 @@ import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.Parseable; /** - * This class contains static methods for creating Config objects. + * Contains static methods for creating {@link Config} instances. * + *

+ * See also {@link ConfigValueFactory} which contains static methods for + * converting Java values into a {@link ConfigObject}. You can then convert a + * {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}. + * + *

* The static methods with "load" in the name do some sort of higher-level * operation potentially parsing multiple resources and resolving substitutions, - * while the ones with "parse" in the name just create a ConfigValue from a - * resource and nothing else. + * while the ones with "parse" in the name just create a {@link ConfigValue} + * from a resource and nothing else. */ public final class ConfigFactory { + private ConfigFactory() { + } + /** - * Loads a configuration for the given root path in a "standard" way. - * Oversimplified, if your root path is foo.bar then this will load files - * from the classpath: foo-bar.conf, foo-bar.json, foo-bar.properties, - * foo-bar-reference.conf, foo-bar-reference.json, - * foo-bar-reference.properties. It will override all those files with any - * system properties that begin with "foo.bar.", as well. - * - * The root path should be a path expression, usually just a single short - * word, that scopes the package being configured; typically it's the - * package name or something similar. System properties overriding values in - * the configuration will have to be prefixed with the root path. The root - * path may have periods in it if you like but other punctuation or - * whitespace will probably cause you headaches. Example root paths: "akka", - * "sbt", "jsoup", "heroku", "mongo", etc. + * Loads an application's configuration from the given classpath resource or + * classpath resource basename, sandwiches it between default reference + * config and default overrides, and then resolves it. The classpath + * resource is "raw" (it should have no "/" prefix, and is not made relative + * to any package, so it's like {@link ClassLoader#getResource} not + * {@link Class#getResource}). * + *

* The loaded object will already be resolved (substitutions have already * been processed). As a result, if you add more fallbacks then they won't * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If * you want to parse additional files or something then you need to use - * loadWithoutResolving(). + * {@link #load(Config)}. * - * @param rootPath - * the configuration "domain" - * @return configuration object for the requested root path + * @param resourceBasename + * name (optionally without extension) of a resource on classpath + * @return configuration for an application */ - public static ConfigRoot load(String rootPath) { - return loadWithoutResolving(rootPath).resolve(); - } - - public static ConfigRoot load(String rootPath, - ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { - return loadWithoutResolving(rootPath, parseOptions).resolve( - resolveOptions); + public static Config load(String resourceBasename) { + return load(resourceBasename, ConfigParseOptions.defaults(), + ConfigResolveOptions.defaults()); } /** - * Like load() but does not resolve the object, so you can go ahead and add - * more fallbacks and stuff and have them seen by substitutions when you do - * call {@link ConfigRoot.resolve()}. + * Like {@link #load(String)} but allows you to specify parse and resolve + * options. * - * @param rootPath - * @return + *

+ * To be aware of: using + * {@link ConfigResolveOptions#setUseSystemProperties(boolean) + * setUseSystemProperties(false)} with this method has no meaningful effect, + * because the system properties are merged into the config as overrides + * anyway. setUseSystemProperties affects whether to fall back + * to system properties when they are not found in the config, but with + * load(), they will be in the config. + * + * @param resourceBasename + * the classpath resource name with optional extension + * @param parseOptions + * options to use when parsing the resource + * @param resolveOptions + * options to use when resolving the stack + * @return configuration for an application */ - public static ConfigRoot loadWithoutResolving(String rootPath) { - return loadWithoutResolving(rootPath, ConfigParseOptions.defaults()); + public static Config load(String resourceBasename, ConfigParseOptions parseOptions, + ConfigResolveOptions resolveOptions) { + Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/" + + resourceBasename, parseOptions); + return load(appConfig, resolveOptions); } - public static ConfigRoot loadWithoutResolving(String rootPath, - ConfigParseOptions options) { - ConfigRoot system = systemPropertiesRoot(rootPath); - - Config mainFiles = parseResourcesForPath(rootPath, options); - Config referenceFiles = parseResourcesForPath(rootPath + ".reference", - options); - - return system.withFallback(mainFiles).withFallback(referenceFiles); + /** + * Assembles a standard configuration using a custom Config + * object rather than loading "application.conf". The Config + * object will be sandwiched between the default reference config and + * default overrides and then resolved. + * + * @param config + * the application's portion of the configuration + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(Config config) { + return load(config, ConfigResolveOptions.defaults()); } - public static ConfigRoot emptyRoot(String rootPath) { - return emptyRoot(rootPath, null); + /** + * Like {@link #load(Config)} but allows you to specify + * {@link ConfigResolveOptions}. + * + *

+ * To be aware of: using + * {@link ConfigResolveOptions#setUseSystemProperties(boolean) + * setUseSystemProperties(false)} with this method has no meaningful effect, + * because the system properties are merged into the config as overrides + * anyway. setUseSystemProperties affects whether to fall back + * to system properties when they are not found in the config, but with + * load(), they will be in the config. + * + * @param config + * the application's portion of the configuration + * @param resolveOptions + * options for resolving the assembled config stack + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(Config config, ConfigResolveOptions resolveOptions) { + return defaultOverrides().withFallback(config).withFallback(defaultReference()) + .resolve(resolveOptions); } + private static class DefaultConfigHolder { + static final Config defaultConfig = load("application"); + } + + /** + * Loads a default configuration, equivalent to {@link #load(String) + * load("application")}. This configuration should be used by libraries and + * frameworks unless an application provides a different one. + *

+ * This method may return a cached singleton. + * + * @return configuration for an application + */ + public static Config load() { + return DefaultConfigHolder.defaultConfig; + } + + /** + * Obtains the default reference configuration, which is currently created + * by merging all resources "reference.conf" found on the classpath and + * overriding the result with system properties. The returned reference + * configuration will already have substitutions resolved. + * + *

+ * Libraries and frameworks should ship with a "reference.conf" in their + * jar. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may look for reference configuration in more places. It + * is not guaranteed that this method only looks at + * "reference.conf". + * + * @return the default reference config + */ + public static Config defaultReference() { + return ConfigImpl.defaultReference(); + } + + /** + * Obtains the default override configuration, which currently consists of + * system properties. The returned override configuration will already have + * substitutions resolved. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may get overrides in more places. It is not guaranteed + * that this method only uses system properties. + * + * @return the default override configuration + */ + public static Config defaultOverrides() { + return systemProperties(); + } + + /** + * Gets an empty configuration. See also {@link #empty(String)} to create an + * empty configuration with a description, which may improve user-visible + * error messages. + * + * @return an empty configuration + */ public static Config empty() { return empty(null); } - public static ConfigRoot emptyRoot(String rootPath, String originDescription) { - return ConfigImpl.emptyRoot(rootPath, originDescription); - } - + /** + * Gets an empty configuration with a description to be used to create a + * {@link ConfigOrigin} for this Config. The description should + * be very short and say what the configuration is, like "default settings" + * or "foo settings" or something. (Presumably you will merge some actual + * settings into this empty config using {@link Config#withFallback}, making + * the description more useful.) + * + * @param originDescription + * description of the config + * @return an empty configuration + */ public static Config empty(String originDescription) { return ConfigImpl.emptyConfig(originDescription); } - public static ConfigRoot systemPropertiesRoot(String rootPath) { - return ConfigImpl.systemPropertiesRoot(rootPath); - } - + /** + * Gets a Config containing the system properties from + * {@link java.lang.System#getProperties()}, parsed and converted as with + * {@link #parseProperties}. This method can return a global immutable + * singleton, so it's preferred over parsing system properties yourself. + * + *

+ * {@link #load} will include the system properties as overrides already, as + * will {@link #defaultReference} and {@link #defaultOverrides}. + * + *

+ * Because this returns a singleton, it will not notice changes to system + * properties made after the first time this method is called. + * + * @return system properties parsed into a Config + */ public static Config systemProperties() { return ConfigImpl.systemPropertiesAsConfig(); } + /** + * Gets a Config containing the system's environment variables. + * This method can return a global immutable singleton. + * + *

+ * Environment variables are used as fallbacks when resolving substitutions + * whether or not this object is included in the config being resolved, so + * you probably don't need to use this method for most purposes. It can be a + * nicer API for accessing environment variables than raw + * {@link java.lang.System#getenv(String)} though, since you can use methods + * such as {@link Config#getInt}. + * + * @return system environment variables parsed into a Config + */ public static Config systemEnvironment() { return ConfigImpl.envVariablesAsConfig(); } /** - * Converts a Java Properties object to a ConfigObject using the rules - * documented in https://github.com/havocp/config/blob/master/HOCON.md The - * keys in the Properties object are split on the period character '.' and - * treated as paths. The values will all end up as string values. If you - * have both "a=foo" and "a.b=bar" in your properties file, so "a" is both - * the object containing "b" and the string "foo", then the string value is - * dropped. + * Converts a Java {@link java.util.Properties} object to a + * {@link ConfigObject} using the rules documented in the HOCON + * spec. The keys in the Properties object are split on the + * period character '.' and treated as paths. The values will all end up as + * string values. If you have both "a=foo" and "a.b=bar" in your properties + * file, so "a" is both the object containing "b" and the string "foo", then + * the string value is dropped. * - * If you want to get System.getProperties() as a ConfigObject, it's better - * to use the systemProperties() or systemPropertiesRoot() methods. Those - * methods are able to use a cached global singleton ConfigObject for the - * system properties. + *

+ * If you want to have System.getProperties() as a + * ConfigObject, it's better to use the {@link #systemProperties()} method + * which returns a cached global singleton. * * @param properties * a Java Properties object * @param options - * @return + * @return the parsed configuration */ public static Config parseProperties(Properties properties, ConfigParseOptions options) { return Parseable.newProperties(properties, options).parse().toConfig(); } + public static Config parseProperties(Properties properties) { + return parseProperties(properties, ConfigParseOptions.defaults()); + } + public static Config parseReader(Reader reader, ConfigParseOptions options) { return Parseable.newReader(reader, options).parse().toConfig(); } + public static Config parseReader(Reader reader) { + return parseReader(reader, ConfigParseOptions.defaults()); + } + public static Config parseURL(URL url, ConfigParseOptions options) { return Parseable.newURL(url, options).parse().toConfig(); } + public static Config parseURL(URL url) { + return parseURL(url, ConfigParseOptions.defaults()); + } + public static Config parseFile(File file, ConfigParseOptions options) { return Parseable.newFile(file, options).parse().toConfig(); } + public static Config parseFile(File file) { + return parseFile(file, ConfigParseOptions.defaults()); + } + /** - * Parses a file. If the fileBasename already ends in a known extension, - * just parses it according to that extension. If the fileBasename does not - * end in an extension, then parse all known extensions and merge whatever - * is found. If options force a specific syntax, only parse files with an - * extension matching that syntax. If options.getAllowMissing() is true, - * then no files have to exist; if false, then at least one file has to - * exist. + * Parses a file with a flexible extension. If the fileBasename + * already ends in a known extension, this method parses it according to + * that extension (the file's syntax must match its extension). If the + * fileBasename does not end in an extension, it parses files + * with all known extensions and merges whatever is found. + * + *

+ * In the current implementation, the extension ".conf" forces + * {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and + * ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files, + * ".conf" falls back to ".json" falls back to ".properties". + * + *

+ * Future versions of the implementation may add additional syntaxes or + * additional extensions. However, the ordering (fallback priority) of the + * three current extensions will remain the same. + * + *

+ * If options forces a specific syntax, this method only parses + * files with an extension matching that syntax. + * + *

+ * If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()} + * is true, then no files have to exist; if false, then at least one file + * has to exist. * * @param fileBasename + * a filename with or without extension * @param options - * @return + * parse options + * @return the parsed configuration */ public static Config parseFileAnySyntax(File fileBasename, ConfigParseOptions options) { return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); } - public static Config parseResource(Class klass, String resource, - ConfigParseOptions options) { - return Parseable.newResource(klass, resource, options).parse() - .toConfig(); + public static Config parseFileAnySyntax(File fileBasename) { + return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults()); } /** - * Same behavior as parseFileAnySyntax() but for classpath resources - * instead. + * Parses all resources on the classpath with the given name and merges them + * into a single Config. + * + *

+ * If the resource name does not begin with a "/", it will have the supplied + * class's package added to it, in the same way as + * {@link java.lang.Class#getResource}. + * + *

+ * Duplicate resources with the same name are merged such that ones returned + * earlier from {@link ClassLoader#getResources} fall back to (have higher + * priority than) the ones returned later. This implies that resources + * earlier in the classpath override those later in the classpath when they + * configure the same setting. However, in practice real applications may + * not be consistent about classpath ordering, so be careful. It may be best + * to avoid assuming too much. * * @param klass - * @param resourceBasename + * klass.getClassLoader() will be used to load + * resources, and non-absolute resource names will have this + * class's package added + * @param resource + * resource to look up, relative to klass's package + * or absolute starting with a "/" * @param options - * @return + * parse options + * @return the parsed configuration */ - public static Config parseResourceAnySyntax(Class klass, String resourceBasename, + public static Config parseResources(Class klass, String resource, ConfigParseOptions options) { - return ConfigImpl.parseResourceAnySyntax(klass, resourceBasename, + return Parseable.newResources(klass, resource, options).parse() + .toConfig(); + } + + public static Config parseResources(Class klass, String resource) { + return parseResources(klass, resource, ConfigParseOptions.defaults()); + } + + /** + * Parses classpath resources with a flexible extension. In general, this + * method has the same behavior as + * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath + * resources instead, as in {@link #parseResources}. + * + *

+ * There is a thorny problem with this method, which is that + * {@link java.lang.ClassLoader#getResources} must be called separately for + * each possible extension. The implementation ends up with separate lists + * of resources called "basename.conf" and "basename.json" for example. As a + * result, the ideal ordering between two files with different extensions is + * unknown; there is no way to figure out how to merge the two lists in + * classpath order. To keep it simple, the lists are simply concatenated, + * with the same syntax priorities as + * {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()} + * - all ".conf" resources are ahead of all ".json" resources which are + * ahead of all ".properties" resources. + * + * @param klass + * class which determines the ClassLoader and the + * package for relative resource names + * @param resourceBasename + * a resource name as in {@link java.lang.Class#getResource}, + * with or without extension + * @param options + * parse options + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(Class klass, String resourceBasename, + ConfigParseOptions options) { + return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename, options).toConfig(); } + public static Config parseResourcesAnySyntax(Class klass, String resourceBasename) { + return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults()); + } + public static Config parseString(String s, ConfigParseOptions options) { return Parseable.newString(s, options).parse().toConfig(); } - /** - * Parses classpath resources corresponding to this path expression. - * Essentially if the path is "foo.bar" then the resources are - * "/foo-bar.conf", "/foo-bar.json", and "/foo-bar.properties". If more than - * one of those exists, they are merged. - * - * @param path - * @param options - * @return - */ - public static Config parseResourcesForPath(String rootPath, - ConfigParseOptions options) { - // null originDescription is allowed in parseResourcesForPath - return ConfigImpl.parseResourcesForPath(rootPath, options).toConfig(); + public static Config parseString(String s) { + return parseString(s, ConfigParseOptions.defaults()); } /** - * Similar to ConfigValueFactory.fromMap(), but the keys in the map are path - * expressions, rather than keys; and correspondingly it returns a Config - * instead of a ConfigObject. This is more convenient if you are writing - * literal maps in code, and less convenient if you are getting your maps - * from some data source such as a parser. + * Creates a {@code Config} based on a {@link java.util.Map} from paths to + * plain Java values. Similar to + * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the + * map are path expressions, rather than keys; and correspondingly it + * returns a {@code Config} instead of a {@code ConfigObject}. This is more + * convenient if you are writing literal maps in code, and less convenient + * if you are getting your maps from some data source such as a parser. * + *

* An exception will be thrown (and it is a bug in the caller of the method) * if a path is both an object and a value, for example if you had both * "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent @@ -221,7 +448,7 @@ public final class ConfigFactory { * description of what this map represents, like a filename, or * "default settings" (origin description is used in error * messages) - * @return + * @return the map converted to a {@code Config} */ public static Config parseMap(Map values, String originDescription) { @@ -229,11 +456,11 @@ public final class ConfigFactory { } /** - * See the other overload of parseMap() for details, this one just uses a - * default origin description. + * See the other overload of {@link #parseMap(Map, String)} for details, + * this one just uses a default origin description. * * @param values - * @return + * @return the map converted to a {@code Config} */ public static Config parseMap(Map values) { return parseMap(values, null); diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java b/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java index a770250171..2be0abff34 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigIncludeContext.java @@ -5,8 +5,9 @@ package com.typesafe.config; /** - * A ConfigIncludeContext is passed to a ConfigIncluder. This interface is not - * intended for apps to implement. + * Context provided to a {@link ConfigIncluder}; this interface is only useful + * inside a {@code ConfigIncluder} implementation, and is not intended for apps + * to implement. */ public interface ConfigIncludeContext { /** @@ -15,7 +16,7 @@ public interface ConfigIncludeContext { * null if it can't meaningfully create a relative name. The returned * parseable may not exist; this function is not required to do any IO, just * compute what the name would be. - * + * * The passed-in filename has to be a complete name (with extension), not * just a basename. (Include statements in config files are allowed to give * just a basename.) diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java b/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java index 364b8c5e30..1ac6f3383d 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigIncluder.java @@ -4,8 +4,9 @@ package com.typesafe.config; /** - * Interface you have to implement to customize "include" statements in config - * files. + * Implement this interface and provide an instance to + * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to + * customize handling of {@code include} statements in config files. */ public interface ConfigIncluder { /** @@ -29,7 +30,7 @@ public interface ConfigIncluder { * Parses another item to be included. The returned object typically would * not have substitutions resolved. You can throw a ConfigException here to * abort parsing, or return an empty object, but may not return null. - * + * * @param context * some info about the include context * @param what diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigList.java b/akka-actor/src/main/java/com/typesafe/config/ConfigList.java index 0688c29abe..2024efe744 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigList.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigList.java @@ -6,9 +6,30 @@ package com.typesafe.config; import java.util.List; /** - * A list (aka array) value corresponding to ConfigValueType.LIST or JSON's - * "[1,2,3]" value. Implements java.util.List so you can use it - * like a regular Java list. + * Subtype of {@link ConfigValue} representing a list value, as in JSON's + * {@code [1,2,3]} syntax. + * + *

+ * {@code ConfigList} implements {@code java.util.List} so you can + * use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the + * list elements into plain Java values. + * + *

+ * Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This + * makes it threadsafe and you never have to create "defensive copies." The + * mutator methods from {@link java.util.List} all throw + * {@link java.lang.UnsupportedOperationException}. + * + *

+ * The {@link ConfigValue#valueType} method on a list returns + * {@link ConfigValueType#LIST}. + * + *

+ * Do not implement {@code ConfigList}; it should only be implemented + * by the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. * */ public interface ConfigList extends List, ConfigValue { diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java index deec42bec1..356f89021b 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java @@ -4,33 +4,48 @@ package com.typesafe.config; /** - * This is a marker for types that can be merged as a fallback into a Config or - * a ConfigValue. Both Config and ConfigValue are mergeable. + * Marker for types whose instances can be merged, that is {@link Config} and + * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can + * be combined into a single new instance using the + * {@link ConfigMergeable#withFallback withFallback()} method. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. */ public interface ConfigMergeable { /** - * Converts the mergeable to a ConfigValue to be merged. + * Converts this instance to a {@link ConfigValue}. If called on a + * {@code ConfigValue} it returns {@code this}, if called on a + * {@link Config} it's equivalent to {@link Config#root()}. * - * @return + * @return this instance as a {@code ConfigValue} */ ConfigValue toValue(); /** * Returns a new value computed by merging this value with another, with - * keys in this value "winning" over the other one. Only ConfigObject and - * Config instances do anything in this method (they need to merge the - * fallback keys into themselves). All other values just return the original - * value, since they automatically override any fallback. - * - * The semantics of merging are described in - * https://github.com/havocp/config/blob/master/HOCON.md - * - * Note that objects do not merge "across" non-objects; if you do + * keys in this value "winning" over the other one. Only + * {@link ConfigObject} and {@link Config} instances do anything in this + * method (they need to merge the fallback keys into themselves). All other + * values just return the original value, since they automatically override + * any fallback. + * + *

+ * The semantics of merging are described in the spec for + * HOCON. + * + *

+ * Note that objects do not merge "across" non-objects; if you write * object.withFallback(nonObject).withFallback(otherObject), * then otherObject will simply be ignored. This is an * intentional part of how merging works. Both non-objects, and any object * which has fallen back to a non-object, block subsequent fallbacks. - * + * * @param other * an object whose keys should be used if the keys are not * present in this one diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java b/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java index c559f9e4ee..8613840223 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigObject.java @@ -6,54 +6,67 @@ package com.typesafe.config; import java.util.Map; /** - * A ConfigObject is a read-only configuration object, which may have nested - * child objects. Implementations of ConfigObject should be immutable (at least - * from the perspective of anyone using this interface) and thus thread-safe. + * Subtype of {@link ConfigValue} representing an object (dictionary, map) + * value, as in JSON's { "a" : 42 } syntax. * - * In most cases you want to use the Config interface rather than this one. Call - * toConfig() to convert a ConfigObject to a config. + *

+ * {@code ConfigObject} implements {@code java.util.Map} so + * you can use it like a regular Java map. Or call {@link #unwrapped()} to + * unwrap the map to a map with plain Java values rather than + * {@code ConfigValue}. * - * The API for a ConfigObject is in terms of keys, while the API for a Config is - * in terms of path expressions. Conceptually, ConfigObject is a tree of maps - * from keys to values, while a ConfigObject is a one-level map from paths to - * values. + *

+ * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. + * This makes it threadsafe and you never have to create "defensive copies." The + * mutator methods from {@link java.util.Map} all throw + * {@link java.lang.UnsupportedOperationException}. * - * Throughout the API, there is a distinction between "keys" and "paths". A key - * is a key in a JSON object; it's just a string that's the key in a map. A - * "path" is a parseable expression with a syntax and it refers to a series of - * keys. A path is used to traverse nested ConfigObject by looking up each key - * in the path. Path expressions are described in the spec for "HOCON", which - * can be found at https://github.com/havocp/config/blob/master/HOCON.md; in - * brief, a path is period-separated so "a.b.c" looks for key c in object b in - * object a in the root object. Sometimes double quotes are needed around - * special characters in path expressions. + *

+ * The {@link ConfigValue#valueType} method on an object returns + * {@link ConfigValueType#OBJECT}. * - * ConfigObject implements java.util.Map and all methods - * work with keys, not path expressions. + *

+ * In most cases you want to use the {@link Config} interface rather than this + * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a + * {@code Config}. * - * While ConfigObject implements the standard Java Map interface, the mutator - * methods all throw UnsupportedOperationException. This Map is immutable. + *

+ * The API for a {@code ConfigObject} is in terms of keys, while the API for a + * {@link Config} is in terms of path expressions. Conceptually, + * {@code ConfigObject} is a tree of maps from keys to values, while a + * {@code ConfigObject} is a one-level map from paths to values. * - * The Map may contain null values, which will have ConfigValue.valueType() == - * ConfigValueType.NULL. If get() returns Java's null then the key was not - * present in the parsed file (or wherever this value tree came from). If get() - * returns a ConfigValue with type ConfigValueType.NULL then the key was set to - * null explicitly. + *

+ * A {@code ConfigObject} may contain null values, which will have + * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If + * {@code get()} returns Java's null then the key was not present in the parsed + * file (or wherever this value tree came from). If {@code get()} returns a + * {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was + * set to null explicitly in the config file. + * + *

+ * Do not implement {@code ConfigObject}; it should only be implemented + * by the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. */ public interface ConfigObject extends ConfigValue, Map { /** - * Converts this object to a Config instance, enabling you to use path - * expressions to find values in the object. This is a constant-time + * Converts this object to a {@link Config} instance, enabling you to use + * path expressions to find values in the object. This is a constant-time * operation (it is not proportional to the size of the object). * - * @return + * @return a {@link Config} with this object as its root */ Config toConfig(); /** * Recursively unwraps the object, returning a map from String to whatever * plain Java values are unwrapped from the object's values. + * + * @return a {@link java.util.Map} containing plain Java objects */ @Override Map unwrapped(); @@ -62,10 +75,15 @@ public interface ConfigObject extends ConfigValue, Map { ConfigObject withFallback(ConfigMergeable other); /** - * Gets a ConfigValue at the given key, or returns null if there is no - * value. The returned ConfigValue may have ConfigValueType.NULL or any - * other type, and the passed-in key must be a key in this object, rather - * than a path expression. + * Gets a {@link ConfigValue} at the given key, or returns null if there is + * no value. The returned {@link ConfigValue} may have + * {@link ConfigValueType#NULL} or any other type, and the passed-in key + * must be a key in this object, rather than a path expression. + * + * @param key + * key to look up + * + * @return the value at the key or null if none */ @Override ConfigValue get(Object key); diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java b/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java index 73240736e5..013d91eb9e 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigOrigin.java @@ -3,10 +3,67 @@ */ package com.typesafe.config; +import java.net.URL; + + /** - * ConfigOrigin is used to track the origin (such as filename and line number) - * of a ConfigValue or other object. The origin is used in error messages. + * Represents the origin (such as filename and line number) of a + * {@link ConfigValue} for use in error messages. Obtain the origin of a value + * with {@link ConfigValue#origin}. Exceptions may have an origin, see + * {@link ConfigException#origin}, but be careful because + * ConfigException.origin() may return null. + * + *

+ * It's best to use this interface only for debugging; its accuracy is + * "best effort" rather than guaranteed, and a potentially-noticeable amount of + * memory could probably be saved if origins were not kept around, so in the + * future there might be some option to discard origins. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. */ public interface ConfigOrigin { + /** + * Returns a string describing the origin of a value or exception. This will + * never return null. + * + * @return string describing the origin + */ public String description(); + + /** + * Returns a filename describing the origin. This will return null if the + * origin was not a file. + * + * @return filename of the origin or null + */ + public String filename(); + + /** + * Returns a URL describing the origin. This will return null if the origin + * has no meaningful URL. + * + * @return url of the origin or null + */ + public URL url(); + + /** + * Returns a classpath resource name describing the origin. This will return + * null if the origin was not a classpath resource. + * + * @return resource name of the origin or null + */ + public String resource(); + + /** + * Returns a line number where the value or exception originated. This will + * return -1 if there's no meaningful line number. + * + * @return line number or -1 if none is available + */ + public int lineNumber(); } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java b/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java index ac0c8f3974..f3765a5479 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigParseOptions.java @@ -4,6 +4,22 @@ package com.typesafe.config; +/** + * A set of options related to parsing. + * + *

+ * This object is immutable, so the "setters" return a new object. + * + *

+ * Here is an example of creating a custom {@code ConfigParseOptions}: + * + *

+ *     ConfigParseOptions options = ConfigParseOptions.defaults()
+ *         .setSyntax(ConfigSyntax.JSON)
+ *         .setAllowMissing(false)
+ * 
+ * + */ public final class ConfigParseOptions { final ConfigSyntax syntax; final String originDescription; @@ -24,10 +40,11 @@ public final class ConfigParseOptions { /** * Set the file format. If set to null, try to guess from any available - * filename extension; if guessing fails, assume ConfigSyntax.CONF. - * + * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}. + * * @param syntax - * @return + * a syntax or {@code null} for best guess + * @return options with the syntax set */ public ConfigParseOptions setSyntax(ConfigSyntax syntax) { if (this.syntax == syntax) @@ -45,10 +62,11 @@ public final class ConfigParseOptions { * Set a description for the thing being parsed. In most cases this will be * set up for you to something like the filename, but if you provide just an * input stream you might want to improve on it. Set to null to allow the - * library to come up with something automatically. + * library to come up with something automatically. This description is the + * basis for the {@link ConfigOrigin} of the parsed values. * * @param originDescription - * @return + * @return options with the origin description set */ public ConfigParseOptions setOriginDescription(String originDescription) { if (this.originDescription == originDescription) @@ -79,7 +97,7 @@ public final class ConfigParseOptions { * case. * * @param allowMissing - * @return + * @return options with the "allow missing" flag set */ public ConfigParseOptions setAllowMissing(boolean allowMissing) { if (this.allowMissing == allowMissing) diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java b/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java index 4c19b5451a..1cc39614ca 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigParseable.java @@ -3,21 +3,40 @@ */ package com.typesafe.config; -import java.net.URL; -/** An opaque handle to something that can be parsed. */ +/** + * An opaque handle to something that can be parsed, obtained from + * {@link ConfigIncludeContext}. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ public interface ConfigParseable { /** - * Parse whatever it is. - * + * Parse whatever it is. The options should come from + * {@link ConfigParseable#options options()} but you could tweak them if you + * like. + * * @param options - * parse options, should be based on the ones from options() + * parse options, should be based on the ones from + * {@link ConfigParseable#options options()} */ ConfigObject parse(ConfigParseOptions options); - /** Possibly return a URL representing the resource; this may return null. */ - URL url(); + /** + * Returns a {@link ConfigOrigin} describing the origin of the parseable + * item. + */ + ConfigOrigin origin(); - /** Get the initial options, which can be modified then passed to parse(). */ + /** + * Get the initial options, which can be modified then passed to parse(). + * These options will have the right description, includer, and other + * parameters already set up. + */ ConfigParseOptions options(); } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java index 6f572a84e8..8ed11c4bba 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java @@ -3,6 +3,28 @@ */ package com.typesafe.config; +/** + * A set of options related to resolving substitutions. Substitutions use the + * ${foo.bar} syntax and are documented in the HOCON spec. + * + *

+ * This object is immutable, so the "setters" return a new object. + * + *

+ * Here is an example of creating a custom {@code ConfigResolveOptions}: + * + *

+ *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
+ *         .setUseSystemProperties(false)
+ *         .setUseSystemEnvironment(false)
+ * 
+ * + *

+ * In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt + * {@link ConfigResolveOptions#noSystem} which avoids looking at any system + * properties or environment variables. + */ public final class ConfigResolveOptions { private final boolean useSystemProperties; private final boolean useSystemEnvironment; @@ -13,26 +35,68 @@ public final class ConfigResolveOptions { this.useSystemEnvironment = useSystemEnvironment; } + /** + * Returns the default resolve options. + * + * @return the default resolve options + */ public static ConfigResolveOptions defaults() { return new ConfigResolveOptions(true, true); } + /** + * Returns resolve options that disable any reference to "system" data + * (system properties or environment variables). + * + * @return the resolve options with system properties and env variables + * disabled + */ public static ConfigResolveOptions noSystem() { - return new ConfigResolveOptions(false, false); + return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false); } + /** + * Returns options with use of Java system properties set to the given + * value. + * + * @param value + * true to resolve substitutions falling back to Java system + * properties. + * @return options with requested setting for use of system properties + */ public ConfigResolveOptions setUseSystemProperties(boolean value) { return new ConfigResolveOptions(value, useSystemEnvironment); } + /** + * Returns options with use of environment variables set to the given value. + * + * @param value + * true to resolve substitutions falling back to environment + * variables. + * @return options with requested setting for use of environment variables + */ public ConfigResolveOptions setUseSystemEnvironment(boolean value) { return new ConfigResolveOptions(useSystemProperties, value); } + /** + * Returns whether the options enable use of system properties. This method + * is mostly used by the config lib internally, not by applications. + * + * @return true if system properties should be used + */ public boolean getUseSystemProperties() { return useSystemProperties; } + /** + * Returns whether the options enable use of system environment variables. + * This method is mostly used by the config lib internally, not by + * applications. + * + * @return true if environment variables should be used + */ public boolean getUseSystemEnvironment() { return useSystemEnvironment; } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java b/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java deleted file mode 100644 index d8f25e89d6..0000000000 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigRoot.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (C) 2011 Typesafe Inc. - */ -package com.typesafe.config; - -/** - * A root object. The only special thing about a root object is that you can - * resolve substitutions against it. So it can have a resolve() method that - * doesn't require you to pass in an object to resolve against. - */ -public interface ConfigRoot extends Config { - /** - * Returns a replacement root object with all substitutions (the - * "${foo.bar}" syntax) resolved. Substitutions are looked up in this root - * object. A configuration value tree must be resolved before you can use - * it. This method uses ConfigResolveOptions.defaults(). - * - * @return an immutable object with substitutions resolved - */ - ConfigRoot resolve(); - - ConfigRoot resolve(ConfigResolveOptions options); - - @Override - ConfigRoot withFallback(ConfigMergeable fallback); - - /** - * Gets the global app name that this root represents. - * - * @return the app's root config path - */ - String rootPath(); -} diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java b/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java index 4f43fe7365..58e7fc020b 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigSyntax.java @@ -3,6 +3,30 @@ */ package com.typesafe.config; +/** + * The syntax of a character stream, JSON, HOCON aka + * ".conf", or Java properties. + * + */ public enum ConfigSyntax { - JSON, CONF, PROPERTIES; + /** + * Pedantically strict JSON format; no + * comments, no unexpected commas, no duplicate keys in the same object. + */ + JSON, + /** + * The JSON-superset HOCON + * format. + */ + CONF, + /** + * Standard Java properties format. + */ + PROPERTIES; } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java b/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java index 8b50f7b205..b636c6f4cd 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigValue.java @@ -4,32 +4,57 @@ package com.typesafe.config; /** - * Interface implemented by any configuration value. From the perspective of - * users of this interface, the object is immutable. It is therefore safe to use - * from multiple threads. + * An immutable value, following the JSON type + * schema. + * + *

+ * Because this object is immutable, it is safe to use from multiple threads and + * there's no need for "defensive copies." + * + *

+ * Do not implement {@code ConfigValue}; it should only be implemented + * by the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. */ public interface ConfigValue extends ConfigMergeable { /** - * The origin of the value, for debugging and error messages. + * The origin of the value (file, line number, etc.), for debugging and + * error messages. * * @return where the value came from */ ConfigOrigin origin(); /** - * The type of the value; matches the JSON type schema. + * The {@link ConfigValueType} of the value; matches the JSON type schema. * * @return value's type */ ConfigValueType valueType(); /** - * Returns the config value as a plain Java boxed value, should be a String, - * Number, etc. matching the valueType() of the ConfigValue. If the value is - * a ConfigObject or ConfigList, it is recursively unwrapped. + * Returns the value as a plain Java boxed value, that is, a {@code String}, + * {@code Number}, {@code Boolean}, {@code Map}, + * {@code List}, or {@code null}, matching the {@link #valueType()} + * of this {@code ConfigValue}. If the value is a {@link ConfigObject} or + * {@link ConfigList}, it is recursively unwrapped. */ Object unwrapped(); + /** + * Renders the config value as a HOCON string. This method is primarily + * intended for debugging, so it tries to add helpful comments and + * whitespace. If the config value has not been resolved (see + * {@link Config#resolve}), it's possible that it can't be rendered as valid + * HOCON. In that case the rendering should still be useful for debugging + * but you might not be able to parse it. + * + * @return the rendered value + */ + String render(); + @Override ConfigValue withFallback(ConfigMergeable other); } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigValueFactory.java b/akka-actor/src/main/java/com/typesafe/config/ConfigValueFactory.java index fd63a6ffe5..2f381f9ad1 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigValueFactory.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigValueFactory.java @@ -3,7 +3,6 @@ */ package com.typesafe.config; -import java.util.Collection; import java.util.Map; import com.typesafe.config.impl.ConfigImpl; @@ -14,6 +13,9 @@ import com.typesafe.config.impl.ConfigImpl; * data structures. */ public final class ConfigValueFactory { + private ConfigValueFactory() { + } + /** * Creates a ConfigValue from a plain Java boxed value, which may be a * Boolean, Number, String, Map, Iterable, or null. A Map must be a Map from @@ -23,23 +25,27 @@ public final class ConfigValueFactory { * the Iterable is not an ordered collection, results could be strange, * since ConfigList is ordered. * + *

* In a Map passed to fromAnyRef(), the map's keys are plain keys, not path * expressions. So if your Map has a key "foo.bar" then you will get one * object with a key called "foo.bar", rather than an object with a key * "foo" containing another object with a key "bar". * + *

* The originDescription will be used to set the origin() field on the * ConfigValue. It should normally be the name of the file the values came * from, or something short describing the value such as "default settings". * The originDescription is prefixed to error messages so users can tell * where problematic values are coming from. * + *

* Supplying the result of ConfigValue.unwrapped() to this function is * guaranteed to work and should give you back a ConfigValue that matches * the one you unwrapped. The re-wrapped ConfigValue will lose some * information that was present in the original such as its origin, but it * will have matching values. * + *

* This function throws if you supply a value that cannot be converted to a * ConfigValue, but supplying such a value is a bug in your program, so you * should never handle the exception. Just fix your program (or report a bug @@ -57,23 +63,25 @@ public final class ConfigValueFactory { /** * See the fromAnyRef() documentation for details. This is a typesafe - * wrapper that only works on Map and returns ConfigObject rather than - * ConfigValue. + * wrapper that only works on {@link java.util.Map} and returns + * {@link ConfigObject} rather than {@link ConfigValue}. * + *

* If your Map has a key "foo.bar" then you will get one object with a key * called "foo.bar", rather than an object with a key "foo" containing * another object with a key "bar". The keys in the map are keys; not path * expressions. That is, the Map corresponds exactly to a single - * ConfigObject. The keys will not be parsed or modified, and the values are - * wrapped in ConfigValue. To get nested ConfigObject, some of the values in - * the map would have to be more maps. + * {@code ConfigObject}. The keys will not be parsed or modified, and the + * values are wrapped in ConfigValue. To get nested {@code ConfigObject}, + * some of the values in the map would have to be more maps. * - * There is a separate fromPathMap() that interprets the keys in the map as - * path expressions. + *

+ * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the + * keys in the map as path expressions. * * @param values * @param originDescription - * @return + * @return a new {@link ConfigObject} value */ public static ConfigObject fromMap(Map values, String originDescription) { @@ -82,12 +90,12 @@ public final class ConfigValueFactory { /** * See the fromAnyRef() documentation for details. This is a typesafe - * wrapper that only works on Iterable and returns ConfigList rather than - * ConfigValue. + * wrapper that only works on {@link java.util.Iterable} and returns + * {@link ConfigList} rather than {@link ConfigValue}. * * @param values * @param originDescription - * @return + * @return a new {@link ConfigList} value */ public static ConfigList fromIterable(Iterable values, String originDescription) { @@ -95,35 +103,39 @@ public final class ConfigValueFactory { } /** - * See the other overload of fromAnyRef() for details, this one just uses a - * default origin description. + * See the other overload {@link #fromAnyRef(Object,String)} for details, + * this one just uses a default origin description. * * @param object - * @return + * @return a new {@link ConfigValue} */ public static ConfigValue fromAnyRef(Object object) { return fromAnyRef(object, null); } /** - * See the other overload of fromMap() for details, this one just uses a - * default origin description. + * See the other overload {@link #fromMap(Map,String)} for details, this one + * just uses a default origin description. + * + *

+ * See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in + * the map as path expressions. * * @param values - * @return + * @return a new {@link ConfigObject} */ public static ConfigObject fromMap(Map values) { return fromMap(values, null); } /** - * See the other overload of fromIterable() for details, this one just uses - * a default origin description. + * See the other overload of {@link #fromIterable(Iterable, String)} for + * details, this one just uses a default origin description. * * @param values - * @return + * @return a new {@link ConfigList} */ - public static ConfigList fromIterable(Collection values) { + public static ConfigList fromIterable(Iterable values) { return fromIterable(values, null); } } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigValueType.java b/akka-actor/src/main/java/com/typesafe/config/ConfigValueType.java index 0ead8e1965..981cb6d189 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigValueType.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigValueType.java @@ -4,7 +4,8 @@ package com.typesafe.config; /** - * The type of a configuration value. Value types follow the JSON type schema. + * The type of a configuration value (following the JSON type schema). */ public enum ConfigValueType { OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index d9d41e3f04..3f7cc0edf1 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -61,19 +61,23 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements * (just returns null if path not found). Does however resolve the path, if * resolver != null. */ - protected ConfigValue peekPath(Path path, SubstitutionResolver resolver, + protected AbstractConfigValue peekPath(Path path, SubstitutionResolver resolver, int depth, ConfigResolveOptions options) { return peekPath(this, path, resolver, depth, options); } - private static ConfigValue peekPath(AbstractConfigObject self, Path path, + AbstractConfigValue peekPath(Path path) { + return peekPath(this, path, null, 0, null); + } + + private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path, SubstitutionResolver resolver, int depth, ConfigResolveOptions options) { String key = path.first(); Path next = path.remainder(); if (next == null) { - ConfigValue v = self.peek(key, resolver, depth, options); + AbstractConfigValue v = self.peek(key, resolver, depth, options); return v; } else { // it's important to ONLY resolve substitutions here, not @@ -164,18 +168,13 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements if (stack.isEmpty()) throw new ConfigException.BugOrBroken( "can't merge origins on empty list"); - final String prefix = "merge of "; - StringBuilder sb = new StringBuilder(); + List origins = new ArrayList(); ConfigOrigin firstOrigin = null; int numMerged = 0; for (AbstractConfigValue v : stack) { if (firstOrigin == null) firstOrigin = v.origin(); - String desc = v.origin().description(); - if (desc.startsWith(prefix)) - desc = desc.substring(prefix.length()); - if (v instanceof AbstractConfigObject && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED && ((ConfigObject) v).isEmpty()) { @@ -183,22 +182,17 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements // config in the description, since they are // likely to be "implementation details" } else { - sb.append(desc); - sb.append(","); + origins.add(v.origin()); numMerged += 1; } } - if (numMerged > 0) { - sb.setLength(sb.length() - 1); // chop comma - if (numMerged > 1) { - return new SimpleConfigOrigin(prefix + sb.toString()); - } else { - return new SimpleConfigOrigin(sb.toString()); - } - } else { - // the configs were all empty. - return firstOrigin; + + if (numMerged == 0) { + // the configs were all empty, so just use the first one + origins.add(firstOrigin); } + + return SimpleConfigOrigin.mergeOrigins(origins); } static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { @@ -210,6 +204,8 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Map changes = null; for (String k : keySet()) { AbstractConfigValue v = peek(k); + // "modified" may be null, which means remove the child; + // to do that we put null in the "changes" map. AbstractConfigValue modified = modifier.modifyChild(v); if (modified != v) { if (changes == null) @@ -223,7 +219,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Map modified = new HashMap(); for (String k : keySet()) { if (changes.containsKey(k)) { - modified.put(k, changes.get(k)); + AbstractConfigValue newValue = changes.get(k); + if (newValue != null) { + modified.put(k, newValue); + } else { + // remove this child; don't put it in the new map. + } } else { modified.put(k, peek(k)); } @@ -271,20 +272,36 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements } @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(valueType().name()); - sb.append("("); - for (String k : keySet()) { - sb.append(k); - sb.append("->"); - sb.append(peek(k).toString()); - sb.append(","); + protected void render(StringBuilder sb, int indent, boolean formatted) { + if (isEmpty()) { + sb.append("{}"); + } else { + sb.append("{"); + if (formatted) + sb.append('\n'); + for (String k : keySet()) { + AbstractConfigValue v = peek(k); + if (formatted) { + indent(sb, indent + 1); + sb.append("# "); + sb.append(v.origin().description()); + sb.append("\n"); + indent(sb, indent + 1); + } + v.render(sb, indent + 1, k, formatted); + sb.append(","); + if (formatted) + sb.append('\n'); + } + // chop comma or newline + sb.setLength(sb.length() - 1); + if (formatted) { + sb.setLength(sb.length() - 1); // also chop comma + sb.append("\n"); // put a newline back + indent(sb, indent); + } + sb.append("}"); } - if (!keySet().isEmpty()) - sb.setLength(sb.length() - 1); // chop comma - sb.append(")"); - return sb.toString(); } private static boolean mapEquals(Map a, diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 2125cf723b..f94cf22123 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -162,8 +162,39 @@ abstract class AbstractConfigValue implements ConfigValue { } @Override - public String toString() { - return valueType().name() + "(" + unwrapped() + ")"; + public final String toString() { + StringBuilder sb = new StringBuilder(); + render(sb, 0, null /* atKey */, false /* formatted */); + return getClass().getSimpleName() + "(" + sb.toString() + ")"; + } + + protected static void indent(StringBuilder sb, int indent) { + int remaining = indent; + while (remaining > 0) { + sb.append(" "); + --remaining; + } + } + + protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { + if (atKey != null) { + sb.append(ConfigUtil.renderJsonString(atKey)); + sb.append(" : "); + } + render(sb, indent, formatted); + } + + protected void render(StringBuilder sb, int indent, boolean formatted) { + Object u = unwrapped(); + sb.append(u.toString()); + } + + + @Override + public final String render() { + StringBuilder sb = new StringBuilder(); + render(sb, 0, null, true /* formatted */); + return sb.toString(); } // toString() is a debugging-oriented string but this is defined diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java index 0c20aa5701..b916d9a0a7 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -5,6 +5,7 @@ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import com.typesafe.config.ConfigException; @@ -76,10 +77,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements AbstractConfigValue merged = null; for (AbstractConfigValue v : stack) { AbstractConfigValue resolved = resolver.resolve(v, depth, options); - if (merged == null) - merged = resolved; - else - merged = merged.withFallback(resolved); + if (resolved != null) { + if (merged == null) + merged = resolved; + else + merged = merged.withFallback(resolved); + } } return merged; @@ -160,16 +163,58 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements } @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("DELAYED_MERGE"); - sb.append("("); - for (Object s : stack) { - sb.append(s.toString()); - sb.append(","); + protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { + render(stack, sb, indent, atKey, formatted); + } + + // static method also used by ConfigDelayedMergeObject. + static void render(List stack, StringBuilder sb, int indent, String atKey, + boolean formatted) { + if (formatted) { + sb.append("# unresolved merge of " + stack.size() + " values follows (\n"); + if (atKey == null) { + indent(sb, indent); + sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n"); + sb.append("# the HOCON format has no way to list multiple root objects in a single file\n"); + } + } + + List reversed = new ArrayList(); + reversed.addAll(stack); + Collections.reverse(reversed); + + int i = 0; + for (AbstractConfigValue v : reversed) { + if (formatted) { + indent(sb, indent); + if (atKey != null) { + sb.append("# unmerged value " + i + " for key " + + ConfigUtil.renderJsonString(atKey) + " from "); + } else { + sb.append("# unmerged value " + i + " from "); + } + i += 1; + sb.append(v.origin().description()); + sb.append("\n"); + indent(sb, indent); + } + + if (atKey != null) { + sb.append(ConfigUtil.renderJsonString(atKey)); + sb.append(" : "); + } + v.render(sb, indent, formatted); + sb.append(","); + if (formatted) + sb.append('\n'); + } + // chop comma or newline + sb.setLength(sb.length() - 1); + if (formatted) { + sb.setLength(sb.length() - 1); // also chop comma + sb.append("\n"); // put a newline back + indent(sb, indent); + sb.append("# ) end of unresolved merge\n"); } - sb.setLength(sb.length() - 1); // chop comma - sb.append(")"); - return sb.toString(); } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java index 6f381afc48..5669f62f34 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -139,17 +139,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements } @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("DELAYED_MERGE_OBJECT"); - sb.append("("); - for (Object s : stack) { - sb.append(s.toString()); - sb.append(","); - } - sb.setLength(sb.length() - 1); // chop comma - sb.append(")"); - return sb.toString(); + protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { + ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted); } private static ConfigException notResolved() { diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 8c2bd01ec1..ddb810494c 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -19,7 +19,6 @@ import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; -import com.typesafe.config.ConfigRoot; import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigValue; @@ -45,8 +44,7 @@ public class ConfigImpl { obj = p.parse(p.options().setAllowMissing( options.getAllowMissing())); } else { - obj = SimpleConfigObject.emptyMissing(new SimpleConfigOrigin( - name)); + obj = SimpleConfigObject.emptyMissing(SimpleConfigOrigin.newSimple(name)); } } else { ConfigParseable confHandle = source.nameToParseable(name + ".conf"); @@ -56,13 +54,13 @@ public class ConfigImpl { if (!options.getAllowMissing() && confHandle == null && jsonHandle == null && propsHandle == null) { - throw new ConfigException.IO(new SimpleConfigOrigin(name), + throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), "No config files {.conf,.json,.properties} found"); } ConfigSyntax syntax = options.getSyntax(); - obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name)); + obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name)); if (confHandle != null && (syntax == null || syntax == ConfigSyntax.CONF)) { obj = confHandle.parse(confHandle.options() @@ -89,39 +87,13 @@ public class ConfigImpl { return obj; } - private static String makeResourceBasename(Path path) { - StringBuilder sb = new StringBuilder("/"); - String next = path.first(); - Path remaining = path.remainder(); - while (next != null) { - sb.append(next); - sb.append('-'); - - if (remaining == null) - break; - - next = remaining.first(); - remaining = remaining.remainder(); - } - sb.setLength(sb.length() - 1); // chop extra hyphen - return sb.toString(); - } - /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigObject parseResourcesForPath(String expression, - final ConfigParseOptions baseOptions) { - Path path = Parser.parsePath(expression); - String basename = makeResourceBasename(path); - return parseResourceAnySyntax(ConfigImpl.class, basename, baseOptions); - } - - /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigObject parseResourceAnySyntax(final Class klass, + public static ConfigObject parseResourcesAnySyntax(final Class klass, String resourceBasename, final ConfigParseOptions baseOptions) { NameSource source = new NameSource() { @Override public ConfigParseable nameToParseable(String name) { - return Parseable.newResource(klass, name, baseOptions); + return Parseable.newResources(klass, name, baseOptions); } }; return fromBasename(source, resourceBasename, baseOptions); @@ -139,16 +111,9 @@ public class ConfigImpl { return fromBasename(source, basename.getPath(), baseOptions); } - /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigRoot emptyRoot(String rootPath, String originDescription) { - String desc = originDescription != null ? originDescription : rootPath; - return emptyObject(desc).toConfig().asRoot( - Path.newPath(rootPath)); - } - static AbstractConfigObject emptyObject(String originDescription) { - ConfigOrigin origin = originDescription != null ? new SimpleConfigOrigin( - originDescription) : null; + ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin + .newSimple(originDescription) : null; return emptyObject(origin); } @@ -162,8 +127,8 @@ public class ConfigImpl { } // default origin for values created with fromAnyRef and no origin specified - final private static ConfigOrigin defaultValueOrigin = new SimpleConfigOrigin( - "hardcoded value"); + final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin + .newSimple("hardcoded value"); final private static ConfigBoolean defaultTrueValue = new ConfigBoolean( defaultValueOrigin, true); final private static ConfigBoolean defaultFalseValue = new ConfigBoolean( @@ -196,7 +161,7 @@ public class ConfigImpl { if (originDescription == null) return defaultValueOrigin; else - return new SimpleConfigOrigin(originDescription); + return SimpleConfigOrigin.newSimple(originDescription); } /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ @@ -290,17 +255,6 @@ public class ConfigImpl { } } - /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ - public static ConfigRoot systemPropertiesRoot(String rootPath) { - Path path = Parser.parsePath(rootPath); - try { - return systemPropertiesAsConfigObject().toConfig().getConfig(rootPath) - .asRoot(path); - } catch (ConfigException.Missing e) { - return emptyObject("system properties").toConfig().asRoot(path); - } - } - private static class SimpleIncluder implements ConfigIncluder { private ConfigIncluder fallback; @@ -346,29 +300,26 @@ public class ConfigImpl { } } - private static ConfigIncluder defaultIncluder = null; - - synchronized static ConfigIncluder defaultIncluder() { - if (defaultIncluder == null) { - defaultIncluder = new SimpleIncluder(null); - } - return defaultIncluder; + private static class DefaultIncluderHolder { + static final ConfigIncluder defaultIncluder = new SimpleIncluder(null); } - private static AbstractConfigObject systemProperties = null; - - synchronized static AbstractConfigObject systemPropertiesAsConfigObject() { - if (systemProperties == null) { - systemProperties = loadSystemProperties(); - } - return systemProperties; + static ConfigIncluder defaultIncluder() { + return DefaultIncluderHolder.defaultIncluder; } private static AbstractConfigObject loadSystemProperties() { - return (AbstractConfigObject) Parseable.newProperties( - System.getProperties(), - ConfigParseOptions.defaults().setOriginDescription( - "system properties")).parse(); + return (AbstractConfigObject) Parseable.newProperties(System.getProperties(), + ConfigParseOptions.defaults().setOriginDescription("system properties")).parse(); + } + + private static class SystemPropertiesHolder { + // this isn't final due to the reloadSystemPropertiesConfig() hack below + static AbstractConfigObject systemProperties = loadSystemProperties(); + } + + static AbstractConfigObject systemPropertiesAsConfigObject() { + return SystemPropertiesHolder.systemProperties; } /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ @@ -376,18 +327,10 @@ public class ConfigImpl { return systemPropertiesAsConfigObject().toConfig(); } - // this is a hack to let us set system props in the test suite - synchronized static void dropSystemPropertiesConfig() { - systemProperties = null; - } - - private static AbstractConfigObject envVariables = null; - - synchronized static AbstractConfigObject envVariablesAsConfigObject() { - if (envVariables == null) { - envVariables = loadEnvVariables(); - } - return envVariables; + // this is a hack to let us set system props in the test suite. + // obviously not thread-safe. + static void reloadSystemPropertiesConfig() { + SystemPropertiesHolder.systemProperties = loadSystemProperties(); } private static AbstractConfigObject loadEnvVariables() { @@ -395,15 +338,37 @@ public class ConfigImpl { Map m = new HashMap(); for (Map.Entry entry : env.entrySet()) { String key = entry.getKey(); - m.put(key, new ConfigString( - new SimpleConfigOrigin("env var " + key), entry.getValue())); + m.put(key, + new ConfigString(SimpleConfigOrigin.newSimple("env var " + key), entry + .getValue())); } - return new SimpleConfigObject(new SimpleConfigOrigin("env variables"), + return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"), m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); } + private static class EnvVariablesHolder { + static final AbstractConfigObject envVariables = loadEnvVariables(); + } + + static AbstractConfigObject envVariablesAsConfigObject() { + return EnvVariablesHolder.envVariables; + } + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ public static Config envVariablesAsConfig() { return envVariablesAsConfigObject().toConfig(); } + + private static class ReferenceHolder { + private static final Config unresolvedResources = Parseable + .newResources(ConfigImpl.class, "/reference.conf", ConfigParseOptions.defaults()) + .parse().toConfig(); + static final Config referenceConfig = systemPropertiesAsConfig().withFallback( + unresolvedResources).resolve(); + } + + /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ + public static Config defaultReference() { + return ReferenceHolder.referenceConfig; + } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigNull.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigNull.java index ea8976e340..a45d2dbc40 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigNull.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigNull.java @@ -34,4 +34,9 @@ final class ConfigNull extends AbstractConfigValue { String transformToString() { return "null"; } + + @Override + protected void render(StringBuilder sb, int indent, boolean formatted) { + sb.append("null"); + } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigString.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigString.java index e054b6c91f..dd8a5fa3b0 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigString.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigString.java @@ -29,4 +29,9 @@ final class ConfigString extends AbstractConfigValue { String transformToString() { return value; } + + @Override + protected void render(StringBuilder sb, int indent, boolean formatted) { + sb.append(ConfigUtil.renderJsonString(value)); + } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java index 2c5531e74f..1b27408654 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java @@ -22,8 +22,8 @@ import com.typesafe.config.ConfigValueType; final class ConfigSubstitution extends AbstractConfigValue implements Unmergeable { - // this is a list of String and Path where the Path - // have to be resolved to values, then if there's more + // this is a list of String and SubstitutionExpression where the + // SubstitutionExpression has to be resolved to values, then if there's more // than one piece everything is stringified and concatenated final private List pieces; // the length of any prefixes added with relativized() @@ -40,19 +40,24 @@ final class ConfigSubstitution extends AbstractConfigValue implements this.pieces = pieces; this.prefixLength = prefixLength; this.ignoresFallbacks = ignoresFallbacks; + for (Object p : pieces) { + if (p instanceof Path) + throw new RuntimeException("broken here"); + } } @Override public ConfigValueType valueType() { throw new ConfigException.NotResolved( - "tried to get value type on an unresolved substitution: " + "need to call resolve() on root config; tried to get value type on an unresolved substitution: " + this); } @Override public Object unwrapped() { throw new ConfigException.NotResolved( - "tried to unwrap an unresolved substitution: " + this); + "need to call resolve() on root config; tried to unwrap an unresolved substitution: " + + this); } @Override @@ -124,15 +129,15 @@ final class ConfigSubstitution extends AbstractConfigValue implements return result; } - private ConfigValue resolve(SubstitutionResolver resolver, Path subst, + private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst, int depth, ConfigResolveOptions options) { - ConfigValue result = findInObject(resolver.root(), resolver, subst, + ConfigValue result = findInObject(resolver.root(), resolver, subst.path(), depth, options); // when looking up system props and env variables, // we don't want the prefix that was added when // we were included in another file. - Path unprefixed = subst.subPath(prefixLength); + Path unprefixed = subst.path().subPath(prefixLength); if (result == null && options.getUseSystemProperties()) { result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null, @@ -144,10 +149,6 @@ final class ConfigSubstitution extends AbstractConfigValue implements unprefixed, depth, options); } - if (result == null) { - result = new ConfigNull(origin()); - } - return result; } @@ -160,28 +161,40 @@ final class ConfigSubstitution extends AbstractConfigValue implements if (p instanceof String) { sb.append((String) p); } else { - ConfigValue v = resolve(resolver, (Path) p, depth, options); - switch (v.valueType()) { - case NULL: - // nothing; becomes empty string - break; - case LIST: - case OBJECT: - // cannot substitute lists and objects into strings - throw new ConfigException.WrongType(v.origin(), - ((Path) p).render(), - "not a list or object", v.valueType().name()); - default: - sb.append(((AbstractConfigValue) v).transformToString()); + SubstitutionExpression exp = (SubstitutionExpression) p; + ConfigValue v = resolve(resolver, exp, depth, options); + + if (v == null) { + if (exp.optional()) { + // append nothing to StringBuilder + } else { + throw new ConfigException.UnresolvedSubstitution(origin(), + exp.toString()); + } + } else { + switch (v.valueType()) { + case LIST: + case OBJECT: + // cannot substitute lists and objects into strings + throw new ConfigException.WrongType(v.origin(), exp.path().render(), + "not a list or object", v.valueType().name()); + default: + sb.append(((AbstractConfigValue) v).transformToString()); + } } } } return new ConfigString(origin(), sb.toString()); } else { - if (!(pieces.get(0) instanceof Path)) + if (!(pieces.get(0) instanceof SubstitutionExpression)) throw new ConfigException.BugOrBroken( "ConfigSubstitution should never contain a single String piece"); - return resolve(resolver, (Path) pieces.get(0), depth, options); + SubstitutionExpression exp = (SubstitutionExpression) pieces.get(0); + ConfigValue v = resolve(resolver, exp, depth, options); + if (v == null && !exp.optional()) { + throw new ConfigException.UnresolvedSubstitution(origin(), exp.toString()); + } + return v; } } @@ -210,8 +223,10 @@ final class ConfigSubstitution extends AbstractConfigValue implements ConfigSubstitution relativized(Path prefix) { List newPieces = new ArrayList(); for (Object p : pieces) { - if (p instanceof Path) { - newPieces.add(((Path) p).prepend(prefix)); + if (p instanceof SubstitutionExpression) { + SubstitutionExpression exp = (SubstitutionExpression) p; + + newPieces.add(exp.changePath(exp.path().prepend(prefix))); } else { newPieces.add(p); } @@ -243,16 +258,13 @@ final class ConfigSubstitution extends AbstractConfigValue implements } @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("SUBST"); - sb.append("("); + protected void render(StringBuilder sb, int indent, boolean formatted) { for (Object p : pieces) { - sb.append(p.toString()); - sb.append(","); + if (p instanceof SubstitutionExpression) { + sb.append(p.toString()); + } else { + sb.append(ConfigUtil.renderJsonString((String) p)); + } } - sb.setLength(sb.length() - 1); // chop comma - sb.append(")"); - return sb.toString(); } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/OriginType.java b/akka-actor/src/main/java/com/typesafe/config/impl/OriginType.java new file mode 100644 index 0000000000..b8e990c091 --- /dev/null +++ b/akka-actor/src/main/java/com/typesafe/config/impl/OriginType.java @@ -0,0 +1,8 @@ +package com.typesafe.config.impl; + +enum OriginType { + GENERIC, + FILE, + URL, + RESOURCE +} diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java b/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java index eef9e75b2a..71808188d3 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java @@ -17,6 +17,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Enumeration; import java.util.Iterator; import java.util.Properties; @@ -39,6 +40,7 @@ import com.typesafe.config.ConfigValue; public abstract class Parseable implements ConfigParseable { private ConfigIncludeContext includeContext; private ConfigParseOptions initialOptions; + private ConfigOrigin initialOrigin; protected Parseable() { @@ -54,9 +56,6 @@ public abstract class Parseable implements ConfigParseable { } ConfigParseOptions modified = baseOptions.setSyntax(syntax); - if (modified.getOriginDescription() == null) - modified = modified.setOriginDescription(originDescription()); - modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); return modified; @@ -71,6 +70,11 @@ public abstract class Parseable implements ConfigParseable { return Parseable.this.relativeTo(filename); } }; + + if (initialOptions.getOriginDescription() != null) + initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription()); + else + initialOrigin = createOrigin(); } // the general idea is that any work should be in here, not in the @@ -108,33 +112,26 @@ public abstract class Parseable implements ConfigParseable { return forceParsedToObject(parseValue(baseOptions)); } - AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { - // note that we are NOT using our "options" and "origin" fields, + final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { + // note that we are NOT using our "initialOptions", // but using the ones from the passed-in options. The idea is that // callers can get our original options and then parse with different // ones if they want. ConfigParseOptions options = fixupOptions(baseOptions); - ConfigOrigin origin = new SimpleConfigOrigin( - options.getOriginDescription()); + + // passed-in options can override origin + ConfigOrigin origin; + if (options.getOriginDescription() != null) + origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); + else + origin = initialOrigin; return parseValue(origin, options); } - protected AbstractConfigValue parseValue(ConfigOrigin origin, + final private AbstractConfigValue parseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) { try { - Reader reader = reader(); - try { - if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) { - return PropertiesParser.parse(reader, origin); - } else { - Iterator tokens = Tokenizer.tokenize(origin, reader, - finalOptions.getSyntax()); - return Parser.parse(tokens, origin, finalOptions, - includeContext()); - } - } finally { - reader.close(); - } + return rawParseValue(origin, finalOptions); } catch (IOException e) { if (finalOptions.getAllowMissing()) { return SimpleConfigObject.emptyMissing(origin); @@ -144,6 +141,28 @@ public abstract class Parseable implements ConfigParseable { } } + // this is parseValue without post-processing the IOException or handling + // options.getAllowMissing() + protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) + throws IOException { + Reader reader = reader(); + try { + return rawParseValue(reader, origin, finalOptions); + } finally { + reader.close(); + } + } + + protected AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin, + ConfigParseOptions finalOptions) throws IOException { + if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) { + return PropertiesParser.parse(reader, origin); + } else { + Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); + return Parser.parse(tokens, origin, finalOptions, includeContext()); + } + } + public ConfigObject parse() { return forceParsedToObject(parseValue(options())); } @@ -152,13 +171,13 @@ public abstract class Parseable implements ConfigParseable { return parseValue(options()); } - abstract String originDescription(); - @Override - public URL url() { - return null; + public final ConfigOrigin origin() { + return initialOrigin; } + protected abstract ConfigOrigin createOrigin(); + @Override public ConfigParseOptions options() { return initialOptions; @@ -228,34 +247,6 @@ public abstract class Parseable implements ConfigParseable { } } - private final static class ParseableInputStream extends Parseable { - final private InputStream input; - - ParseableInputStream(InputStream input, ConfigParseOptions options) { - this.input = input; - postConstruct(options); - } - - @Override - protected Reader reader() { - return doNotClose(readerFromStream(input)); - } - - @Override - String originDescription() { - return "InputStream"; - } - } - - /** - * note that we will never close this stream; you have to do it when parsing - * is complete. - */ - public static Parseable newInputStream(InputStream input, - ConfigParseOptions options) { - return new ParseableInputStream(input, options); - } - private final static class ParseableReader extends Parseable { final private Reader reader; @@ -270,8 +261,8 @@ public abstract class Parseable implements ConfigParseable { } @Override - String originDescription() { - return "Reader"; + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("Reader"); } } @@ -297,8 +288,8 @@ public abstract class Parseable implements ConfigParseable { } @Override - String originDescription() { - return "String"; + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("String"); } } @@ -335,13 +326,8 @@ public abstract class Parseable implements ConfigParseable { } @Override - String originDescription() { - return input.toExternalForm(); - } - - @Override - public URL url() { - return input; + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newURL(input); } @Override @@ -387,17 +373,8 @@ public abstract class Parseable implements ConfigParseable { } @Override - String originDescription() { - return input.getPath(); - } - - @Override - public URL url() { - try { - return input.toURI().toURL(); - } catch (MalformedURLException e) { - return null; - } + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newFile(input.getPath()); } @Override @@ -410,25 +387,61 @@ public abstract class Parseable implements ConfigParseable { return new ParseableFile(input, options); } - private final static class ParseableResource extends Parseable { - final private Class klass; + private final static class ParseableResources extends Parseable { + final private ClassLoader loader; final private String resource; - ParseableResource(Class klass, String resource, + ParseableResources(ClassLoader loader, String resource, ConfigParseOptions options) { - this.klass = klass; + this.loader = loader; this.resource = resource; postConstruct(options); } @Override protected Reader reader() throws IOException { - InputStream stream = klass.getResourceAsStream(resource); - if (stream == null) { - throw new IOException("resource not found on classpath: " - + resource); + throw new ConfigException.BugOrBroken( + "reader() should not be called on resources"); + } + + @Override + protected AbstractConfigObject rawParseValue(ConfigOrigin origin, + ConfigParseOptions finalOptions) throws IOException { + Enumeration e = loader.getResources(resource); + if (!e.hasMoreElements()) { + throw new IOException("resource not found on classpath: " + resource); } - return readerFromStream(stream); + AbstractConfigObject merged = SimpleConfigObject.empty(origin); + while (e.hasMoreElements()) { + URL url = e.nextElement(); + + ConfigOrigin elementOrigin = ((SimpleConfigOrigin) origin).addURL(url); + + AbstractConfigValue v; + + // it's tempting to use ParseableURL here but it would be wrong + // because the wrong relativeTo() would be used for includes. + InputStream stream = url.openStream(); + try { + Reader reader = readerFromStream(stream); + stream = null; // reader now owns it + try { + // parse in "raw" mode which will throw any IOException + // from here. + v = rawParseValue(reader, elementOrigin, finalOptions); + } finally { + reader.close(); + } + } finally { + // stream is null if the reader owns it + if (stream != null) + stream.close(); + } + + merged = merged.withFallback(v); + } + + return merged; } @Override @@ -436,48 +449,76 @@ public abstract class Parseable implements ConfigParseable { return syntaxFromExtension(resource); } - @Override - ConfigParseable relativeTo(String filename) { - // not using File.isAbsolute because resource paths always use '/' - // (?) - if (filename.startsWith("/")) + static String parent(String resource) { + int i = resource.lastIndexOf('/'); + if (i < 0) { return null; + } else { + return resource.substring(0, i); + } + } + @Override + ConfigParseable relativeTo(String sibling) { // here we want to build a new resource name and let // the class loader have it, rather than getting the // url with getResource() and relativizing to that url. // This is needed in case the class loader is going to // search a classpath. - File parent = new File(resource).getParentFile(); + String parent = parent(resource); if (parent == null) - return newResource(klass, "/" + filename, options() + return newResources(loader, sibling, options() .setOriginDescription(null)); else - return newResource(klass, new File(parent, filename).getPath(), + return newResources(loader, parent + "/" + sibling, options().setOriginDescription(null)); } @Override - String originDescription() { - return resource + " on classpath"; - } - - @Override - public URL url() { - return klass.getResource(resource); + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newResource(resource); } @Override public String toString() { return getClass().getSimpleName() + "(" + resource + "," - + klass.getName() - + ")"; + + loader.getClass().getSimpleName() + ")"; } } - public static Parseable newResource(Class klass, String resource, + public static Parseable newResources(Class klass, String resource, ConfigParseOptions options) { - return new ParseableResource(klass, resource, options); + return newResources(klass.getClassLoader(), convertResourceName(klass, resource), options); + } + + // this function is supposed to emulate the difference + // between Class.getResource and ClassLoader.getResource + // (unfortunately there doesn't seem to be public API for it). + // We're using it because the Class API is more limited, + // for example it lacks getResources(). So we want to be able to + // use ClassLoader directly. + private static String convertResourceName(Class klass, String resource) { + if (resource.startsWith("/")) { + // "absolute" resource, chop the slash + return resource.substring(1); + } else { + String className = klass.getName(); + int i = className.lastIndexOf('.'); + if (i < 0) { + // no package + return resource; + } else { + // need to be relative to the package + String packageName = className.substring(0, i); + String packagePath = packageName.replace('.', '/'); + return packagePath + "/" + resource; + } + } + } + + public static Parseable newResources(ClassLoader loader, String resource, + ConfigParseOptions options) { + return new ParseableResources(loader, resource, options); } private final static class ParseableProperties extends Parseable { @@ -495,7 +536,7 @@ public abstract class Parseable implements ConfigParseable { } @Override - protected AbstractConfigObject parseValue(ConfigOrigin origin, + protected AbstractConfigObject rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) { return PropertiesParser.fromProperties(origin, props); } @@ -506,13 +547,8 @@ public abstract class Parseable implements ConfigParseable { } @Override - String originDescription() { - return "properties"; - } - - @Override - public URL url() { - return null; + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("properties"); } @Override diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Parser.java b/akka-actor/src/main/java/com/typesafe/config/impl/Parser.java index d168e8d57a..8c1434b566 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Parser.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Parser.java @@ -110,7 +110,8 @@ final class Parser { Token t = nextToken(); while (true) { if (Tokens.isNewline(t)) { - lineNumber = Tokens.getLineNumber(t); + // newline number is the line just ended, so add one + lineNumber = Tokens.getLineNumber(t) + 1; sawSeparatorOrNewline = true; // we want to continue to also eat // a comma if there is one. @@ -155,7 +156,7 @@ final class Parser { return; } - // this will be a list of String and Path + // this will be a list of String and SubstitutionExpression List minimized = new ArrayList(); // we have multiple value tokens or one unquoted text token; @@ -187,7 +188,9 @@ final class Parser { .getSubstitutionPathExpression(valueToken); Path path = parsePathExpression(expression.iterator(), Tokens.getSubstitutionOrigin(valueToken)); - minimized.add(path); + boolean optional = Tokens.getSubstitutionOptional(valueToken); + + minimized.add(new SubstitutionExpression(path, optional)); } else { throw new ConfigException.BugOrBroken( "should not be trying to consolidate token: " @@ -219,8 +222,7 @@ final class Parser { } private ConfigOrigin lineOrigin() { - return new SimpleConfigOrigin(baseOrigin.description() + ": line " - + lineNumber); + return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber); } private ConfigException parseError(String message) { @@ -681,7 +683,7 @@ final class Parser { return pb.result(); } - static ConfigOrigin apiOrigin = new SimpleConfigOrigin("path parameter"); + static ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter"); static Path parsePath(String path) { Path speculated = speculativeFastParsePath(path); diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Path.java b/akka-actor/src/main/java/com/typesafe/config/impl/Path.java index b0434f0f14..d57cc0091e 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Path.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Path.java @@ -152,7 +152,7 @@ final class Path { } private void appendToStringBuilder(StringBuilder sb) { - if (hasFunkyChars(first)) + if (hasFunkyChars(first) || first.isEmpty()) sb.append(ConfigUtil.renderJsonString(first)); else sb.append(first); diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/RootConfig.java b/akka-actor/src/main/java/com/typesafe/config/impl/RootConfig.java deleted file mode 100644 index 723b9fad89..0000000000 --- a/akka-actor/src/main/java/com/typesafe/config/impl/RootConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (C) 2011 Typesafe Inc. - */ -package com.typesafe.config.impl; - -import com.typesafe.config.ConfigMergeable; -import com.typesafe.config.ConfigResolveOptions; -import com.typesafe.config.ConfigRoot; - -final class RootConfig extends SimpleConfig implements ConfigRoot { - - final private Path rootPath; - - RootConfig(AbstractConfigObject underlying, Path rootPath) { - super(underlying); - this.rootPath = rootPath; - } - - @Override - protected RootConfig asRoot(AbstractConfigObject underlying, - Path newRootPath) { - if (newRootPath.equals(this.rootPath)) - return this; - else - return new RootConfig(underlying, newRootPath); - } - - @Override - public RootConfig resolve() { - return resolve(ConfigResolveOptions.defaults()); - } - - @Override - public RootConfig resolve(ConfigResolveOptions options) { - // if the object is already resolved then we should end up returning - // "this" here, since asRoot() should return this if the path - // is unchanged. - AbstractConfigObject resolved = resolvedObject(options); - return newRootIfObjectChanged(this, resolved); - } - - @Override - public RootConfig withFallback(ConfigMergeable value) { - // this can return "this" if the withFallback does nothing - return newRootIfObjectChanged(this, super.withFallback(value).toObject()); - } - - Path rootPathObject() { - return rootPath; - } - - @Override - public String rootPath() { - return rootPath.render(); - } - - @Override - public String toString() { - return "Root" + super.toString(); - } -} diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java index d64ce4625a..aba696ae65 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -4,7 +4,9 @@ package com.typesafe.config.impl; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import com.typesafe.config.Config; @@ -34,7 +36,7 @@ class SimpleConfig implements Config { } @Override - public AbstractConfigObject toObject() { + public AbstractConfigObject root() { return object; } @@ -43,35 +45,22 @@ class SimpleConfig implements Config { return object.origin(); } - /** - * Returns a version of this config that implements the ConfigRoot - * interface. - * - * @return a config root - */ - RootConfig asRoot(Path rootPath) { - return asRoot(object, rootPath); + @Override + public SimpleConfig resolve() { + return resolve(ConfigResolveOptions.defaults()); } - // RootConfig overrides this to avoid a new object on unchanged path. - protected RootConfig asRoot(AbstractConfigObject underlying, - Path newRootPath) { - return new RootConfig(underlying, newRootPath); - } - - static protected RootConfig newRootIfObjectChanged(RootConfig self, AbstractConfigObject underlying) { - if (underlying == self.object) - return self; - else - return new RootConfig(underlying, self.rootPathObject()); - } - - protected AbstractConfigObject resolvedObject(ConfigResolveOptions options) { + @Override + public SimpleConfig resolve(ConfigResolveOptions options) { AbstractConfigValue resolved = SubstitutionResolver.resolve(object, object, options); - return (AbstractConfigObject) resolved; + if (resolved == object) + return this; + else + return new SimpleConfig((AbstractConfigObject) resolved); } + @Override public boolean hasPath(String pathExpression) { Path path = Path.newPath(pathExpression); @@ -196,13 +185,13 @@ class SimpleConfig implements Config { } @Override - public Long getMemorySizeInBytes(String path) { + public Long getBytes(String path) { Long size = null; try { size = getLong(path); } catch (ConfigException.WrongType e) { ConfigValue v = find(path, ConfigValueType.STRING, path); - size = parseMemorySizeInBytes((String) v.unwrapped(), + size = parseBytes((String) v.unwrapped(), v.origin(), path); } return size; @@ -346,7 +335,7 @@ class SimpleConfig implements Config { l.add(((Number) v.unwrapped()).longValue()); } else if (v.valueType() == ConfigValueType.STRING) { String s = (String) v.unwrapped(); - Long n = parseMemorySizeInBytes(s, v.origin(), path); + Long n = parseBytes(s, v.origin(), path); l.add(n); } else { throw new ConfigException.WrongType(v.origin(), path, @@ -509,23 +498,87 @@ class SimpleConfig implements Config { } private static enum MemoryUnit { - BYTES(1), KILOBYTES(1024), MEGABYTES(1024 * 1024), GIGABYTES( - 1024 * 1024 * 1024), TERABYTES(1024 * 1024 * 1024 * 1024); + BYTES("", 1024, 0), - int bytes; + KILOBYTES("kilo", 1000, 1), + MEGABYTES("mega", 1000, 2), + GIGABYTES("giga", 1000, 3), + TERABYTES("tera", 1000, 4), + PETABYTES("peta", 1000, 5), + EXABYTES("exa", 1000, 6), + ZETTABYTES("zetta", 1000, 7), + YOTTABYTES("yotta", 1000, 8), - MemoryUnit(int bytes) { + KIBIBYTES("kibi", 1024, 1), + MEBIBYTES("mebi", 1024, 2), + GIBIBYTES("gibi", 1024, 3), + TEBIBYTES("tebi", 1024, 4), + PEBIBYTES("pebi", 1024, 5), + EXBIBYTES("exbi", 1024, 6), + ZEBIBYTES("zebi", 1024, 7), + YOBIBYTES("yobi", 1024, 8); + + final String prefix; + final int powerOf; + final int power; + final long bytes; + + MemoryUnit(String prefix, int powerOf, int power) { + this.prefix = prefix; + this.powerOf = powerOf; + this.power = power; + int i = power; + long bytes = 1; + while (i > 0) { + bytes *= powerOf; + --i; + } this.bytes = bytes; } + + private static Map makeUnitsMap() { + Map map = new HashMap(); + for (MemoryUnit unit : MemoryUnit.values()) { + map.put(unit.prefix + "byte", unit); + map.put(unit.prefix + "bytes", unit); + if (unit.prefix.length() == 0) { + map.put("b", unit); + map.put("B", unit); + map.put("", unit); // no unit specified means bytes + } else { + String first = unit.prefix.substring(0, 1); + String firstUpper = first.toUpperCase(); + if (unit.powerOf == 1024) { + map.put(first, unit); // 512m + map.put(firstUpper, unit); // 512M + map.put(firstUpper + "i", unit); // 512Mi + map.put(firstUpper + "iB", unit); // 512MiB + } else if (unit.powerOf == 1000) { + if (unit.power == 1) { + map.put(first + "B", unit); // 512kB + } else { + map.put(firstUpper + "B", unit); // 512MB + } + } else { + throw new RuntimeException("broken MemoryUnit enum"); + } + } + } + return map; + } + + private static Map unitsMap = makeUnitsMap(); + + static MemoryUnit parseUnit(String unit) { + return unitsMap.get(unit); + } } /** - * Parses a memory-size string. If no units are specified in the string, it - * is assumed to be in bytes. The returned value is in bytes. The purpose of - * this function is to implement the memory-size-related methods in the - * ConfigObject interface. The units parsed are interpreted as powers of - * two, that is, the convention for memory rather than the convention for - * disk space. + * Parses a size-in-bytes string. If no units are specified in the string, + * it is assumed to be in bytes. The returned value is in bytes. The purpose + * of this function is to implement the size-in-bytes-related methods in the + * Config interface. * * @param input * the string to parse @@ -537,19 +590,12 @@ class SimpleConfig implements Config { * @throws ConfigException * if string is invalid */ - public static long parseMemorySizeInBytes(String input, - ConfigOrigin originForException, String pathForException) { + public static long parseBytes(String input, ConfigOrigin originForException, + String pathForException) { String s = ConfigUtil.unicodeTrim(input); - String unitStringMaybePlural = getUnits(s); - String unitString; - if (unitStringMaybePlural.endsWith("s")) - unitString = unitStringMaybePlural.substring(0, - unitStringMaybePlural.length() - 1); - else - unitString = unitStringMaybePlural; - String unitStringLower = unitString.toLowerCase(); - String numberString = ConfigUtil.unicodeTrim(s.substring(0, s.length() - - unitStringMaybePlural.length())); + String unitString = getUnits(s); + String numberString = ConfigUtil.unicodeTrim(s.substring(0, + s.length() - unitString.length())); // this would be caught later anyway, but the error message // is more helpful if we check it here. @@ -558,40 +604,197 @@ class SimpleConfig implements Config { pathForException, "No number in size-in-bytes value '" + input + "'"); - MemoryUnit units = null; + MemoryUnit units = MemoryUnit.parseUnit(unitString); - // the short abbreviations are case-insensitive but you can't write the - // long form words in all caps. - if (unitString.equals("") || unitStringLower.equals("b") - || unitString.equals("byte")) { - units = MemoryUnit.BYTES; - } else if (unitStringLower.equals("k") || unitString.equals("kilobyte")) { - units = MemoryUnit.KILOBYTES; - } else if (unitStringLower.equals("m") || unitString.equals("megabyte")) { - units = MemoryUnit.MEGABYTES; - } else if (unitStringLower.equals("g") || unitString.equals("gigabyte")) { - units = MemoryUnit.GIGABYTES; - } else if (unitStringLower.equals("t") || unitString.equals("terabyte")) { - units = MemoryUnit.TERABYTES; - } else { - throw new ConfigException.BadValue(originForException, - pathForException, "Could not parse size unit '" - + unitStringMaybePlural + "' (try b, k, m, g, t)"); + if (units == null) { + throw new ConfigException.BadValue(originForException, pathForException, + "Could not parse size-in-bytes unit '" + unitString + + "' (try k, K, kB, KiB, kilobytes, kibibytes)"); } try { // if the string is purely digits, parse as an integer to avoid - // possible precision loss; - // otherwise as a double. + // possible precision loss; otherwise as a double. if (numberString.matches("[0-9]+")) { return Long.parseLong(numberString) * units.bytes; } else { return (long) (Double.parseDouble(numberString) * units.bytes); } } catch (NumberFormatException e) { - throw new ConfigException.BadValue(originForException, - pathForException, "Could not parse memory size number '" - + numberString + "'"); + throw new ConfigException.BadValue(originForException, pathForException, + "Could not parse size-in-bytes number '" + numberString + "'"); + } + } + + private AbstractConfigValue peekPath(Path path) { + return root().peekPath(path); + } + + private static void addProblem(List accumulator, Path path, + ConfigOrigin origin, String problem) { + accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem)); + } + + private static String getDesc(ConfigValue refValue) { + if (refValue instanceof AbstractConfigObject) { + AbstractConfigObject obj = (AbstractConfigObject) refValue; + if (obj.isEmpty()) + return "object"; + else + return "object with keys " + obj.keySet(); + } else if (refValue instanceof SimpleConfigList) { + return "list"; + } else { + return refValue.valueType().name().toLowerCase(); + } + } + + private static void addMissing(List accumulator, + ConfigValue refValue, Path path, ConfigOrigin origin) { + addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: " + + getDesc(refValue)); + } + + private static void addWrongType(List accumulator, + ConfigValue refValue, AbstractConfigValue actual, Path path) { + addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render() + + "', expecting: " + getDesc(refValue) + " but got: " + + getDesc(actual)); + } + + private static boolean couldBeNull(AbstractConfigValue v) { + return DefaultTransformer.transform(v, ConfigValueType.NULL) + .valueType() == ConfigValueType.NULL; + } + + private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) { + if (couldBeNull((AbstractConfigValue) reference) || couldBeNull(value)) { + // we allow any setting to be null + return true; + } else if (reference instanceof AbstractConfigObject) { + if (value instanceof AbstractConfigObject) { + return true; + } else { + return false; + } + } else if (reference instanceof SimpleConfigList) { + if (value instanceof SimpleConfigList) { + return true; + } else { + return false; + } + } else if (reference instanceof ConfigString) { + // assume a string could be gotten as any non-collection type; + // allows things like getMilliseconds including domain-specific + // interpretations of strings + return true; + } else if (value instanceof ConfigString) { + // assume a string could be gotten as any non-collection type + return true; + } else { + if (reference.valueType() == value.valueType()) { + return true; + } else { + return false; + } + } + } + + // path is null if we're at the root + private static void checkValidObject(Path path, AbstractConfigObject reference, + AbstractConfigObject value, + List accumulator) { + for (Map.Entry entry : reference.entrySet()) { + String key = entry.getKey(); + + Path childPath; + if (path != null) + childPath = Path.newKey(key).prepend(path); + else + childPath = Path.newKey(key); + + AbstractConfigValue v = value.get(key); + if (v == null) { + addMissing(accumulator, entry.getValue(), childPath, value.origin()); + } else { + checkValid(childPath, entry.getValue(), v, accumulator); + } + } + } + + private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value, + List accumulator) { + // Unmergeable is supposed to be impossible to encounter in here + // because we check for resolve status up front. + + if (haveCompatibleTypes(reference, value)) { + if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) { + checkValidObject(path, (AbstractConfigObject) reference, + (AbstractConfigObject) value, accumulator); + } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) { + SimpleConfigList listRef = (SimpleConfigList) reference; + SimpleConfigList listValue = (SimpleConfigList) value; + if (listRef.isEmpty() || listValue.isEmpty()) { + // can't verify type, leave alone + } else { + AbstractConfigValue refElement = listRef.get(0); + for (ConfigValue elem : listValue) { + AbstractConfigValue e = (AbstractConfigValue) elem; + if (!haveCompatibleTypes(refElement, e)) { + addProblem(accumulator, path, e.origin(), "List at '" + path.render() + + "' contains wrong value type, expecting list of " + + getDesc(refElement) + " but got element of type " + + getDesc(e)); + // don't add a problem for every last array element + break; + } + } + } + } + } else { + addWrongType(accumulator, reference, value, path); + } + } + + @Override + public void checkValid(Config reference, String... restrictToPaths) { + SimpleConfig ref = (SimpleConfig) reference; + + // unresolved reference config is a bug in the caller of checkValid + if (ref.root().resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.BugOrBroken( + "do not call checkValid() with an unresolved reference config, call Config.resolve()"); + + // unresolved config under validation is probably a bug in something, + // but our whole goal here is to check for bugs in this config, so + // BugOrBroken is not the appropriate exception. + if (root().resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.NotResolved( + "config has unresolved substitutions; must call Config.resolve()"); + + // Now we know that both reference and this config are resolved + + List problems = new ArrayList(); + + if (restrictToPaths.length == 0) { + checkValidObject(null, ref.root(), root(), problems); + } else { + for (String p : restrictToPaths) { + Path path = Path.newPath(p); + AbstractConfigValue refValue = ref.peekPath(path); + if (refValue != null) { + AbstractConfigValue child = peekPath(path); + if (child != null) { + checkValid(path, refValue, child, problems); + } else { + addMissing(problems, refValue, path, origin()); + } + } + } + } + + if (!problems.isEmpty()) { + throw new ConfigException.ValidationFailed(problems); } } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigList.java index 9e610858c9..6703540040 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -9,7 +9,6 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigResolveOptions; @@ -68,8 +67,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { } // once the new list is created, all elements - // have to go in it. - if (changed != null) { + // have to go in it. if modifyChild returned + // null, we drop that element. + if (changed != null && modified != null) { changed.add(modified); } @@ -77,9 +77,6 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { } if (changed != null) { - if (changed.size() != value.size()) - throw new ConfigException.BugOrBroken( - "substituted list's size doesn't match"); return new SimpleConfigList(origin(), changed, newResolveStatus); } else { return this; @@ -135,18 +132,34 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { } @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(valueType().name()); - sb.append("("); - for (ConfigValue e : value) { - sb.append(e.toString()); - sb.append(","); + protected void render(StringBuilder sb, int indent, boolean formatted) { + if (value.isEmpty()) { + sb.append("[]"); + } else { + sb.append("["); + if (formatted) + sb.append('\n'); + for (AbstractConfigValue v : value) { + if (formatted) { + indent(sb, indent + 1); + sb.append("# "); + sb.append(v.origin().description()); + sb.append("\n"); + indent(sb, indent + 1); + } + v.render(sb, indent + 1, formatted); + sb.append(","); + if (formatted) + sb.append('\n'); + } + sb.setLength(sb.length() - 1); // chop or newline + if (formatted) { + sb.setLength(sb.length() - 1); // also chop comma + sb.append('\n'); + indent(sb, indent); + } + sb.append("]"); } - if (!value.isEmpty()) - sb.setLength(sb.length() - 1); // chop comma - sb.append(")"); - return sb.toString(); } @Override @@ -160,7 +173,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList { } @Override - public ConfigValue get(int index) { + public AbstractConfigValue get(int index) { return value.get(index); } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 88b2b9090f..0c855ba879 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -112,8 +112,8 @@ final class SimpleConfigObject extends AbstractConfigObject { } final private static String EMPTY_NAME = "empty config"; - final private static SimpleConfigObject emptyInstance = empty(new SimpleConfigOrigin( - EMPTY_NAME)); + final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin + .newSimple(EMPTY_NAME)); final static SimpleConfigObject empty() { return emptyInstance; @@ -128,7 +128,7 @@ final class SimpleConfigObject extends AbstractConfigObject { } final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { - return new SimpleConfigObject(new SimpleConfigOrigin( + return new SimpleConfigObject(SimpleConfigOrigin.newSimple( baseOrigin.description() + " (not found)"), Collections. emptyMap()); } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java index 6e37756638..1ae914c0e4 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java @@ -3,26 +3,101 @@ */ package com.typesafe.config.impl; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +// it would be cleaner to have a class hierarchy for various origin types, +// but was hoping this would be enough simpler to be a little messy. eh. final class SimpleConfigOrigin implements ConfigOrigin { - final private String description; + final private int lineNumber; + final private int endLineNumber; + final private OriginType originType; + final private String urlOrNull; - SimpleConfigOrigin(String description) { + protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, + OriginType originType, + String urlOrNull) { this.description = description; + this.lineNumber = lineNumber; + this.endLineNumber = endLineNumber; + this.originType = originType; + this.urlOrNull = urlOrNull; + } + + static SimpleConfigOrigin newSimple(String description) { + return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null); + } + + static SimpleConfigOrigin newFile(String filename) { + String url; + try { + url = (new File(filename)).toURI().toURL().toExternalForm(); + } catch (MalformedURLException e) { + url = null; + } + return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url); + } + + static SimpleConfigOrigin newURL(URL url) { + String u = url.toExternalForm(); + return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u); + } + + static SimpleConfigOrigin newResource(String resource, URL url) { + return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE, + url != null ? url.toExternalForm() : null); + } + + static SimpleConfigOrigin newResource(String resource) { + return newResource(resource, null); + } + + SimpleConfigOrigin setLineNumber(int lineNumber) { + if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { + return this; + } else { + return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, + this.originType, this.urlOrNull); + } + } + + SimpleConfigOrigin addURL(URL url) { + return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, + url != null ? url.toExternalForm() : null); } @Override public String description() { - return description; + // not putting the URL in here for files and resources, because people + // parsing "file: line" syntax would hit the ":" in the URL. + if (lineNumber < 0) { + return description; + } else if (endLineNumber == lineNumber) { + return description + ": " + lineNumber; + } else { + return description + ": " + lineNumber + "-" + endLineNumber; + } } @Override public boolean equals(Object other) { if (other instanceof SimpleConfigOrigin) { - return this.description - .equals(((SimpleConfigOrigin) other).description); + SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other; + + return this.description.equals(otherOrigin.description) + && this.lineNumber == otherOrigin.lineNumber + && this.endLineNumber == otherOrigin.endLineNumber + && this.originType == otherOrigin.originType + && ConfigUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull); } else { return false; } @@ -30,11 +105,201 @@ final class SimpleConfigOrigin implements ConfigOrigin { @Override public int hashCode() { - return description.hashCode(); + int h = 41 * (41 + description.hashCode()); + h = 41 * (h + lineNumber); + h = 41 * (h + endLineNumber); + h = 41 * (h + originType.hashCode()); + if (urlOrNull != null) + h = 41 * (h + urlOrNull.hashCode()); + return h; } @Override public String toString() { - return "ConfigOrigin(" + description + ")"; + // the url is only really useful on top of description for resources + if (originType == OriginType.RESOURCE && urlOrNull != null) { + return "ConfigOrigin(" + description + "," + urlOrNull + ")"; + } else { + return "ConfigOrigin(" + description + ")"; + } + } + + @Override + public String filename() { + if (originType == OriginType.FILE) { + return description; + } else if (urlOrNull != null) { + URL url; + try { + url = new URL(urlOrNull); + } catch (MalformedURLException e) { + return null; + } + if (url.getProtocol().equals("file")) { + return url.getFile(); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public URL url() { + if (urlOrNull == null) { + return null; + } else { + try { + return new URL(urlOrNull); + } catch (MalformedURLException e) { + return null; + } + } + } + + @Override + public String resource() { + if (originType == OriginType.RESOURCE) { + return description; + } else { + return null; + } + } + + @Override + public int lineNumber() { + return lineNumber; + } + + static final String MERGE_OF_PREFIX = "merge of "; + + private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) { + String mergedDesc; + int mergedStartLine; + int mergedEndLine; + + OriginType mergedType; + if (a.originType == b.originType) { + mergedType = a.originType; + } else { + mergedType = OriginType.GENERIC; + } + + // first use the "description" field which has no line numbers + // cluttering it. + String aDesc = a.description; + String bDesc = b.description; + if (aDesc.startsWith(MERGE_OF_PREFIX)) + aDesc = aDesc.substring(MERGE_OF_PREFIX.length()); + if (bDesc.startsWith(MERGE_OF_PREFIX)) + bDesc = bDesc.substring(MERGE_OF_PREFIX.length()); + + if (aDesc.equals(bDesc)) { + mergedDesc = aDesc; + + if (a.lineNumber < 0) + mergedStartLine = b.lineNumber; + else if (b.lineNumber < 0) + mergedStartLine = a.lineNumber; + else + mergedStartLine = Math.min(a.lineNumber, b.lineNumber); + + mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber); + } else { + // this whole merge song-and-dance was intended to avoid this case + // whenever possible, but we've lost. Now we have to lose some + // structured information and cram into a string. + + // description() method includes line numbers, so use it instead + // of description field. + String aFull = a.description(); + String bFull = b.description(); + if (aFull.startsWith(MERGE_OF_PREFIX)) + aFull = aFull.substring(MERGE_OF_PREFIX.length()); + if (bFull.startsWith(MERGE_OF_PREFIX)) + bFull = bFull.substring(MERGE_OF_PREFIX.length()); + + mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull; + + mergedStartLine = -1; + mergedEndLine = -1; + } + + String mergedURL; + if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) { + mergedURL = a.urlOrNull; + } else { + mergedURL = null; + } + + return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, + mergedURL); + } + + private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { + int count = 0; + + if (a.originType == b.originType) + count += 1; + + if (a.description.equals(b.description)) { + count += 1; + + // only count these if the description field (which is the file + // or resource name) also matches. + if (a.lineNumber == b.lineNumber) + count += 1; + if (a.endLineNumber == b.endLineNumber) + count += 1; + if (ConfigUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) + count += 1; + } + + return count; + } + + // this picks the best pair to merge, because the pair has the most in + // common. we want to merge two lines in the same file rather than something + // else with one of the lines; because two lines in the same file can be + // better consolidated. + private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, + SimpleConfigOrigin c) { + if (similarity(a, b) >= similarity(b, c)) { + return mergeTwo(mergeTwo(a, b), c); + } else { + return mergeTwo(a, mergeTwo(b, c)); + } + } + + static ConfigOrigin mergeOrigins(Collection stack) { + if (stack.isEmpty()) { + throw new ConfigException.BugOrBroken("can't merge empty list of origins"); + } else if (stack.size() == 1) { + return stack.iterator().next(); + } else if (stack.size() == 2) { + Iterator i = stack.iterator(); + return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next()); + } else { + List remaining = new ArrayList(); + for (ConfigOrigin o : stack) { + remaining.add((SimpleConfigOrigin) o); + } + while (remaining.size() > 2) { + SimpleConfigOrigin c = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + SimpleConfigOrigin b = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + SimpleConfigOrigin a = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + + SimpleConfigOrigin merged = mergeThree(a, b, c); + + remaining.add(merged); + } + + // should be down to either 1 or 2 + return mergeOrigins(remaining); + } } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionExpression.java b/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionExpression.java new file mode 100644 index 0000000000..abc1c74a39 --- /dev/null +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionExpression.java @@ -0,0 +1,46 @@ +package com.typesafe.config.impl; + +final class SubstitutionExpression { + + final private Path path; + final private boolean optional; + + SubstitutionExpression(Path path, boolean optional) { + this.path = path; + this.optional = optional; + } + + Path path() { + return path; + } + + boolean optional() { + return optional; + } + + SubstitutionExpression changePath(Path newPath) { + return new SubstitutionExpression(newPath, optional); + } + + @Override + public String toString() { + return "${" + (optional ? "?" : "") + path.render() + "}"; + } + + @Override + public boolean equals(Object other) { + if (other instanceof SubstitutionExpression) { + SubstitutionExpression otherExp = (SubstitutionExpression) other; + return otherExp.path.equals(this.path) && otherExp.optional == this.optional; + } else { + return false; + } + } + + @Override + public int hashCode() { + int h = 41 * (41 + path.hashCode()); + h = 41 * (h + (optional ? 1 : 0)); + return h; + } +} diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java b/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java index 7f77570d02..65a2b30900 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SubstitutionResolver.java @@ -16,6 +16,8 @@ import com.typesafe.config.ConfigResolveOptions; */ final class SubstitutionResolver { final private AbstractConfigObject root; + // note that we can resolve things to undefined (represented as Java null, + // rather than ConfigNull) so this map can have null values. final private Map memos; SubstitutionResolver(AbstractConfigObject root) { @@ -31,9 +33,11 @@ final class SubstitutionResolver { } else { AbstractConfigValue resolved = original.resolveSubstitutions(this, depth, options); - if (resolved.resolveStatus() != ResolveStatus.RESOLVED) - throw new ConfigException.BugOrBroken( - "resolveSubstitutions() did not give us a resolved object"); + if (resolved != null) { + if (resolved.resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.BugOrBroken( + "resolveSubstitutions() did not give us a resolved object"); + } memos.put(original, resolved); return resolved; } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Tokenizer.java b/akka-actor/src/main/java/com/typesafe/config/impl/Tokenizer.java index 147917bf40..4965b2a619 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Tokenizer.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Tokenizer.java @@ -219,8 +219,7 @@ final class Tokenizer { private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, int lineNumber) { - return new SimpleConfigOrigin(baseOrigin.description() + ": line " - + lineNumber); + return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber); } // chars JSON allows a number to start with @@ -228,7 +227,7 @@ final class Tokenizer { // chars JSON allows to be part of a number static final String numberChars = "0123456789eE+-."; // chars that stop an unquoted string - static final String notInUnquotedText = "$\"{}[]:=,\\+#"; + static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\"; // The rules here are intended to maximize convenience while // avoiding confusion with real valid JSON. Basically anything @@ -404,6 +403,14 @@ final class Tokenizer { throw parseError("'$' not followed by {"); } + boolean optional = false; + c = nextCharSkippingComments(); + if (c == '?') { + optional = true; + } else { + putBack(c); + } + WhitespaceSaver saver = new WhitespaceSaver(); List expression = new ArrayList(); @@ -428,7 +435,7 @@ final class Tokenizer { } } while (true); - return Tokens.newSubstitution(origin, expression); + return Tokens.newSubstitution(origin, optional, expression); } private Token pullNextToken(WhitespaceSaver saver) { diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Tokens.java b/akka-actor/src/main/java/com/typesafe/config/impl/Tokens.java index 9ec73a819c..f36527d738 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Tokens.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Tokens.java @@ -125,11 +125,13 @@ final class Tokens { // This is not a Value, because it requires special processing static private class Substitution extends Token { final private ConfigOrigin origin; + final private boolean optional; final private List value; - Substitution(ConfigOrigin origin, List expression) { + Substitution(ConfigOrigin origin, boolean optional, List expression) { super(TokenType.SUBSTITUTION); this.origin = origin; + this.optional = optional; this.value = expression; } @@ -137,6 +139,10 @@ final class Tokens { return origin; } + boolean optional() { + return optional; + } + List value() { return value; } @@ -237,6 +243,15 @@ final class Tokens { } } + static boolean getSubstitutionOptional(Token token) { + if (token instanceof Substitution) { + return ((Substitution) token).optional(); + } else { + throw new ConfigException.BugOrBroken("tried to get substitution optionality from " + + token); + } + } + final static Token START = new Token(TokenType.START); final static Token END = new Token(TokenType.END); final static Token COMMA = new Token(TokenType.COMMA); @@ -255,8 +270,8 @@ final class Tokens { return new UnquotedText(origin, s); } - static Token newSubstitution(ConfigOrigin origin, List expression) { - return new Substitution(origin, expression); + static Token newSubstitution(ConfigOrigin origin, boolean optional, List expression) { + return new Substitution(origin, optional, expression); } static Token newValue(AbstractConfigValue value) { diff --git a/akka-actor/src/main/java/com/typesafe/config/package.html b/akka-actor/src/main/java/com/typesafe/config/package.html new file mode 100644 index 0000000000..1e1c78bfb2 --- /dev/null +++ b/akka-actor/src/main/java/com/typesafe/config/package.html @@ -0,0 +1,42 @@ + + + + + + + + +

+An API for loading and using configuration files, see the project site +for more information. +

+ +

+Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use +it with methods in the {@link com.typesafe.config.Config} interface. +

+ +

+An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place +its configuration in "application.conf" on the classpath. +If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()} +there's no need to pass a configuration to your libraries +and frameworks, as long as they all default to this same default, which they should. +

+ +

+A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a +{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided, +call {@link com.typesafe.config.ConfigFactory#load()} +to get the default one. Typically a library might offer two constructors, one with a Config parameter +and one which uses {@link com.typesafe.config.ConfigFactory#load()}. +

+ +

+You can find an example app and library on GitHub. +

+ + + diff --git a/akka-actor/src/main/resources/akka-serialization-reference.conf b/akka-actor/src/main/resources/akka-serialization-reference.conf deleted file mode 100644 index fb6f134b93..0000000000 --- a/akka-actor/src/main/resources/akka-serialization-reference.conf +++ /dev/null @@ -1,32 +0,0 @@ -############################################ -# Akka Serialization Reference Config File # -############################################ - -# This the reference config file has all the default settings. -# Make your edits/overrides in your akka.conf. - -akka { - actor { - - # Entries for pluggable serializers and their bindings. If a binding for a specific class is not found, - # then the default serializer (Java serialization) is used. - # - serializers { - # java = "akka.serialization.JavaSerializer" - # proto = "akka.testing.ProtobufSerializer" - # sjson = "akka.testing.SJSONSerializer" - default = "akka.serialization.JavaSerializer" - } - - # serialization-bindings { - # java = ["akka.serialization.SerializeSpec$Address", - # "akka.serialization.MyJavaSerializableActor", - # "akka.serialization.MyStatelessActorWithMessagesInMailbox", - # "akka.serialization.MyActorWithProtobufMessagesInMailbox"] - # sjson = ["akka.serialization.SerializeSpec$Person"] - # proto = ["com.google.protobuf.Message", - # "akka.actor.ProtobufProtocol$MyMessage"] - # } - } - -} diff --git a/akka-actor/src/main/resources/akka-actor-reference.conf b/akka-actor/src/main/resources/reference.conf similarity index 91% rename from akka-actor/src/main/resources/akka-actor-reference.conf rename to akka-actor/src/main/resources/reference.conf index fbfd7d7e9c..8fedbecbdc 100644 --- a/akka-actor/src/main/resources/akka-actor-reference.conf +++ b/akka-actor/src/main/resources/reference.conf @@ -16,7 +16,7 @@ akka { loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG # this level is used by the configured loggers (see "event-handlers") as soon # as they have been started; before that, see "stdout-loglevel" - stdout-loglevel = "INFO" # Loglevel for the very basic logger activated during AkkaApplication startup + stdout-loglevel = "WARNING" # Loglevel for the very basic logger activated during AkkaApplication startup # FIXME: Is there any sensible reason why we have 2 different log levels? logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started. @@ -125,6 +125,26 @@ akka { fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers event-stream = off # enable DEBUG logging of subscription changes on the eventStream } + + # Entries for pluggable serializers and their bindings. If a binding for a specific class is not found, + # then the default serializer (Java serialization) is used. + # + serializers { + # java = "akka.serialization.JavaSerializer" + # proto = "akka.testing.ProtobufSerializer" + # sjson = "akka.testing.SJSONSerializer" + default = "akka.serialization.JavaSerializer" + } + + # serialization-bindings { + # java = ["akka.serialization.SerializeSpec$Address", + # "akka.serialization.MyJavaSerializableActor", + # "akka.serialization.MyStatelessActorWithMessagesInMailbox", + # "akka.serialization.MyActorWithProtobufMessagesInMailbox"] + # sjson = ["akka.serialization.SerializeSpec$Person"] + # proto = ["com.google.protobuf.Message", + # "akka.actor.ProtobufProtocol$MyMessage"] + # } } @@ -142,6 +162,5 @@ akka { tickDuration = 100ms ticksPerWheel = 512 } - - + } diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 4f02473b4f..249fbd343a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -13,9 +13,10 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.NANOSECONDS import java.io.File import com.typesafe.config.Config -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigResolveOptions +import com.typesafe.config.ConfigException import java.lang.reflect.InvocationTargetException import akka.util.{ Helpers, Duration, ReflectiveAccess } import java.util.concurrent.atomic.AtomicLong @@ -43,17 +44,32 @@ object ActorSystem { def create(name: String, config: Config): ActorSystem = apply(name, config) def apply(name: String, config: Config): ActorSystem = new ActorSystemImpl(name, config).start() + /** + * Uses the standard default Config from ConfigFactory.load(), since none is provided. + */ def create(name: String): ActorSystem = apply(name) - def apply(name: String): ActorSystem = apply(name, DefaultConfigurationLoader.defaultConfig) + /** + * Uses the standard default Config from ConfigFactory.load(), since none is provided. + */ + def apply(name: String): ActorSystem = apply(name, ConfigFactory.load()) def create(): ActorSystem = apply() def apply(): ActorSystem = apply("default") class Settings(cfg: Config) { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-actor-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-actor").withFallback(cfg).withFallback(referenceConfig).resolve() + + // Verify that the Config is sane and has our reference config. + val config: Config = + try { + cfg.checkValid(ConfigFactory.defaultReference, "akka") + cfg + } catch { + case e: ConfigException ⇒ + // try again with added defaultReference + val cfg2 = cfg.withFallback(ConfigFactory.defaultReference) + cfg2.checkValid(ConfigFactory.defaultReference, "akka") + cfg2 + } import scala.collection.JavaConverters._ import config._ @@ -102,9 +118,13 @@ object ActorSystem { } - object DefaultConfigurationLoader { + // TODO move to migration kit + object OldConfigurationLoader { - val defaultConfig: Config = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig + val defaultConfig: Config = { + val cfg = fromProperties orElse fromClasspath orElse fromHome getOrElse emptyConfig + cfg.withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults) + } // file extensions (.conf, .json, .properties), are handled by parseFileAnySyntax val defaultLocation: String = (systemMode orElse envMode).map("akka." + _).getOrElse("akka") @@ -130,7 +150,7 @@ object ActorSystem { private def fromClasspath = try { Option(ConfigFactory.systemProperties.withFallback( - ConfigFactory.parseResourceAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions))) + ConfigFactory.parseResourcesAnySyntax(ActorSystem.getClass, "/" + defaultLocation, configParseOptions))) } catch { case _ ⇒ None } private def fromHome = try { @@ -273,7 +293,7 @@ abstract class ActorSystem extends ActorRefFactory { def hasExtension(ext: ExtensionId[_ <: Extension]): Boolean } -class ActorSystemImpl(val name: String, val applicationConfig: Config) extends ActorSystem { +class ActorSystemImpl(val name: String, applicationConfig: Config) extends ActorSystem { import ActorSystem._ diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index ec1d8dfc4c..da666c2522 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -88,7 +88,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, } import scala.collection.JavaConverters._ - settings.config.getConfig("akka.actor.deployment").toObject.keySet.asScala + settings.config.getConfig("akka.actor.deployment").toValue.keySet.asScala .filterNot("default" ==) .map(path ⇒ pathSubstring(path)) .toSet.toList // toSet to force uniqueness diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index 7232375fa8..f7ca6aae9b 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -7,8 +7,7 @@ package akka.serialization import akka.AkkaException import akka.util.ReflectiveAccess import scala.util.DynamicVariable -import com.typesafe.config.{ ConfigRoot, ConfigParseOptions, ConfigFactory, Config } -import com.typesafe.config.Config._ +import com.typesafe.config.Config import akka.config.ConfigurationException import akka.actor.{ Extension, ActorSystem, ActorSystemImpl } @@ -19,11 +18,7 @@ object Serialization { // TODO ensure that these are always set (i.e. withValue()) when doing deserialization val currentSystem = new DynamicVariable[ActorSystemImpl](null) - class Settings(cfg: Config) { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-serialization-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-serialization").withFallback(cfg).withFallback(referenceConfig).resolve() + class Settings(val config: Config) { import scala.collection.JavaConverters._ import config._ @@ -37,7 +32,7 @@ object Serialization { hasPath(configPath) match { case false ⇒ Map() case true ⇒ - val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toObject.unwrapped.asScala.toMap.map { + val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toValue.unwrapped.asScala.toMap.map { case (k: String, v: java.util.Collection[_]) ⇒ (k -> v.asScala.toSeq.asInstanceOf[Seq[String]]) case invalid ⇒ throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid)) } @@ -47,7 +42,7 @@ object Serialization { } private def toStringMap(mapConfig: Config): Map[String, String] = - mapConfig.toObject.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } + mapConfig.toValue.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } } } @@ -58,7 +53,7 @@ object Serialization { class Serialization(val system: ActorSystemImpl) extends Extension { import Serialization._ - val settings = new Settings(system.applicationConfig) + val settings = new Settings(system.settings.config) //TODO document me def serialize(o: AnyRef): Either[Exception, Array[Byte]] = diff --git a/akka-docs/general/code/ConfigDocSpec.scala b/akka-docs/general/code/ConfigDocSpec.scala index b7b106b94f..4b3de65e65 100644 --- a/akka-docs/general/code/ConfigDocSpec.scala +++ b/akka-docs/general/code/ConfigDocSpec.scala @@ -6,7 +6,6 @@ import org.scalatest.matchers.MustMatchers //#imports import akka.actor.ActorSystem import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions //#imports @@ -21,7 +20,7 @@ class ConfigDocSpec extends WordSpec { nr-of-instances = 3 } } - """, ConfigParseOptions.defaults) + """) val system = ActorSystem("MySystem", ConfigFactory.systemProperties.withFallback(customConf)) //#custom-config diff --git a/akka-docs/general/configuration.rst b/akka-docs/general/configuration.rst index cffd15b4dd..58dfbd5edd 100644 --- a/akka-docs/general/configuration.rst +++ b/akka-docs/general/configuration.rst @@ -16,6 +16,8 @@ configuration files that you see below. You can specify your own configuration f property in the reference config. You only have to define the properties that differ from the default configuration. +FIXME: These default locations has changed + The location of the config file to use can be specified in various ways: * Define the ``-Dakka.config=...`` system property parameter with a file path to configuration file. @@ -44,47 +46,42 @@ Each Akka module has a reference configuration file with the default values. *akka-actor:* -.. literalinclude:: ../../akka-actor/src/main/resources/akka-actor-reference.conf +.. literalinclude:: ../../akka-actor/src/main/resources/reference.conf :language: none *akka-remote:* -.. literalinclude:: ../../akka-remote/src/main/resources/akka-remote-reference.conf +.. literalinclude:: ../../akka-remote/src/main/resources/reference.conf :language: none -*akka-serialization:* - -.. literalinclude:: ../../akka-actor/src/main/resources/akka-serialization-reference.conf - :language: none - *akka-testkit:* -.. literalinclude:: ../../akka-testkit/src/main/resources/akka-testkit-reference.conf +.. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf :language: none *akka-beanstalk-mailbox:* -.. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/akka-beanstalk-mailbox-reference.conf +.. literalinclude:: ../../akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/reference.conf :language: none *akka-file-mailbox:* -.. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf +.. literalinclude:: ../../akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf :language: none *akka-mongo-mailbox:* -.. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/akka-mongo-mailbox-reference.conf +.. literalinclude:: ../../akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/reference.conf :language: none *akka-redis-mailbox:* -.. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/akka-redis-mailbox-reference.conf +.. literalinclude:: ../../akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/reference.conf :language: none *akka-zookeeper-mailbox:* -.. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/akka-zookeeper-mailbox-reference.conf +.. literalinclude:: ../../akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/reference.conf :language: none A custom ``akka.conf`` might look like this:: @@ -121,7 +118,6 @@ A custom ``akka.conf`` might look like this:: } } -.. _-Dakka.mode: Config file format ------------------ @@ -129,9 +125,13 @@ Config file format The configuration file syntax is described in the `HOCON `_ specification. Note that it supports three formats; conf, json, and properties. +.. _-Dakka.mode: + Specifying files for different modes ------------------------------------ +FIXME: mode doesn't exist, or will it? + You can use different configuration files for different purposes by specifying a mode option, either as ``-Dakka.mode=...`` system property or as ``AKKA_MODE=...`` environment variable. For example using DEBUG log level when in development mode. Run with ``-Dakka.mode=dev`` and place the following ``akka.dev.conf`` in the root of @@ -152,6 +152,8 @@ The mode option is not used when specifying the configuration file with ``-Dakka Including files --------------- +FIXME: The include syntax has changed + Sometimes it can be useful to include another configuration file, for example if you have one ``akka.conf`` with all environment independent settings and then override some settings for specific modes. @@ -165,14 +167,14 @@ akka.dev.conf: loglevel = "DEBUG" } -.. _-Dakka.output.config.source: +.. _-Dakka.logConfigOnStart: -Showing Configuration Source ----------------------------- +Logging of Configuration +------------------------ -If the system property ``akka.output.config.source`` is set to anything but -null, then the source from which Akka reads its configuration is printed to the -console during application startup. +If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the +complete configuration at INFO level when the actor system is started. This is useful +when you are uncertain of what configuration is used. Summary of System Properties ---------------------------- @@ -180,4 +182,3 @@ Summary of System Properties * :ref:`akka.home <-Dakka.home>` (``AKKA_HOME``): where Akka searches for configuration * :ref:`akka.config <-Dakka.config>`: explicit configuration file location * :ref:`akka.mode <-Dakka.mode>` (``AKKA_MODE``): modify configuration file name for multiple profiles -* :ref:`akka.output.config.source <-Dakka.output.config.source>`: whether to print configuration source to console diff --git a/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/akka-beanstalk-mailbox-reference.conf b/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/reference.conf similarity index 100% rename from akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/akka-beanstalk-mailbox-reference.conf rename to akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/resources/reference.conf diff --git a/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailboxExtension.scala b/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailboxExtension.scala index 5f6fd40708..eaf8833c86 100644 --- a/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailboxExtension.scala +++ b/akka-durable-mailboxes/akka-beanstalk-mailbox/src/main/scala/akka/actor/mailbox/BeanstalkBasedMailboxExtension.scala @@ -4,23 +4,16 @@ package akka.actor.mailbox import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import akka.actor._ object BeanstalkBasedMailboxExtension extends ExtensionId[BeanstalkMailboxSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new BeanstalkMailboxSettings(system.settings.config) } -class BeanstalkMailboxSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-beanstalk-mailbox-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-beanstalk-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve() +class BeanstalkMailboxSettings(val config: Config) extends Extension { import config._ diff --git a/akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf b/akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf similarity index 89% rename from akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf rename to akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf index 313b8d85e9..f81f8995f9 100644 --- a/akka-durable-mailboxes/akka-file-mailbox/src/main/resources/akka-file-mailbox-reference.conf +++ b/akka-durable-mailboxes/akka-file-mailbox/src/main/resources/reference.conf @@ -15,8 +15,8 @@ akka { max-items = 2147483647 max-item-size = 2147483647 bytes max-age = 0s - max-journal-size = 16 megabytes - max-memory-size = 128 megabytes + max-journal-size = 16 MiB + max-memory-size = 128 MiB max-journal-overflow = 10 max-journal-size-absolute = 9223372036854775807 bytes discard-old-when-full = on diff --git a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FileBasedMailboxExtension.scala b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FileBasedMailboxExtension.scala index 1bdf9ae958..fcdd3ac29f 100644 --- a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FileBasedMailboxExtension.scala +++ b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FileBasedMailboxExtension.scala @@ -4,36 +4,29 @@ package akka.actor.mailbox import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import akka.actor._ object FileBasedMailboxExtension extends ExtensionId[FileBasedMailboxSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new FileBasedMailboxSettings(system.settings.config) } -class FileBasedMailboxSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-file-mailbox-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-file-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve() +class FileBasedMailboxSettings(val config: Config) extends Extension { import config._ val QueuePath = getString("akka.actor.mailbox.file-based.directory-path") val MaxItems = getInt("akka.actor.mailbox.file-based.max-items") - val MaxSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-size") - val MaxItemSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-item-size") + val MaxSize = getBytes("akka.actor.mailbox.file-based.max-size") + val MaxItemSize = getBytes("akka.actor.mailbox.file-based.max-item-size") val MaxAge = Duration(getMilliseconds("akka.actor.mailbox.file-based.max-age"), MILLISECONDS) - val MaxJournalSize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size") - val MaxMemorySize = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-memory-size") + val MaxJournalSize = getBytes("akka.actor.mailbox.file-based.max-journal-size") + val MaxMemorySize = getBytes("akka.actor.mailbox.file-based.max-memory-size") val MaxJournalOverflow = getInt("akka.actor.mailbox.file-based.max-journal-overflow") - val MaxJournalSizeAbsolute = getMemorySizeInBytes("akka.actor.mailbox.file-based.max-journal-size-absolute") + val MaxJournalSizeAbsolute = getBytes("akka.actor.mailbox.file-based.max-journal-size-absolute") val DiscardOldWhenFull = getBoolean("akka.actor.mailbox.file-based.discard-old-when-full") val KeepJournal = getBoolean("akka.actor.mailbox.file-based.keep-journal") val SyncJournal = getBoolean("akka.actor.mailbox.file-based.sync-journal") diff --git a/akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/akka-mongo-mailbox-reference.conf b/akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/reference.conf similarity index 100% rename from akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/akka-mongo-mailbox-reference.conf rename to akka-durable-mailboxes/akka-mongo-mailbox/src/main/resources/reference.conf diff --git a/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/MongoBasedMailboxExtension.scala b/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/MongoBasedMailboxExtension.scala index 88eb95438c..3075edf9e9 100644 --- a/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/MongoBasedMailboxExtension.scala +++ b/akka-durable-mailboxes/akka-mongo-mailbox/src/main/scala/akka/actor/mailbox/MongoBasedMailboxExtension.scala @@ -4,23 +4,16 @@ package akka.actor.mailbox import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import akka.actor._ object MongoBasedMailboxExtension extends ExtensionId[MongoBasedMailboxSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new MongoBasedMailboxSettings(system.settings.config) } -class MongoBasedMailboxSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-mongo-mailbox-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-mongo-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve() +class MongoBasedMailboxSettings(val config: Config) extends Extension { import config._ diff --git a/akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/akka-redis-mailbox-reference.conf b/akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/reference.conf similarity index 100% rename from akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/akka-redis-mailbox-reference.conf rename to akka-durable-mailboxes/akka-redis-mailbox/src/main/resources/reference.conf diff --git a/akka-durable-mailboxes/akka-redis-mailbox/src/main/scala/akka/actor/mailbox/RedisBasedMailboxExtension.scala b/akka-durable-mailboxes/akka-redis-mailbox/src/main/scala/akka/actor/mailbox/RedisBasedMailboxExtension.scala index beccf4051f..ef25eead1d 100644 --- a/akka-durable-mailboxes/akka-redis-mailbox/src/main/scala/akka/actor/mailbox/RedisBasedMailboxExtension.scala +++ b/akka-durable-mailboxes/akka-redis-mailbox/src/main/scala/akka/actor/mailbox/RedisBasedMailboxExtension.scala @@ -4,21 +4,14 @@ package akka.actor.mailbox import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.actor._ object RedisBasedMailboxExtension extends ExtensionId[RedisBasedMailboxSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new RedisBasedMailboxSettings(system.settings.config) } -class RedisBasedMailboxSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-redis-mailbox-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-redis-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve() +class RedisBasedMailboxSettings(val config: Config) extends Extension { import config._ diff --git a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/akka-zookeeper-mailbox-reference.conf b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/reference.conf similarity index 100% rename from akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/akka-zookeeper-mailbox-reference.conf rename to akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/resources/reference.conf diff --git a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailboxExtension.scala b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailboxExtension.scala index e2b0ad45f7..2d2e7c1be1 100644 --- a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailboxExtension.scala +++ b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailboxExtension.scala @@ -4,22 +4,15 @@ package akka.actor.mailbox import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import akka.actor._ object ZooKeeperBasedMailboxExtension extends ExtensionId[ZooKeeperBasedMailboxSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new ZooKeeperBasedMailboxSettings(system.settings.config) } -class ZooKeeperBasedMailboxSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-zookeeper-mailbox-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-zookeeper-mailbox").withFallback(cfg).withFallback(referenceConfig).resolve() +class ZooKeeperBasedMailboxSettings(val config: Config) extends Extension { import config._ diff --git a/akka-remote/src/main/resources/akka-remote-reference.conf b/akka-remote/src/main/resources/reference.conf similarity index 96% rename from akka-remote/src/main/resources/akka-remote-reference.conf rename to akka-remote/src/main/resources/reference.conf index 39fcda8cd7..bc262ee258 100644 --- a/akka-remote/src/main/resources/akka-remote-reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -31,7 +31,7 @@ akka { server { hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA) - message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads + message-frame-size = 1 MiB # Increase this if you want to be able to send messages with large payloads connection-timeout = 120s # Timeout duration require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. @@ -46,7 +46,7 @@ akka { } reconnect-delay = 5s read-timeout = 3600s - message-frame-size = 1048576 + message-frame-size = 1 MiB reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for } } diff --git a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala index 53efaba4b4..645ce87f2c 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala @@ -4,9 +4,6 @@ package akka.remote import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import java.net.InetAddress @@ -18,14 +15,10 @@ import scala.collection.JavaConverters._ object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider { def lookup() = this - def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl) = new RemoteExtensionSettings(system.settings.config) } -class RemoteExtensionSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-remote-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-remote").withFallback(cfg).withFallback(referenceConfig).resolve() +class RemoteExtensionSettings(val config: Config) extends Extension { import config._ @@ -57,12 +50,12 @@ class RemoteExtensionSettings(cfg: Config) extends Extension { val ReconnectionTimeWindow = Duration(config.getMilliseconds("akka.remote.client.reconnection-time-window"), MILLISECONDS) val ReadTimeout = Duration(config.getMilliseconds("akka.remote.client.read-timeout"), MILLISECONDS) val ReconnectDelay = Duration(config.getMilliseconds("akka.remote.client.reconnect-delay"), MILLISECONDS) - val MessageFrameSize = config.getInt("akka.remote.client.message-frame-size") + val MessageFrameSize = config.getBytes("akka.remote.client.message-frame-size").toInt } class RemoteServerSettings { import scala.collection.JavaConverters._ - val MessageFrameSize = config.getInt("akka.remote.server.message-frame-size") + val MessageFrameSize = config.getBytes("akka.remote.server.message-frame-size").toInt val SecureCookie: Option[String] = config.getString("akka.remote.secure-cookie") match { case "" ⇒ None case cookie ⇒ Some(cookie) diff --git a/akka-remote/src/multi-jvm/scala/akka/remote/AkkaRemoteSpec.scala b/akka-remote/src/multi-jvm/scala/akka/remote/AkkaRemoteSpec.scala index f41ea0e855..01cc597d49 100644 --- a/akka-remote/src/multi-jvm/scala/akka/remote/AkkaRemoteSpec.scala +++ b/akka-remote/src/multi-jvm/scala/akka/remote/AkkaRemoteSpec.scala @@ -6,8 +6,27 @@ package akka.remote import akka.testkit._ import akka.actor.ActorSystemImpl +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigResolveOptions +import java.io.File -abstract class AkkaRemoteSpec extends AkkaSpec with MultiJvmSync { +object AkkaRemoteSpec { + private def configParseOptions = ConfigParseOptions.defaults.setAllowMissing(false) + + val testConf: Config = { + System.getProperty("akka.config") match { + case null ⇒ AkkaSpec.testConf + case location ⇒ + ConfigFactory.systemProperties + .withFallback(ConfigFactory.parseFileAnySyntax(new File(location), configParseOptions)) + .withFallback(ConfigFactory.defaultReference).resolve(ConfigResolveOptions.defaults) + } + } +} + +abstract class AkkaRemoteSpec extends AkkaSpec(AkkaRemoteSpec.testConf) with MultiJvmSync { /** * Helper function for accessing the underlying remoting. diff --git a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala index f72904fc3f..96e4266105 100644 --- a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala @@ -19,7 +19,7 @@ class RemoteConfigSpec extends AkkaSpec { //akka.remote.server getInt("akka.remote.server.port") must equal(2552) - getInt("akka.remote.server.message-frame-size") must equal(1048576) + getBytes("akka.remote.server.message-frame-size") must equal(1048576L) getMilliseconds("akka.remote.server.connection-timeout") must equal(120 * 1000) getBoolean("akka.remote.server.require-cookie") must equal(false) getBoolean("akka.remote.server.untrusted-mode") must equal(false) diff --git a/akka-stm/src/main/resources/akka-stm-reference.conf b/akka-stm/src/main/resources/reference.conf similarity index 100% rename from akka-stm/src/main/resources/akka-stm-reference.conf rename to akka-stm/src/main/resources/reference.conf diff --git a/akka-stm/src/test/scala/akka/stm/test/ConfigSpec.scala b/akka-stm/src/test/scala/akka/stm/test/ConfigSpec.scala index 19a4450cf3..3c455bf702 100644 --- a/akka-stm/src/test/scala/akka/stm/test/ConfigSpec.scala +++ b/akka-stm/src/test/scala/akka/stm/test/ConfigSpec.scala @@ -10,11 +10,10 @@ import org.scalatest.junit.JUnitRunner import org.scalatest.matchers.MustMatchers import akka.actor.ActorSystem import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions import akka.testkit.AkkaSpec @RunWith(classOf[JUnitRunner]) -class ConfigSpec extends AkkaSpec(ConfigFactory.parseResource(classOf[ConfigSpec], "/akka-stm-reference.conf", ConfigParseOptions.defaults)) { +class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) { "The default configuration file (i.e. akka-stm-reference.conf)" should { "contain all configuration properties for akka-stm that are used in code with their correct defaults" in { diff --git a/akka-testkit/src/main/resources/akka-testkit-reference.conf b/akka-testkit/src/main/resources/reference.conf similarity index 100% rename from akka-testkit/src/main/resources/akka-testkit-reference.conf rename to akka-testkit/src/main/resources/reference.conf diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKitExtension.scala b/akka-testkit/src/main/scala/akka/testkit/TestKitExtension.scala index 5af1bde50a..103241636a 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKitExtension.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKitExtension.scala @@ -4,22 +4,15 @@ package akka.testkit import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import com.typesafe.config.ConfigRoot import akka.util.Duration import java.util.concurrent.TimeUnit.MILLISECONDS import akka.actor.{ ExtensionId, ActorSystem, Extension, ActorSystemImpl } object TestKitExtension extends ExtensionId[TestKitSettings] { - def createExtension(system: ActorSystemImpl): TestKitSettings = new TestKitSettings(system.applicationConfig) + def createExtension(system: ActorSystemImpl): TestKitSettings = new TestKitSettings(system.settings.config) } -class TestKitSettings(cfg: Config) extends Extension { - private def referenceConfig: Config = - ConfigFactory.parseResource(classOf[ActorSystem], "/akka-testkit-reference.conf", - ConfigParseOptions.defaults.setAllowMissing(false)) - val config: ConfigRoot = ConfigFactory.emptyRoot("akka-testkit").withFallback(cfg).withFallback(referenceConfig).resolve() +class TestKitSettings(val config: Config) extends Extension { import config._ diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index 7b2df6efab..e43ea378cb 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -13,14 +13,12 @@ import akka.util.duration._ import akka.dispatch.FutureTimeoutException import com.typesafe.config.Config import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions object TimingTest extends Tag("timing") object AkkaSpec { - val testConf = - ActorSystem.DefaultConfigurationLoader.defaultConfig.withFallback( - ConfigFactory.parseString(""" + val testConf = { + val cfg = ConfigFactory.parseString(""" akka { event-handlers = ["akka.testkit.TestEventListener"] loglevel = "WARNING" @@ -32,7 +30,9 @@ object AkkaSpec { } } } - """, ConfigParseOptions.defaults)) + """) + ConfigFactory.load(cfg) + } def mapToConfig(map: Map[String, Any]): Config = { import scala.collection.JavaConverters._ @@ -64,7 +64,7 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam def this(config: Config) = this(ActorSystem(getClass.getSimpleName, config.withFallback(AkkaSpec.testConf))) - def this(s: String) = this(ConfigFactory.parseString(s, ConfigParseOptions.defaults)) + def this(s: String) = this(ConfigFactory.parseString(s)) def this(configMap: Map[String, _]) = { this(AkkaSpec.mapToConfig(configMap)) @@ -87,12 +87,11 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam class AkkaSpecSpec extends WordSpec with MustMatchers { "An AkkaSpec" must { "terminate all actors" in { - import ActorSystem.DefaultConfigurationLoader.defaultConfig import scala.collection.JavaConverters._ val conf = Map( "akka.actor.debug.lifecycle" -> true, "akka.actor.debug.event-stream" -> true, "akka.loglevel" -> "DEBUG", "akka.stdout-loglevel" -> "DEBUG") - val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(defaultConfig)) + val system = ActorSystem("test", ConfigFactory.parseMap(conf.asJava).withFallback(AkkaSpec.testConf)) val spec = new AkkaSpec(system) { val ref = Seq(testActor, system.actorOf(Props.empty, "name")) } From 823a68ac0f77bac7c4a67f643ad50073051e0d2a Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Fri, 25 Nov 2011 14:49:09 +0100 Subject: [PATCH 04/30] Updated samples and tutorial to Akka 2.0. Added projects to SBT project file. Fixes #1278 --- .../akka/routing/ConnectionManager.scala | 5 + .../src/main/scala/akka/routing/Routing.scala | 13 +- .../remote/netty/NettyRemoteSupport.scala | 4 +- .../akka/remote/NetworkFailureSpec.scala | 6 +- akka-samples/akka-sample-ants/README.md | 6 +- .../src/main/scala/Ants.scala | 67 +-- .../src/main/scala/sample/camel/Actors.scala | 322 +++++------ .../src/main/scala/sample/camel/Boot.scala | 196 +++---- .../sample/camel/ClientApplication.scala | 60 ++- .../sample/camel/ServerApplication.scala | 56 +- .../sample/camel/StandaloneApplication.scala | 256 ++++----- .../src/main/scala/ChatServer.scala | 505 +++++++++--------- akka-samples/akka-sample-fsm/src/README | 30 ++ .../src/main/scala/Buncher.scala | 21 +- .../main/scala/DiningHakkersOnBecome.scala | 35 +- .../src/main/scala/DiningHakkersOnFsm.scala | 33 +- .../akka-sample-hello/config/akka.conf | 27 - .../config/microkernel-server.xml | 65 --- akka-samples/akka-sample-hello/src/README | 28 + .../src/main/scala/sample/hello/Boot.scala | 14 - .../scala/sample/hello/HelloEndpoint.scala | 29 - .../src/main/scala/sample/hello/Main.scala | 32 ++ .../src/main/scala/OsgiExample.scala | 14 +- .../ServerManagedRemoteActorSample.scala | 64 +-- akka-tutorials/akka-tutorial-first/pom.xml | 2 +- .../project/TutorialBuild.scala | 22 + .../project/build.properties | 6 +- .../project/build/Project.scala | 3 - .../project/plugins/Plugins.scala | 6 - .../java/akka/tutorial/first/java/Pi.java | 290 +++++----- .../src/main/scala/Pi.scala | 175 +++--- .../src/test/scala/WorkerSpec.scala | 26 + project/AkkaBuild.scala | 82 +-- 33 files changed, 1291 insertions(+), 1209 deletions(-) create mode 100644 akka-samples/akka-sample-fsm/src/README delete mode 100644 akka-samples/akka-sample-hello/config/akka.conf delete mode 100644 akka-samples/akka-sample-hello/config/microkernel-server.xml create mode 100644 akka-samples/akka-sample-hello/src/README delete mode 100644 akka-samples/akka-sample-hello/src/main/scala/sample/hello/Boot.scala delete mode 100644 akka-samples/akka-sample-hello/src/main/scala/sample/hello/HelloEndpoint.scala create mode 100644 akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala create mode 100644 akka-tutorials/akka-tutorial-first/project/TutorialBuild.scala delete mode 100644 akka-tutorials/akka-tutorial-first/project/build/Project.scala delete mode 100644 akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala create mode 100644 akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala diff --git a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala index b7655e376e..e8649dc3db 100644 --- a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala +++ b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala @@ -11,6 +11,7 @@ import scala.annotation.tailrec import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } import java.net.InetSocketAddress import akka.remote.RemoteAddress +import collection.JavaConverters /** * An Iterable that also contains a version. @@ -85,6 +86,10 @@ trait ConnectionManager { */ class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends ConnectionManager { + def this(linkedList: java.util.LinkedList[ActorRef]) { + this(JavaConverters.iterableAsScalaIterableConverter(linkedList).asScala) + } + case class State(version: Long, connections: Iterable[ActorRef]) extends VersionedIterable[ActorRef] { def iterable = connections } diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 0dc6366cb1..8257002a42 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -14,6 +14,7 @@ import java.lang.reflect.InvocationTargetException import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger } import scala.annotation.tailrec +import akka.japi.Creator sealed trait RouterType @@ -66,11 +67,17 @@ object RouterType { * Contains the configuration to create local and clustered routed actor references. * Routed ActorRef configuration object, this is thread safe and fully sharable. */ -case class RoutedProps private[akka] ( +private[akka] case class RoutedProps( routerFactory: () ⇒ Router = RoutedProps.defaultRouterFactory, connectionManager: ConnectionManager = new LocalConnectionManager(List()), timeout: Timeout = RoutedProps.defaultTimeout, localOnly: Boolean = RoutedProps.defaultLocalOnly) { + + // Java API + def this(creator: Creator[Router], connectionManager: ConnectionManager, timeout: Timeout, localOnly: Boolean) { + this(() ⇒ creator.create(), connectionManager, timeout, localOnly) + } + } object RoutedProps { @@ -167,7 +174,7 @@ abstract private[akka] class AbstractRoutedActorRef(val system: ActorSystem, val * A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to send a message to * on (or more) of these actors. */ -private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, override val name: String) extends AbstractRoutedActorRef(system, routedProps) { +class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, override val name: String) extends AbstractRoutedActorRef(system, routedProps) { val path = supervisor.path / name @@ -346,7 +353,7 @@ class RandomRouter extends BasicRouter { * * @author Jonas Bonér */ -class RoundRobinRouter extends BasicRouter { +private[akka] class RoundRobinRouter extends BasicRouter { private val state = new AtomicReference[RoundRobinState] diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index eedf58fd9e..9424b93372 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -95,8 +95,8 @@ abstract class RemoteClient private[akka] ( } class PassiveRemoteClient(val currentChannel: Channel, - remoteSupport: NettyRemoteSupport, - remoteAddress: RemoteAddress) + remoteSupport: NettyRemoteSupport, + remoteAddress: RemoteAddress) extends RemoteClient(remoteSupport, remoteAddress) { def connect(reconnectIfAlreadyConnected: Boolean = false): Boolean = runSwitch switchOn { diff --git a/akka-remote/src/test/scala/akka/remote/NetworkFailureSpec.scala b/akka-remote/src/test/scala/akka/remote/NetworkFailureSpec.scala index 7f7072b427..56a27079ea 100644 --- a/akka-remote/src/test/scala/akka/remote/NetworkFailureSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/NetworkFailureSpec.scala @@ -20,7 +20,7 @@ trait NetworkFailureSpec { self: AkkaSpec ⇒ val BytesPerSecond = "60KByte/s" val DelayMillis = "350ms" - val PortRang = "1024-65535" + val PortRange = "1024-65535" def replyWithTcpResetFor(duration: Duration, dead: AtomicBoolean) = { Future { @@ -82,12 +82,12 @@ trait NetworkFailureSpec { self: AkkaSpec ⇒ def enableNetworkDrop() = { restoreIP() - assert(new ProcessBuilder("ipfw", "add", "1", "deny", "tcp", "from", "any", "to", "any", PortRang).start.waitFor == 0) + assert(new ProcessBuilder("ipfw", "add", "1", "deny", "tcp", "from", "any", "to", "any", PortRange).start.waitFor == 0) } def enableTcpReset() = { restoreIP() - assert(new ProcessBuilder("ipfw", "add", "1", "reset", "tcp", "from", "any", "to", "any", PortRang).start.waitFor == 0) + assert(new ProcessBuilder("ipfw", "add", "1", "reset", "tcp", "from", "any", "to", "any", PortRange).start.waitFor == 0) } def restoreIP() = { diff --git a/akka-samples/akka-sample-ants/README.md b/akka-samples/akka-sample-ants/README.md index 3c559834cb..5a416e0ba4 100644 --- a/akka-samples/akka-sample-ants/README.md +++ b/akka-samples/akka-sample-ants/README.md @@ -39,8 +39,8 @@ By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software. -[ants.clj]:http://clojure.googlegroups.com/web/ants.clj -[akka]:http://akkasource.org -[spde]:http://technically.us/spde/ +[ants.clj]: http://clojure.googlegroups.com/web/ants.clj +[akka]: http://akka.io +[spde]: http://technically.us/spde/ [sbt]: http://code.google.com/p/simple-build-tool/ [cpl]: http://opensource.org/licenses/cpl1.0.php \ No newline at end of file diff --git a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala index a6aae6f8bd..4b9cd8dd88 100644 --- a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala +++ b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala @@ -4,22 +4,23 @@ package sample.ants +import scala.util.Random.{ nextInt ⇒ randomInt } +import akka.actor.{ ActorSystem, Actor, ActorRef } +import akka.util.Duration +import akka.util.duration._ import java.util.concurrent.TimeUnit -import scala.util.Random.{nextInt => randomInt} -import akka.actor.{Actor, ActorRef, Scheduler} -import akka.actor.Actor.actorOf import akka.stm._ object Config { - val Dim = 80 // dimensions of square world - val AntsSqrt = 20 // number of ants = AntsSqrt^2 - val FoodPlaces = 35 // number of places with food - val FoodRange = 100 // range of amount of food at a place - val PherScale = 10 // scale factor for pheromone drawing - val AntMillis = 100 // how often an ant behaves (milliseconds) - val EvapMillis = 1000 // how often pheromone evaporation occurs (milliseconds) - val EvapRate = 0.99f // pheromone evaporation rate - val StartDelay = 1000 // delay before everything kicks off (milliseconds) + val Dim = 80 // dimensions of square world + val AntsSqrt = 20 // number of ants = AntsSqrt^2 + val FoodPlaces = 35 // number of places with food + val FoodRange = 100 // range of amount of food at a place + val PherScale = 10 // scale factor for pheromone drawing + val AntMillis = 100 // how often an ant behaves (milliseconds) + val EvapMillis = 1000 // how often pheromone evaporation occurs (milliseconds) + val EvapRate = 0.99f // pheromone evaporation rate + val StartDelay = 1000 milliseconds // delay before everything kicks off (milliseconds) } case class Ant(dir: Int, food: Boolean = false) { @@ -32,7 +33,7 @@ case class Ant(dir: Int, food: Boolean = false) { case class Cell(food: Int = 0, pher: Float = 0, ant: Option[Ant] = None, home: Boolean = false) { def addFood(i: Int) = copy(food = food + i) def addPher(x: Float) = copy(pher = pher + x) - def alterPher(f: Float => Float) = copy(pher = f(pher)) + def alterPher(f: Float ⇒ Float) = copy(pher = f(pher)) def putAnt(antOpt: Option[Ant]) = copy(ant = antOpt) def makeHome = copy(home = true) } @@ -45,10 +46,10 @@ class Place(initCell: Cell = EmptyCell) extends Ref(initCell) { def food(i: Int) = alter(_.addFood(i)) def hasFood = food > 0 def pher: Float = cell.pher - def pher(f: Float => Float) = alter(_.alterPher(f)) + def pher(f: Float ⇒ Float) = alter(_.alterPher(f)) def trail = alter(_.addPher(1)) def ant: Option[Ant] = cell.ant - def ant(f: Ant => Ant): Cell = alter(_.putAnt(ant map f)) + def ant(f: Ant ⇒ Ant): Cell = alter(_.putAnt(ant map f)) def enter(antOpt: Option[Ant]): Cell = alter(_.putAnt(antOpt)) def enter(ant: Ant): Cell = enter(Some(ant)) def leave = enter(None) @@ -62,10 +63,12 @@ case object Ping object World { import Config._ + val system = ActorSystem() + val homeOff = Dim / 4 lazy val places = Vector.fill(Dim, Dim)(new Place) lazy val ants = setup - lazy val evaporator = actorOf[Evaporator] + lazy val evaporator = system.actorOf[Evaporator] private val snapshotFactory = TransactionFactory(readonly = true, familyName = "snapshot") @@ -74,14 +77,14 @@ object World { def place(loc: (Int, Int)) = places(loc._1)(loc._2) private def setup = atomic { - for (i <- 1 to FoodPlaces) { + for (i ← 1 to FoodPlaces) { place(randomInt(Dim), randomInt(Dim)) food (randomInt(FoodRange)) } val homeRange = homeOff until (AntsSqrt + homeOff) - for (x <- homeRange; y <- homeRange) yield { + for (x ← homeRange; y ← homeRange) yield { place(x, y).makeHome place(x, y) enter Ant(randomInt(8)) - actorOf(new AntActor(x, y)) + system.actorOf(new AntActor(x, y)) } } @@ -91,7 +94,7 @@ object World { } private def pingEvery(millis: Long)(actor: ActorRef) = - Scheduler.schedule(actor, Ping, Config.StartDelay, millis, TimeUnit.MILLISECONDS) + system.scheduler.schedule(actor, Ping, Config.StartDelay, Duration(millis, TimeUnit.MILLISECONDS)) } object Util { @@ -106,13 +109,13 @@ object Util { def dimBound(n: Int) = bound(Dim, n) val dirDelta = Map(0 -> (0, -1), 1 -> (1, -1), 2 -> (1, 0), 3 -> (1, 1), - 4 -> (0, 1), 5 -> (-1, 1), 6 -> (-1, 0), 7 -> (-1, -1)) + 4 -> (0, 1), 5 -> (-1, 1), 6 -> (-1, 0), 7 -> (-1, -1)) def deltaLoc(x: Int, y: Int, dir: Int) = { val (dx, dy) = dirDelta(dirBound(dir)) (dimBound(x + dx), dimBound(y + dy)) } - def rankBy[A, B: Ordering](xs: Seq[A], f: A => B) = Map(xs.sortBy(f).zip(Stream from 1): _*) + def rankBy[A, B: Ordering](xs: Seq[A], f: A ⇒ B) = Map(xs.sortBy(f).zip(Stream from 1): _*) def roulette(slices: Seq[Int]) = { val total = slices.sum @@ -128,7 +131,7 @@ object Util { trait WorldActor extends Actor { def act - def receive = { case Ping => act } + def receive = { case Ping ⇒ act } } class AntActor(initLoc: (Int, Int)) extends WorldActor { @@ -140,8 +143,8 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { val name = "ant-from-" + initLoc._1 + "-" + initLoc._2 implicit val txFactory = TransactionFactory(familyName = name) - val homing = (p: Place) => p.pher + (100 * (if (p.home) 0 else 1)) - val foraging = (p: Place) => p.pher + p.food + val homing = (p: Place) ⇒ p.pher + (100 * (if (p.home) 0 else 1)) + val foraging = (p: Place) ⇒ p.pher + p.food def loc = locRef.getOrElse(initLoc) def newLoc(l: (Int, Int)) = locRef swap l @@ -149,7 +152,7 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { def act = atomic { val (x, y) = loc val current = place(x, y) - for (ant <- current.ant) { + for (ant ← current.ant) { val ahead = place(deltaLoc(x, y, ant.dir)) if (ant.food) { // homing if (current.home) dropFood @@ -166,7 +169,7 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { def move = { val (x, y) = loc val from = place(x, y) - for (ant <- from.ant) { + for (ant ← from.ant) { val toLoc = deltaLoc(x, y, ant.dir) val to = place(toLoc) to enter ant @@ -188,11 +191,11 @@ class AntActor(initLoc: (Int, Int)) extends WorldActor { current ant (_.dropOff.turnAround) } - def random[A: Ordering](ranking: Place => A) = { + def random[A: Ordering](ranking: Place ⇒ A) = { val (x, y) = loc val current = place(x, y) - for (ant <- current.ant) { - val delta = (turn: Int) => place(deltaLoc(x, y, ant.dir + turn)) + for (ant ← current.ant) { + val delta = (turn: Int) ⇒ place(deltaLoc(x, y, ant.dir + turn)) val ahead = delta(0) val aheadLeft = delta(-1) val aheadRight = delta(+1) @@ -211,9 +214,9 @@ class Evaporator extends WorldActor { import World._ implicit val txFactory = TransactionFactory(familyName = "evaporator") - val evaporate = (pher: Float) => pher * EvapRate + val evaporate = (pher: Float) ⇒ pher * EvapRate - def act = for (x <- 0 until Dim; y <- 0 until Dim) { + def act = for (x ← 0 until Dim; y ← 0 until Dim) { atomic { place(x, y) pher evaporate } } } diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala index f4655c3985..c091d7dfd8 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala @@ -1,161 +1,167 @@ -package sample.camel - -import org.apache.camel.Exchange - -import akka.actor.{ Actor, ActorRef, ActorRegistry } -import akka.camel.{ Ack, Failure, Producer, Message, Consumer } - /** - * Client-initiated remote actor. - */ -class RemoteActor1 extends Actor with Consumer { - def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-1" + * Copyright (C) 2009-2010 Typesafe Inc. . + */ - protected def receive = { - case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote1")) - } -} +// CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 -/** - * Server-initiated remote actor. - */ -class RemoteActor2 extends Actor with Consumer { - def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-2" - - protected def receive = { - case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote2")) - } -} - -class Producer1 extends Actor with Producer { - def endpointUri = "direct:welcome" - override def oneway = false // default -} - -class Consumer1 extends Actor with Consumer { - def endpointUri = "file:data/input/actor" - - def receive = { - case msg: Message ⇒ println("received %s" format msg.bodyAs[String]) - } -} - -class Consumer2 extends Actor with Consumer { - def endpointUri = "jetty:http://0.0.0.0:8877/camel/default" - - def receive = { - case msg: Message ⇒ sender ! ("Hello %s" format msg.bodyAs[String]) - } -} - -class Consumer3(transformer: ActorRef) extends Actor with Consumer { - def endpointUri = "jetty:http://0.0.0.0:8877/camel/welcome" - - def receive = { - case msg: Message ⇒ transformer.forward(msg.setBodyAs[String]) - } -} - -class Consumer4 extends Actor with Consumer { - def endpointUri = "jetty:http://0.0.0.0:8877/camel/stop" - - def receive = { - case msg: Message ⇒ msg.bodyAs[String] match { - case "stop" ⇒ { - sender ! "Consumer4 stopped" - self.stop - } - case body ⇒ sender ! body - } - } -} - -class Consumer5 extends Actor with Consumer { - def endpointUri = "jetty:http://0.0.0.0:8877/camel/start" - - def receive = { - case _ ⇒ { - Actor.actorOf[Consumer4] - sender ! "Consumer4 started" - } - } -} - -class Transformer(producer: ActorRef) extends Actor { - protected def receive = { - case msg: Message ⇒ producer.forward(msg.transformBody((body: String) ⇒ "- %s -" format body)) - } -} - -class Subscriber(name: String, uri: String) extends Actor with Consumer { - def endpointUri = uri - - protected def receive = { - case msg: Message ⇒ println("%s received: %s" format (name, msg.body)) - } -} - -class Publisher(uri: String) extends Actor with Producer { - def endpointUri = uri - override def oneway = true -} - -class PublisherBridge(uri: String, publisher: ActorRef) extends Actor with Consumer { - def endpointUri = uri - - protected def receive = { - case msg: Message ⇒ { - publisher ! msg.bodyAs[String] - sender ! "message published" - } - } -} - -class HttpConsumer(producer: ActorRef) extends Actor with Consumer { - def endpointUri = "jetty:http://0.0.0.0:8875/" - - protected def receive = { - case msg ⇒ producer forward msg - } -} - -class HttpProducer(transformer: ActorRef) extends Actor with Producer { - def endpointUri = "jetty://http://akka.io/?bridgeEndpoint=true" - - override protected def receiveBeforeProduce = { - // only keep Exchange.HTTP_PATH message header (which needed by bridge endpoint) - case msg: Message ⇒ msg.setHeaders(msg.headers(Set(Exchange.HTTP_PATH))) - } - - override protected def receiveAfterProduce = { - // do not reply but forward result to transformer - case msg ⇒ transformer forward msg - } -} - -class HttpTransformer extends Actor { - protected def receive = { - case msg: Message ⇒ sender ! (msg.transformBody { body: String ⇒ body replaceAll ("Akka ", "AKKA ") }) - case msg: Failure ⇒ sender ! msg - } -} - -class FileConsumer extends Actor with Consumer { - def endpointUri = "file:data/input/actor?delete=true" - override def autoack = false - - var counter = 0 - - def receive = { - case msg: Message ⇒ { - if (counter == 2) { - println("received %s" format msg.bodyAs[String]) - sender ! Ack - } else { - println("rejected %s" format msg.bodyAs[String]) - counter += 1 - sender ! Failure(new Exception("message number %s not accepted" format counter)) - } - } - } -} +//package sample.camel +// +//import org.apache.camel.Exchange +// +//import akka.actor.{ Actor, ActorRef, ActorRegistry } +//import akka.camel.{ Ack, Failure, Producer, Message, Consumer } +// +///** +// * Client-initiated remote actor. +// */ +//class RemoteActor1 extends Actor with Consumer { +// def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-1" +// +// protected def receive = { +// case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote1")) +// } +//} +// +///** +// * Server-initiated remote actor. +// */ +//class RemoteActor2 extends Actor with Consumer { +// def endpointUri = "jetty:http://localhost:6644/camel/remote-actor-2" +// +// protected def receive = { +// case msg: Message ⇒ sender ! Message("hello %s" format msg.bodyAs[String], Map("sender" -> "remote2")) +// } +//} +// +//class Producer1 extends Actor with Producer { +// def endpointUri = "direct:welcome" +// override def oneway = false // default +//} +// +//class Consumer1 extends Actor with Consumer { +// def endpointUri = "file:data/input/actor" +// +// def receive = { +// case msg: Message ⇒ println("received %s" format msg.bodyAs[String]) +// } +//} +// +//class Consumer2 extends Actor with Consumer { +// def endpointUri = "jetty:http://0.0.0.0:8877/camel/default" +// +// def receive = { +// case msg: Message ⇒ sender ! ("Hello %s" format msg.bodyAs[String]) +// } +//} +// +//class Consumer3(transformer: ActorRef) extends Actor with Consumer { +// def endpointUri = "jetty:http://0.0.0.0:8877/camel/welcome" +// +// def receive = { +// case msg: Message ⇒ transformer.forward(msg.setBodyAs[String]) +// } +//} +// +//class Consumer4 extends Actor with Consumer { +// def endpointUri = "jetty:http://0.0.0.0:8877/camel/stop" +// +// def receive = { +// case msg: Message ⇒ msg.bodyAs[String] match { +// case "stop" ⇒ { +// sender ! "Consumer4 stopped" +// self.stop +// } +// case body ⇒ sender ! body +// } +// } +//} +// +//class Consumer5 extends Actor with Consumer { +// def endpointUri = "jetty:http://0.0.0.0:8877/camel/start" +// +// def receive = { +// case _ ⇒ { +// Actor.actorOf[Consumer4] +// sender ! "Consumer4 started" +// } +// } +//} +// +//class Transformer(producer: ActorRef) extends Actor { +// protected def receive = { +// case msg: Message ⇒ producer.forward(msg.transformBody((body: String) ⇒ "- %s -" format body)) +// } +//} +// +//class Subscriber(name: String, uri: String) extends Actor with Consumer { +// def endpointUri = uri +// +// protected def receive = { +// case msg: Message ⇒ println("%s received: %s" format (name, msg.body)) +// } +//} +// +//class Publisher(uri: String) extends Actor with Producer { +// def endpointUri = uri +// override def oneway = true +//} +// +//class PublisherBridge(uri: String, publisher: ActorRef) extends Actor with Consumer { +// def endpointUri = uri +// +// protected def receive = { +// case msg: Message ⇒ { +// publisher ! msg.bodyAs[String] +// sender ! "message published" +// } +// } +//} +// +//class HttpConsumer(producer: ActorRef) extends Actor with Consumer { +// def endpointUri = "jetty:http://0.0.0.0:8875/" +// +// protected def receive = { +// case msg ⇒ producer forward msg +// } +//} +// +//class HttpProducer(transformer: ActorRef) extends Actor with Producer { +// def endpointUri = "jetty://http://akka.io/?bridgeEndpoint=true" +// +// override protected def receiveBeforeProduce = { +// // only keep Exchange.HTTP_PATH message header (which needed by bridge endpoint) +// case msg: Message ⇒ msg.setHeaders(msg.headers(Set(Exchange.HTTP_PATH))) +// } +// +// override protected def receiveAfterProduce = { +// // do not reply but forward result to transformer +// case msg ⇒ transformer forward msg +// } +//} +// +//class HttpTransformer extends Actor { +// protected def receive = { +// case msg: Message ⇒ sender ! (msg.transformBody { body: String ⇒ body replaceAll ("Akka ", "AKKA ") }) +// case msg: Failure ⇒ sender ! msg +// } +//} +// +//class FileConsumer extends Actor with Consumer { +// def endpointUri = "file:data/input/actor?delete=true" +// override def autoack = false +// +// var counter = 0 +// +// def receive = { +// case msg: Message ⇒ { +// if (counter == 2) { +// println("received %s" format msg.bodyAs[String]) +// sender ! Ack +// } else { +// println("rejected %s" format msg.bodyAs[String]) +// counter += 1 +// sender ! Failure(new Exception("message number %s not accepted" format counter)) +// } +// } +// } +//} diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala index b84dd9c1c9..31b76835a4 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala @@ -1,98 +1,104 @@ -package sample.camel - -import org.apache.camel.{ Exchange, Processor } -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.impl.DefaultCamelContext -import org.apache.camel.spring.spi.ApplicationContextRegistry -import org.springframework.context.support.ClassPathXmlApplicationContext - -import akka.actor.Actor._ -import akka.actor.Props -import akka.actor.TypedActor -import akka.camel.CamelContextManager - /** - * @author Martin Krasser - */ -class Boot { + * Copyright (C) 2009-2010 Typesafe Inc. . + */ - // ----------------------------------------------------------------------- - // Basic example - // ----------------------------------------------------------------------- +// CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 - actorOf[Consumer1] - actorOf[Consumer2] - - // ----------------------------------------------------------------------- - // Custom Camel route example - // ----------------------------------------------------------------------- - - // Create CamelContext and a Spring-based registry - val context = new ClassPathXmlApplicationContext("/context-jms.xml", getClass) - val registry = new ApplicationContextRegistry(context) - - // Use a custom Camel context and a custom touter builder - CamelContextManager.init(new DefaultCamelContext(registry)) - CamelContextManager.mandatoryContext.addRoutes(new CustomRouteBuilder) - - val producer = actorOf[Producer1] - val mediator = actorOf(new Transformer(producer)) - val consumer = actorOf(new Consumer3(mediator)) - - // ----------------------------------------------------------------------- - // Asynchronous consumer-producer example (Akka homepage transformation) - // ----------------------------------------------------------------------- - - val httpTransformer = actorOf(new HttpTransformer) - val httpProducer = actorOf(new HttpProducer(httpTransformer)) - val httpConsumer = actorOf(new HttpConsumer(httpProducer)) - - // ----------------------------------------------------------------------- - // Publish subscribe examples - // ----------------------------------------------------------------------- - - // - // Cometd example commented out because camel-cometd is broken since Camel 2.3 - // - - //val cometdUri = "cometd://localhost:8111/test/abc?baseResource=file:target" - //val cometdSubscriber = actorOf(new Subscriber("cometd-subscriber", cometdUri)) - //val cometdPublisher = actorOf(new Publisher("cometd-publisher", cometdUri)) - - val jmsUri = "jms:topic:test" - val jmsSubscriber1 = actorOf(new Subscriber("jms-subscriber-1", jmsUri)) - val jmsSubscriber2 = actorOf(new Subscriber("jms-subscriber-2", jmsUri)) - val jmsPublisher = actorOf(new Publisher(jmsUri), "jms-publisher") - - //val cometdPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/cometd", cometdPublisher)) - val jmsPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/jms", jmsPublisher)) - - // ----------------------------------------------------------------------- - // Actor un-publishing and re-publishing example - // ----------------------------------------------------------------------- - - actorOf[Consumer4] // POSTing "stop" to http://0.0.0.0:8877/camel/stop stops and unpublishes this actor - actorOf[Consumer5] // POSTing any msg to http://0.0.0.0:8877/camel/start starts and published Consumer4 again. - - // ----------------------------------------------------------------------- - // Active object example - // ----------------------------------------------------------------------- - - // TODO: investigate why this consumer is not published - TypedActor.typedActorOf(classOf[TypedConsumer1], classOf[TypedConsumer1Impl], Props()) -} - -/** - * @author Martin Krasser - */ -class CustomRouteBuilder extends RouteBuilder { - def configure { - val actorUri = "actor:%s" format classOf[Consumer2].getName - from("jetty:http://0.0.0.0:8877/camel/custom").to(actorUri) - from("direct:welcome").process(new Processor() { - def process(exchange: Exchange) { - exchange.getOut.setBody("Welcome %s" format exchange.getIn.getBody) - } - }) - } -} +//package sample.camel +// +//import org.apache.camel.{ Exchange, Processor } +//import org.apache.camel.builder.RouteBuilder +//import org.apache.camel.impl.DefaultCamelContext +//import org.apache.camel.spring.spi.ApplicationContextRegistry +//import org.springframework.context.support.ClassPathXmlApplicationContext +// +//import akka.actor.Actor._ +//import akka.actor.Props +//import akka.actor.TypedActor +//import akka.camel.CamelContextManager +// +///** +// * @author Martin Krasser +// */ +//class Boot { +// +// // ----------------------------------------------------------------------- +// // Basic example +// // ----------------------------------------------------------------------- +// +// actorOf[Consumer1] +// actorOf[Consumer2] +// +// // ----------------------------------------------------------------------- +// // Custom Camel route example +// // ----------------------------------------------------------------------- +// +// // Create CamelContext and a Spring-based registry +// val context = new ClassPathXmlApplicationContext("/context-jms.xml", getClass) +// val registry = new ApplicationContextRegistry(context) +// +// // Use a custom Camel context and a custom touter builder +// CamelContextManager.init(new DefaultCamelContext(registry)) +// CamelContextManager.mandatoryContext.addRoutes(new CustomRouteBuilder) +// +// val producer = actorOf[Producer1] +// val mediator = actorOf(new Transformer(producer)) +// val consumer = actorOf(new Consumer3(mediator)) +// +// // ----------------------------------------------------------------------- +// // Asynchronous consumer-producer example (Akka homepage transformation) +// // ----------------------------------------------------------------------- +// +// val httpTransformer = actorOf(new HttpTransformer) +// val httpProducer = actorOf(new HttpProducer(httpTransformer)) +// val httpConsumer = actorOf(new HttpConsumer(httpProducer)) +// +// // ----------------------------------------------------------------------- +// // Publish subscribe examples +// // ----------------------------------------------------------------------- +// +// // +// // Cometd example commented out because camel-cometd is broken since Camel 2.3 +// // +// +// //val cometdUri = "cometd://localhost:8111/test/abc?baseResource=file:target" +// //val cometdSubscriber = actorOf(new Subscriber("cometd-subscriber", cometdUri)) +// //val cometdPublisher = actorOf(new Publisher("cometd-publisher", cometdUri)) +// +// val jmsUri = "jms:topic:test" +// val jmsSubscriber1 = actorOf(new Subscriber("jms-subscriber-1", jmsUri)) +// val jmsSubscriber2 = actorOf(new Subscriber("jms-subscriber-2", jmsUri)) +// val jmsPublisher = actorOf(new Publisher(jmsUri), "jms-publisher") +// +// //val cometdPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/cometd", cometdPublisher)) +// val jmsPublisherBridge = actorOf(new PublisherBridge("jetty:http://0.0.0.0:8877/camel/pub/jms", jmsPublisher)) +// +// // ----------------------------------------------------------------------- +// // Actor un-publishing and re-publishing example +// // ----------------------------------------------------------------------- +// +// actorOf[Consumer4] // POSTing "stop" to http://0.0.0.0:8877/camel/stop stops and unpublishes this actor +// actorOf[Consumer5] // POSTing any msg to http://0.0.0.0:8877/camel/start starts and published Consumer4 again. +// +// // ----------------------------------------------------------------------- +// // Active object example +// // ----------------------------------------------------------------------- +// +// // TODO: investigate why this consumer is not published +// TypedActor.typedActorOf(classOf[TypedConsumer1], classOf[TypedConsumer1Impl], Props()) +//} +// +///** +// * @author Martin Krasser +// */ +//class CustomRouteBuilder extends RouteBuilder { +// def configure { +// val actorUri = "actor:%s" format classOf[Consumer2].getName +// from("jetty:http://0.0.0.0:8877/camel/custom").to(actorUri) +// from("direct:welcome").process(new Processor() { +// def process(exchange: Exchange) { +// exchange.getOut.setBody("Welcome %s" format exchange.getIn.getBody) +// } +// }) +// } +//} diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala index c5662ea3b6..a4c5edf398 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala @@ -1,29 +1,35 @@ -package sample.camel - -import akka.actor.Actor._ -import akka.actor.TypedActor -import akka.camel.Message - /** - * @author Martin Krasser - */ -object ClientApplication extends App { - - /* TODO: fix remote example - - val actor1 = remote.actorOf[RemoteActor1]("localhost", 7777) - val actor2 = remote.actorFor("remote2", "localhost", 7777) - - val typedActor1 = - TypedActor.newRemoteInstance(classOf[RemoteTypedConsumer1],classOf[RemoteTypedConsumer1Impl], "localhost", 7777) - - val typedActor2 = remote.typedActorFor(classOf[RemoteTypedConsumer2], "remote3", "localhost", 7777) - - println(actor1 !! Message("actor1")) // activates and publishes actor remotely - println(actor2 !! Message("actor2")) // actor already activated and published remotely - - println(typedActor1.foo("x1", "y1")) // activates and publishes typed actor methods remotely - println(typedActor2.foo("x2", "y2")) // typed actor methods already activated and published remotely - + * Copyright (C) 2009-2010 Typesafe Inc. . */ -} + +// CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 + +//package sample.camel +// +//import akka.actor.Actor._ +//import akka.actor.TypedActor +//import akka.camel.Message +// +///** +// * @author Martin Krasser +// */ +//object ClientApplication extends App { +// +// /* TODO: fix remote example +// +// val actor1 = remote.actorOf[RemoteActor1]("localhost", 7777) +// val actor2 = remote.actorFor("remote2", "localhost", 7777) +// +// val typedActor1 = +// TypedActor.newRemoteInstance(classOf[RemoteTypedConsumer1],classOf[RemoteTypedConsumer1Impl], "localhost", 7777) +// +// val typedActor2 = remote.typedActorFor(classOf[RemoteTypedConsumer2], "remote3", "localhost", 7777) +// +// println(actor1 !! Message("actor1")) // activates and publishes actor remotely +// println(actor2 !! Message("actor2")) // actor already activated and published remotely +// +// println(typedActor1.foo("x1", "y1")) // activates and publishes typed actor methods remotely +// println(typedActor2.foo("x2", "y2")) // typed actor methods already activated and published remotely +// +// */ +//} diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala index aae7a61d99..1181e661d4 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala @@ -1,27 +1,33 @@ -package sample.camel - -import akka.actor.Actor._ -import akka.camel.CamelServiceManager -import akka.actor.{ TypedActor, Props } - /** - * @author Martin Krasser - */ -object ServerApplication extends App { - import CamelServiceManager._ - - /* TODO: fix remote example - - startCamelService - - val ua = actorOf[RemoteActor2] - val ta = TypedActor.typedActorOf( - classOf[RemoteTypedConsumer2], - classOf[RemoteTypedConsumer2Impl], Props()) - - remote.start("localhost", 7777) - remote.register("remote2", ua) - remote.registerTypedActor("remote3", ta) - + * Copyright (C) 2009-2010 Typesafe Inc. . */ -} + +// CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 + +//package sample.camel +// +//import akka.actor.Actor._ +//import akka.camel.CamelServiceManager +//import akka.actor.{ TypedActor, Props } +// +///** +// * @author Martin Krasser +// */ +//object ServerApplication extends App { +// import CamelServiceManager._ +// +// /* TODO: fix remote example +// +// startCamelService +// +// val ua = actorOf[RemoteActor2] +// val ta = TypedActor.typedActorOf( +// classOf[RemoteTypedConsumer2], +// classOf[RemoteTypedConsumer2Impl], Props()) +// +// remote.start("localhost", 7777) +// remote.register("remote2", ua) +// remote.registerTypedActor("remote3", ta) +// +// */ +//} diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala index 62506c5c5a..c87e6bdcab 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala @@ -1,128 +1,134 @@ -package sample.camel - -import org.apache.camel.impl.{ DefaultCamelContext, SimpleRegistry } -import org.apache.camel.builder.RouteBuilder -import org.apache.camel.spring.spi.ApplicationContextRegistry -import org.springframework.context.support.ClassPathXmlApplicationContext - -import akka.actor.{ Actor, TypedActor, Props } -import akka.camel._ - /** - * @author Martin Krasser - */ -object StandaloneApplication extends App { - import CamelContextManager._ - import CamelServiceManager._ + * Copyright (C) 2009-2010 Typesafe Inc. . + */ - // 'externally' register typed actors - val registry = new SimpleRegistry - registry.put("sample", TypedActor.typedActorOf(classOf[BeanIntf], classOf[BeanImpl], Props())) - - // customize CamelContext - CamelContextManager.init(new DefaultCamelContext(registry)) - CamelContextManager.mandatoryContext.addRoutes(new StandaloneApplicationRoute) - - startCamelService - - // access 'externally' registered typed actors - assert("hello msg1" == mandatoryContext.createProducerTemplate.requestBody("direct:test", "msg1")) - - mandatoryService.awaitEndpointActivation(1) { - // 'internally' register typed actor (requires CamelService) - TypedActor.typedActorOf(classOf[TypedConsumer2], classOf[TypedConsumer2Impl], Props()) - } - - // access 'internally' (automatically) registered typed-actors - // (see @consume annotation value at TypedConsumer2.foo method) - assert("default: msg3" == mandatoryContext.createProducerTemplate.requestBody("direct:default", "msg3")) - - stopCamelService - - Actor.registry.local.shutdownAll -} - -class StandaloneApplicationRoute extends RouteBuilder { - def configure = { - // route to typed actors (in SimpleRegistry) - from("direct:test").to("typed-actor:sample?method=foo") - } -} - -object StandaloneSpringApplication extends App { - import CamelContextManager._ - - // load Spring application context - val appctx = new ClassPathXmlApplicationContext("/context-standalone.xml") - - // We cannot use the CamelServiceManager to wait for endpoint activation - // because CamelServiceManager is started by the Spring application context. - // (and hence is not available for setting expectations on activations). This - // will be improved/enabled in upcoming releases. - Thread.sleep(1000) - - // access 'externally' registered typed actors with typed-actor component - assert("hello msg3" == mandatoryTemplate.requestBody("direct:test3", "msg3")) - - // access auto-started untyped consumer - assert("received msg3" == mandatoryTemplate.requestBody("direct:untyped-consumer-1", "msg3")) - - appctx.close - - Actor.registry.local.shutdownAll -} - -class StandaloneSpringApplicationRoute extends RouteBuilder { - def configure = { - // routes to typed actor (in ApplicationContextRegistry) - from("direct:test3").to("typed-actor:ta?method=foo") - } -} - -object StandaloneJmsApplication extends App { - import CamelServiceManager._ - - val context = new ClassPathXmlApplicationContext("/context-jms.xml") - val registry = new ApplicationContextRegistry(context) - - // Init CamelContextManager with custom CamelContext - CamelContextManager.init(new DefaultCamelContext(registry)) - - startCamelService - - val jmsUri = "jms:topic:test" - val jmsPublisher = Actor.actorOf(new Publisher(jmsUri), "jms-publisher") - - mandatoryService.awaitEndpointActivation(2) { - Actor.actorOf(new Subscriber("jms-subscriber-1", jmsUri)) - Actor.actorOf(new Subscriber("jms-subscriber-2", jmsUri)) - } - - // Send 10 messages to via publisher actor - for (i ← 1 to 10) { - jmsPublisher ! ("Akka rocks (%d)" format i) - } - - // Send 10 messages to JMS topic directly - for (i ← 1 to 10) { - CamelContextManager.mandatoryTemplate.sendBody(jmsUri, "Camel rocks (%d)" format i) - } - - // Wait a bit for subscribes to receive messages - Thread.sleep(1000) - - stopCamelService - Actor.registry.local.shutdownAll -} - -object StandaloneFileApplication { - import CamelServiceManager._ - - def main(args: Array[String]) { - startCamelService - mandatoryService.awaitEndpointActivation(1) { - Actor.actorOf(new FileConsumer) - } - } -} +// CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +//package sample.camel +// +//import org.apache.camel.impl.{ DefaultCamelContext, SimpleRegistry } +//import org.apache.camel.builder.RouteBuilder +//import org.apache.camel.spring.spi.ApplicationContextRegistry +//import org.springframework.context.support.ClassPathXmlApplicationContext +// +//import akka.actor.{ Actor, TypedActor, Props } +//import akka.camel._ +// +///** +// * @author Martin Krasser +// */ +//object StandaloneApplication extends App { +// import CamelContextManager._ +// import CamelServiceManager._ +// +// // 'externally' register typed actors +// val registry = new SimpleRegistry +// registry.put("sample", TypedActor.typedActorOf(classOf[BeanIntf], classOf[BeanImpl], Props())) +// +// // customize CamelContext +// CamelContextManager.init(new DefaultCamelContext(registry)) +// CamelContextManager.mandatoryContext.addRoutes(new StandaloneApplicationRoute) +// +// startCamelService +// +// // access 'externally' registered typed actors +// assert("hello msg1" == mandatoryContext.createProducerTemplate.requestBody("direct:test", "msg1")) +// +// mandatoryService.awaitEndpointActivation(1) { +// // 'internally' register typed actor (requires CamelService) +// TypedActor.typedActorOf(classOf[TypedConsumer2], classOf[TypedConsumer2Impl], Props()) +// } +// +// // access 'internally' (automatically) registered typed-actors +// // (see @consume annotation value at TypedConsumer2.foo method) +// assert("default: msg3" == mandatoryContext.createProducerTemplate.requestBody("direct:default", "msg3")) +// +// stopCamelService +// +// Actor.registry.local.shutdownAll +//} +// +//class StandaloneApplicationRoute extends RouteBuilder { +// def configure = { +// // route to typed actors (in SimpleRegistry) +// from("direct:test").to("typed-actor:sample?method=foo") +// } +//} +// +//object StandaloneSpringApplication extends App { +// import CamelContextManager._ +// +// // load Spring application context +// val appctx = new ClassPathXmlApplicationContext("/context-standalone.xml") +// +// // We cannot use the CamelServiceManager to wait for endpoint activation +// // because CamelServiceManager is started by the Spring application context. +// // (and hence is not available for setting expectations on activations). This +// // will be improved/enabled in upcoming releases. +// Thread.sleep(1000) +// +// // access 'externally' registered typed actors with typed-actor component +// assert("hello msg3" == mandatoryTemplate.requestBody("direct:test3", "msg3")) +// +// // access auto-started untyped consumer +// assert("received msg3" == mandatoryTemplate.requestBody("direct:untyped-consumer-1", "msg3")) +// +// appctx.close +// +// Actor.registry.local.shutdownAll +//} +// +//class StandaloneSpringApplicationRoute extends RouteBuilder { +// def configure = { +// // routes to typed actor (in ApplicationContextRegistry) +// from("direct:test3").to("typed-actor:ta?method=foo") +// } +//} +// +//object StandaloneJmsApplication extends App { +// import CamelServiceManager._ +// +// val context = new ClassPathXmlApplicationContext("/context-jms.xml") +// val registry = new ApplicationContextRegistry(context) +// +// // Init CamelContextManager with custom CamelContext +// CamelContextManager.init(new DefaultCamelContext(registry)) +// +// startCamelService +// +// val jmsUri = "jms:topic:test" +// val jmsPublisher = Actor.actorOf(new Publisher(jmsUri), "jms-publisher") +// +// mandatoryService.awaitEndpointActivation(2) { +// Actor.actorOf(new Subscriber("jms-subscriber-1", jmsUri)) +// Actor.actorOf(new Subscriber("jms-subscriber-2", jmsUri)) +// } +// +// // Send 10 messages to via publisher actor +// for (i ← 1 to 10) { +// jmsPublisher ! ("Akka rocks (%d)" format i) +// } +// +// // Send 10 messages to JMS topic directly +// for (i ← 1 to 10) { +// CamelContextManager.mandatoryTemplate.sendBody(jmsUri, "Camel rocks (%d)" format i) +// } +// +// // Wait a bit for subscribes to receive messages +// Thread.sleep(1000) +// +// stopCamelService +// Actor.registry.local.shutdownAll +//} +// +//object StandaloneFileApplication { +// import CamelServiceManager._ +// +// def main(args: Array[String]) { +// startCamelService +// mandatoryService.awaitEndpointActivation(1) { +// Actor.actorOf(new FileConsumer) +// } +// } +//} +// diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index d9b58ef771..49b5da2138 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -1,253 +1,256 @@ - /** - * Copyright (C) 2009-2010 Typesafe Inc. . - */ +/** + * Copyright (C) 2009-2010 Typesafe Inc. . + */ - package sample.chat - - import scala.collection.mutable.HashMap - - import akka.actor.{Actor, ActorRef, Props} - import akka.stm._ - import akka.actor.Actor._ - import akka.event.EventHandler - - /****************************************************************************** - Akka Chat Client/Server Sample Application - - How to run the sample: - - 1. Fire up two shells. For each of them: - - Step down into to the root of the Akka distribution. - - Set 'export AKKA_HOME=. - - Run 'sbt console' to start up a REPL (interpreter). - 2. In the first REPL you get execute: - - scala> import sample.chat._ - - scala> import akka.actor.Actor._ - - scala> val chatService = actorOf[ChatService] - 3. In the second REPL you get execute: - - scala> import sample.chat._ - - scala> ClientRunner.run - 4. See the chat simulation run. - 5. Run it again to see full speed after first initialization. - 6. In the client REPL, or in a new REPL, you can also create your own client - - scala> import sample.chat._ - - scala> val myClient = new ChatClient("") - - scala> myClient.login - - scala> myClient.post("Can I join?") - - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) - - - That’s it. Have fun. - - ******************************************************************************/ - - /** - * ChatServer's internal events. - */ - sealed trait Event - case class Login(user: String) extends Event - case class Logout(user: String) extends Event - case class GetChatLog(from: String) extends Event - case class ChatLog(log: List[String]) extends Event - case class ChatMessage(from: String, message: String) extends Event - - /** - * Chat client. - */ - class ChatClient(val name: String) { - val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) - - def login = chat ! Login(name) - def logout = chat ! Logout(name) - def post(message: String) = chat ! ChatMessage(name, name + ": " + message) - def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) - } - - /** - * Internal chat client session. - */ - class Session(user: String, storage: ActorRef) extends Actor { - private val loginTime = System.currentTimeMillis - private var userLog: List[String] = Nil - - EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) - - def receive = { - case msg @ ChatMessage(from, message) => - userLog ::= message - storage ! msg - - case msg @ GetChatLog(_) => - storage forward msg - } - } - - /** - * Abstraction of chat storage holding the chat log. - */ - trait ChatStorage extends Actor - - /** - * Memory-backed chat storage implementation. - */ - class MemoryChatStorage extends ChatStorage { - private var chatLog = TransactionalVector[Array[Byte]]() - - EventHandler.info(this, "Memory-based chat storage is starting up...") - - def receive = { - case msg @ ChatMessage(from, message) => - EventHandler.debug(this, "New chat message [%s]".format(message)) - atomic { chatLog + message.getBytes("UTF-8") } - - case GetChatLog(_) => - val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } - reply(ChatLog(messageList)) - } - - override def postRestart(reason: Throwable) { - chatLog = TransactionalVector() - } - } - - /** - * Implements user session management. - *

- * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. - */ - trait SessionManagement { this: Actor => - - val storage: ActorRef // needs someone to provide the ChatStorage - val sessions = new HashMap[String, ActorRef] - - protected def sessionManagement: Receive = { - case Login(username) => - EventHandler.info(this, "User [%s] has logged in".format(username)) - val session = actorOf(new Session(username, storage)) - session - sessions += (username -> session) - - case Logout(username) => - EventHandler.info(this, "User [%s] has logged out".format(username)) - val session = sessions(username) - session.stop() - sessions -= username - } - - protected def shutdownSessions() { - sessions.foreach { case (_, session) => session.stop() } - } - } - - /** - * Implements chat management, e.g. chat message dispatch. - *

- * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. - */ - trait ChatManagement { this: Actor => - val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map - - protected def chatManagement: Receive = { - case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) - case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) - } - - private def getSession(from: String) : Option[ActorRef] = { - if (sessions.contains(from)) - Some(sessions(from)) - else { - EventHandler.info(this, "Session expired for %s".format(from)) - None - } - } - } - - /** - * Creates and links a MemoryChatStorage. - */ - trait MemoryChatStorageFactory { this: Actor => - val storage = actorOf(Props[MemoryChatStorage].withSupervisor(this.self)) // starts and links ChatStorage - } - - /** - * Chat server. Manages sessions and redirects all other messages to the Session for the client. - */ - trait ChatServer extends Actor { - //faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) - val storage: ActorRef - - EventHandler.info(this, "Chat server is starting up...") - - // actor message handler - def receive: Receive = sessionManagement orElse chatManagement - - // abstract methods to be defined somewhere else - protected def chatManagement: Receive - protected def sessionManagement: Receive - protected def shutdownSessions() - - override def postStop() { - EventHandler.info(this, "Chat server is shutting down...") - shutdownSessions() - storage.stop() - } - } - - /** - * Class encapsulating the full Chat Service. - * Start service by invoking: - *

-   * val chatService = Actor.actorOf[ChatService]
-   * 
- */ - class ChatService extends - ChatServer with - SessionManagement with - ChatManagement with - MemoryChatStorageFactory { - override def preStart() { - remote.start("localhost", 2552); - remote.register("chat:service", self) //Register the actor with the specified service id - } - } - - /** - * Test runner starting ChatService. - */ - object ServerRunner { - - def main(args: Array[String]) { ServerRunner.run() } - - def run() { - actorOf[ChatService] - } - } - - /** - * Test runner emulating a chat session. - */ - object ClientRunner { - - def main(args: Array[String]) { ClientRunner.run() } - - def run() { - - val client1 = new ChatClient("jonas") - client1.login - val client2 = new ChatClient("patrik") - client2.login - - client1.post("Hi there") - println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) - - client2.post("Hello") - println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) - - client1.post("Hi again") - println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) - - client1.logout - client2.logout - } - } +// REMOTING IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// +// package sample.chat +// +// import scala.collection.mutable.HashMap +// +// import akka.actor.{Actor, ActorRef, Props} +// import akka.stm._ +// import akka.actor.Actor._ +// import akka.event.EventHandler +// +// /****************************************************************************** +// Akka Chat Client/Server Sample Application +// +// How to run the sample: +// +// 1. Fire up two shells. For each of them: +// - Step down into to the root of the Akka distribution. +// - Set 'export AKKA_HOME=. +// - Run 'sbt console' to start up a REPL (interpreter). +// 2. In the first REPL you get execute: +// - scala> import sample.chat._ +// - scala> import akka.actor.Actor._ +// - scala> val chatService = actorOf[ChatService] +// 3. In the second REPL you get execute: +// - scala> import sample.chat._ +// - scala> ClientRunner.run +// 4. See the chat simulation run. +// 5. Run it again to see full speed after first initialization. +// 6. In the client REPL, or in a new REPL, you can also create your own client +// - scala> import sample.chat._ +// - scala> val myClient = new ChatClient("") +// - scala> myClient.login +// - scala> myClient.post("Can I join?") +// - scala> println("CHAT LOG:\n\t" + myClient.chatLog.log.mkString("\n\t")) +// +// +// That’s it. Have fun. +// +// ******************************************************************************/ +// +// /** +// * ChatServer's internal events. +// */ +// sealed trait Event +// case class Login(user: String) extends Event +// case class Logout(user: String) extends Event +// case class GetChatLog(from: String) extends Event +// case class ChatLog(log: List[String]) extends Event +// case class ChatMessage(from: String, message: String) extends Event +// +// /** +// * Chat client. +// */ +// class ChatClient(val name: String) { +// val chat = Actor.remote.actorFor("chat:service", "localhost", 2552) +// +// def login = chat ! Login(name) +// def logout = chat ! Logout(name) +// def post(message: String) = chat ! ChatMessage(name, name + ": " + message) +// def chatLog = (chat !! GetChatLog(name)).as[ChatLog].getOrElse(throw new Exception("Couldn't get the chat log from ChatServer")) +// } +// +// /** +// * Internal chat client session. +// */ +// class Session(user: String, storage: ActorRef) extends Actor { +// private val loginTime = System.currentTimeMillis +// private var userLog: List[String] = Nil +// +// EventHandler.info(this, "New session for user [%s] has been created at [%s]".format(user, loginTime)) +// +// def receive = { +// case msg @ ChatMessage(from, message) => +// userLog ::= message +// storage ! msg +// +// case msg @ GetChatLog(_) => +// storage forward msg +// } +// } +// +// /** +// * Abstraction of chat storage holding the chat log. +// */ +// trait ChatStorage extends Actor +// +// /** +// * Memory-backed chat storage implementation. +// */ +// class MemoryChatStorage extends ChatStorage { +// private var chatLog = TransactionalVector[Array[Byte]]() +// +// EventHandler.info(this, "Memory-based chat storage is starting up...") +// +// def receive = { +// case msg @ ChatMessage(from, message) => +// EventHandler.debug(this, "New chat message [%s]".format(message)) +// atomic { chatLog + message.getBytes("UTF-8") } +// +// case GetChatLog(_) => +// val messageList = atomic { chatLog.map(bytes => new String(bytes, "UTF-8")).toList } +// reply(ChatLog(messageList)) +// } +// +// override def postRestart(reason: Throwable) { +// chatLog = TransactionalVector() +// } +// } +// +// /** +// * Implements user session management. +// *

+// * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. +// */ +// trait SessionManagement { this: Actor => +// +// val storage: ActorRef // needs someone to provide the ChatStorage +// val sessions = new HashMap[String, ActorRef] +// +// protected def sessionManagement: Receive = { +// case Login(username) => +// EventHandler.info(this, "User [%s] has logged in".format(username)) +// val session = actorOf(new Session(username, storage)) +// session +// sessions += (username -> session) +// +// case Logout(username) => +// EventHandler.info(this, "User [%s] has logged out".format(username)) +// val session = sessions(username) +// session.stop() +// sessions -= username +// } +// +// protected def shutdownSessions() { +// sessions.foreach { case (_, session) => session.stop() } +// } +// } +// +// /** +// * Implements chat management, e.g. chat message dispatch. +// *

+// * Uses self-type annotation (this: Actor =>) to declare that it needs to be mixed in with an Actor. +// */ +// trait ChatManagement { this: Actor => +// val sessions: HashMap[String, ActorRef] // needs someone to provide the Session map +// +// protected def chatManagement: Receive = { +// case msg @ ChatMessage(from, _) => getSession(from).foreach(_ ! msg) +// case msg @ GetChatLog(from) => getSession(from).foreach(_ forward msg) +// } +// +// private def getSession(from: String) : Option[ActorRef] = { +// if (sessions.contains(from)) +// Some(sessions(from)) +// else { +// EventHandler.info(this, "Session expired for %s".format(from)) +// None +// } +// } +// } +// +// /** +// * Creates and links a MemoryChatStorage. +// */ +// trait MemoryChatStorageFactory { this: Actor => +// val storage = actorOf(Props[MemoryChatStorage].withSupervisor(this.self)) // starts and links ChatStorage +// } +// +// /** +// * Chat server. Manages sessions and redirects all other messages to the Session for the client. +// */ +// trait ChatServer extends Actor { +// //faultHandler = OneForOneStrategy(List(classOf[Exception]),5, 5000) +// val storage: ActorRef +// +// EventHandler.info(this, "Chat server is starting up...") +// +// // actor message handler +// def receive: Receive = sessionManagement orElse chatManagement +// +// // abstract methods to be defined somewhere else +// protected def chatManagement: Receive +// protected def sessionManagement: Receive +// protected def shutdownSessions() +// +// override def postStop() { +// EventHandler.info(this, "Chat server is shutting down...") +// shutdownSessions() +// storage.stop() +// } +// } +// +// /** +// * Class encapsulating the full Chat Service. +// * Start service by invoking: +// *

+//   * val chatService = Actor.actorOf[ChatService]
+//   * 
+// */ +// class ChatService extends +// ChatServer with +// SessionManagement with +// ChatManagement with +// MemoryChatStorageFactory { +// override def preStart() { +// remote.start("localhost", 2552); +// remote.register("chat:service", self) //Register the actor with the specified service id +// } +// } +// +// /** +// * Test runner starting ChatService. +// */ +// object ServerRunner { +// +// def main(args: Array[String]) { ServerRunner.run() } +// +// def run() { +// actorOf[ChatService] +// } +// } +// +// /** +// * Test runner emulating a chat session. +// */ +// object ClientRunner { +// +// def main(args: Array[String]) { ClientRunner.run() } +// +// def run() { +// +// val client1 = new ChatClient("jonas") +// client1.login +// val client2 = new ChatClient("patrik") +// client2.login +// +// client1.post("Hi there") +// println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) +// +// client2.post("Hello") +// println("CHAT LOG:\n\t" + client2.chatLog.log.mkString("\n\t")) +// +// client1.post("Hi again") +// println("CHAT LOG:\n\t" + client1.chatLog.log.mkString("\n\t")) +// +// client1.logout +// client2.logout +// } +// } +// diff --git a/akka-samples/akka-sample-fsm/src/README b/akka-samples/akka-sample-fsm/src/README new file mode 100644 index 0000000000..17971c005b --- /dev/null +++ b/akka-samples/akka-sample-fsm/src/README @@ -0,0 +1,30 @@ +FSM +=== + +Requirements +------------ + +To build and run FSM you need [Simple Build Tool][sbt] (sbt). + +Running +------- + +First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'. +Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run': +> cd $AKKA_HOME + +> % sbt + +> > update + +> > project akka-sample-fsm + +> > run + +> > Choose 1 or 2 depending on what sample you wish to run + +Notice +------ + +[akka]: http://akka.io +[sbt]: http://code.google.com/p/simple-build-tool/ diff --git a/akka-samples/akka-sample-fsm/src/main/scala/Buncher.scala b/akka-samples/akka-sample-fsm/src/main/scala/Buncher.scala index 0dcf33e401..d039609a98 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/Buncher.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/Buncher.scala @@ -1,3 +1,6 @@ +/** + * Copyright (C) 2009-2010 Typesafe Inc. . + */ package sample.fsm.buncher import akka.actor.ActorRefFactory @@ -6,15 +9,15 @@ import akka.util.Duration import akka.actor.{ FSM, Actor, ActorRef } /* - * generic typed object buncher. - * - * To instantiate it, use the factory method like so: - * Buncher(100, 500)(x : List[AnyRef] => x foreach println) - * which will yield a fully functional ActorRef. - * The type of messages allowed is strongly typed to match the - * supplied processing method; other messages are discarded (and - * possibly logged). - */ +* generic typed object buncher. +* +* To instantiate it, use the factory method like so: +* Buncher(100, 500)(x : List[AnyRef] => x foreach println) +* which will yield a fully functional ActorRef. +* The type of messages allowed is strongly typed to match the +* supplied processing method; other messages are discarded (and +* possibly logged). +*/ object GenericBuncher { trait State case object Idle extends State diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala index 2c23940c9f..3acbf473e6 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala @@ -1,3 +1,6 @@ +/** + * Copyright (C) 2009-2010 Typesafe Inc. . + */ package sample.fsm.dining.become //Akka adaptation of @@ -7,8 +10,8 @@ import akka.actor.{ ActorRef, Actor, ActorSystem } import akka.util.duration._ /* - * First we define our messages, they basically speak for themselves - */ +* First we define our messages, they basically speak for themselves +*/ sealed trait DiningHakkerMessage case class Busy(chopstick: ActorRef) extends DiningHakkerMessage case class Put(hakker: ActorRef) extends DiningHakkerMessage @@ -18,9 +21,9 @@ object Eat extends DiningHakkerMessage object Think extends DiningHakkerMessage /* - * A Chopstick is an actor, it can be taken, and put back - */ -class Chopstick(name: String) extends Actor { +* A Chopstick is an actor, it can be taken, and put back +*/ +class Chopstick extends Actor { //When a Chopstick is taken by a hakker //It will refuse to be taken by other hakkers @@ -44,8 +47,8 @@ class Chopstick(name: String) extends Actor { } /* - * A hakker is an awesome dude or dudett who either thinks about hacking or has to eat ;-) - */ +* A hakker is an awesome dude or dudett who either thinks about hacking or has to eat ;-) +*/ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { //When a hakker is thinking it can become hungry @@ -75,7 +78,7 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { //back to think about how he should obtain his chopsticks :-) def waiting_for(chopstickToWaitFor: ActorRef, otherChopstick: ActorRef): Receive = { case Taken(`chopstickToWaitFor`) ⇒ - println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) + println("%s has picked up %s and %s and starts to eat".format(name, left.name, right.name)) become(eating) system.scheduler.scheduleOnce(self, Think, 5 seconds) @@ -105,27 +108,33 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { become(thinking) left ! Put(self) right ! Put(self) - println("%s puts down his chopsticks and starts to think", name) + println("%s puts down his chopsticks and starts to think".format(name)) system.scheduler.scheduleOnce(self, Eat, 5 seconds) } //All hakkers start in a non-eating state def receive = { case Think ⇒ - println("%s starts to think", name) + println("%s starts to think".format(name)) become(thinking) system.scheduler.scheduleOnce(self, Eat, 5 seconds) } } /* - * Alright, here's our test-harness - */ +* Alright, here's our test-harness +*/ object DiningHakkers { val system = ActorSystem() + + def main(args: Array[String]) { + run + } + def run { //Create 5 chopsticks - val chopsticks = for (i ← 1 to 5) yield system.actorOf(new Chopstick("Chopstick " + i)) + val chopsticks = for (i ← 1 to 5) yield system.actorOf[Chopstick]("Chopstick " + i) + //Create 5 awesome hakkers and assign them their left and right chopstick val hakkers = for { (name, i) ← List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala index 987f630784..d0c8bca54a 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala @@ -1,3 +1,6 @@ +/** + * Copyright (C) 2009-2010 Typesafe Inc. . + */ package sample.fsm.dining.fsm import akka.actor.{ ActorRef, Actor, FSM, ActorSystem } @@ -6,8 +9,8 @@ import akka.util.Duration import akka.util.duration._ /* - * Some messages for the chopstick - */ +* Some messages for the chopstick +*/ sealed trait ChopstickMessage object Take extends ChopstickMessage object Put extends ChopstickMessage @@ -27,9 +30,9 @@ case object Taken extends ChopstickState case class TakenBy(hakker: ActorRef) /* - * A chopstick is an actor, it can be taken, and put back - */ -class Chopstick(name: String) extends Actor with FSM[ChopstickState, TakenBy] { +* A chopstick is an actor, it can be taken, and put back +*/ +class Chopstick extends Actor with FSM[ChopstickState, TakenBy] { // A chopstick begins its existence as available and taken by no one startWith(Available, TakenBy(system.deadLetters)) @@ -77,8 +80,8 @@ case object Eating extends FSMHakkerState case class TakenChopsticks(left: Option[ActorRef], right: Option[ActorRef]) /* - * A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-) - */ +* A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-) +*/ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] { //All hakkers start waiting @@ -86,7 +89,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit when(Waiting) { case Event(Think, _) ⇒ - println("%s starts to think", name) + println("%s starts to think".format(name)) startThinking(5 seconds) } @@ -125,7 +128,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit } private def startEating(left: ActorRef, right: ActorRef): State = { - println("%s has picked up %s and %s, and starts to eat", name, left.address, right.address) + println("%s has picked up %s and %s and starts to eat".format(name, left.name, right.name)) goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5 seconds) } @@ -144,7 +147,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit // then he puts down his chopsticks and starts to think when(Eating) { case Event(StateTimeout, _) ⇒ - println("%s puts down his chopsticks and starts to think", name) + println("%s puts down his chopsticks and starts to think".format(name)) left ! Put right ! Put startThinking(5 seconds) @@ -159,15 +162,19 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit } /* - * Alright, here's our test-harness - */ +* Alright, here's our test-harness +*/ object DiningHakkersOnFsm { val system = ActorSystem() + def main(args: Array[String]) { + run + } + def run = { // Create 5 chopsticks - val chopsticks = for (i ← 1 to 5) yield system.actorOf(new Chopstick("Chopstick " + i)) + val chopsticks = for (i ← 1 to 5) yield system.actorOf[Chopstick]("Chopstick " + i) // Create 5 awesome fsm hakkers and assign them their left and right chopstick val hakkers = for { (name, i) ← List("Ghosh", "Bonér", "Klang", "Krasser", "Manie").zipWithIndex diff --git a/akka-samples/akka-sample-hello/config/akka.conf b/akka-samples/akka-sample-hello/config/akka.conf deleted file mode 100644 index 5b8920874f..0000000000 --- a/akka-samples/akka-sample-hello/config/akka.conf +++ /dev/null @@ -1,27 +0,0 @@ -#################### -# Akka Config File # -#################### - -akka { - version = "2.0-SNAPSHOT" - - enabled-modules = ["http"] - - time-unit = "seconds" - - event-handlers = ["akka.event.EventHandler$DefaultListener"] - - boot = ["sample.hello.Boot"] - - http { - hostname = "localhost" - port = 9998 - - connection-close = true - root-actor-id = "_httproot" - root-actor-builtin = true - timeout = 1000 - expired-header-name = "Async-Timeout" - expired-header-value = "expired" - } -} diff --git a/akka-samples/akka-sample-hello/config/microkernel-server.xml b/akka-samples/akka-sample-hello/config/microkernel-server.xml deleted file mode 100644 index 4f86dab23c..0000000000 --- a/akka-samples/akka-sample-hello/config/microkernel-server.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - 300000 - 2 - false - 8443 - 20000 - 5000 - - - - - - - - - - - - - - / - - akka.http.AkkaMistServlet - /* - - - - - - - - - - - - - - - true - true - true - 1000 - - diff --git a/akka-samples/akka-sample-hello/src/README b/akka-samples/akka-sample-hello/src/README new file mode 100644 index 0000000000..cd271f1dde --- /dev/null +++ b/akka-samples/akka-sample-hello/src/README @@ -0,0 +1,28 @@ +HELLO +===== + +Requirements +------------ + +To build and run FSM you need [Simple Build Tool][sbt] (sbt). + +Running +------- + +First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'. +Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run': +> cd $AKKA_HOME + +> % sbt + +> > update + +> > project akka-sample-hello + +> > run + +Notice +------ + +[akka]: http://akka.io +[sbt]: http://code.google.com/p/simple-build-tool/ diff --git a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Boot.scala b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Boot.scala deleted file mode 100644 index 149c6a3ee4..0000000000 --- a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Boot.scala +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package sample.hello - -import akka.actor._ -import akka.http._ - -class Boot { - val supervisor = Supervisor(OneForOneStrategy(List(classOf[Exception]), 3, 100)) - Actor.actorOf(Props[RootEndpoint].withSupervisor(supervisor)) - Actor.actorOf(Props[HelloEndpoint].withSupervisor(supervisor)) -} diff --git a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/HelloEndpoint.scala b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/HelloEndpoint.scala deleted file mode 100644 index 2ea8c1fe83..0000000000 --- a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/HelloEndpoint.scala +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package sample.hello - -import akka.actor._ -import akka.http._ - -import java.text.DateFormat -import java.util.Date - -class HelloEndpoint extends Actor with Endpoint { - self.dispatcher = Endpoint.Dispatcher - - lazy val hello = Actor.actorOf( - new Actor { - def time = DateFormat.getTimeInstance.format(new Date) - def receive = { - case get: Get => get OK "Hello at " + time - } - }) - - def hook: Endpoint.Hook = { case _ => hello } - - override def preStart = Actor.registry.actorFor(MistSettings.RootActorID).get ! Endpoint.Attach(hook) - - def receive = handleHttpRequest -} diff --git a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala new file mode 100644 index 0000000000..e3399e86fc --- /dev/null +++ b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package sample.hello + +import akka.actor.{ ActorSystem, Actor } + +case object Start + +object Main { + def main(args: Array[String]) { + val system = ActorSystem() + system.actorOf[HelloActor] ! Start + } +} + +class HelloActor extends Actor { + val worldActor = system.actorOf[WorldActor] + def receive = { + case Start ⇒ worldActor ! "Hello" + case s: String ⇒ + println("Received message: %s".format(s)) + system.stop() + } +} + +class WorldActor extends Actor { + def receive = { + case s: String ⇒ sender ! s.toUpperCase + " world!" + } +} + diff --git a/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala b/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala index 3e2d7af049..bf257ffd49 100644 --- a/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala +++ b/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala @@ -3,29 +3,27 @@ */ package sample.osgi -import akka.actor.Actor -import akka.actor.Actor._ - import org.osgi.framework.{ BundleActivator, BundleContext } +import akka.actor.{ Timeout, ActorSystem, Actor } class Activator extends BundleActivator { + val system = ActorSystem() def start(context: BundleContext) { println("Starting the OSGi example ...") - val echo = actorOf[EchoActor] - val answer = (echo ? "OSGi example").as[String] + val echo = system.actorOf[EchoActor] + val answer = (echo ? ("OSGi example", Timeout(100))).as[String] println(answer getOrElse "No answer!") } def stop(context: BundleContext) { - Actor.registry.local.shutdownAll() + system.stop() println("Stopped the OSGi example.") } } class EchoActor extends Actor { - override def receive = { - case x => reply(x) + case x ⇒ sender ! x } } diff --git a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala index 84a201f530..9b3a27a7ae 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala @@ -2,35 +2,37 @@ * Copyright (C) 2009-2011 Typesafe Inc. */ -package sample.remote - -import akka.actor.Actor._ -import akka.actor. {ActorRegistry, Actor} - -class HelloWorldActor extends Actor { - def receive = { - case "Hello" => - reply("World") - } -} - -object ServerManagedRemoteActorServer { - - def run = { - Actor.remote.start("localhost", 2552) - Actor.remote.register("hello-service", actorOf[HelloWorldActor]) - } - - def main(args: Array[String]) = run -} - -object ServerManagedRemoteActorClient { - - def run = { - val actor = Actor.remote.actorFor("hello-service", "localhost", 2552) - val result = actor !! "Hello" - } - - def main(args: Array[String]) = run -} +// REMOTING IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +//package sample.remote +// +//import akka.actor.Actor._ +//import akka.actor. {ActorRegistry, Actor} +// +//class HelloWorldActor extends Actor { +// def receive = { +// case "Hello" => +// reply("World") +// } +//} +// +//object ServerManagedRemoteActorServer { +// +// def run = { +// Actor.remote.start("localhost", 2552) +// Actor.remote.register("hello-service", actorOf[HelloWorldActor]) +// } +// +// def main(args: Array[String]) = run +//} +// +//object ServerManagedRemoteActorClient { +// +// def run = { +// val actor = Actor.remote.actorFor("hello-service", "localhost", 2552) +// val result = actor !! "Hello" +// } +// +// def main(args: Array[String]) = run +//} +// diff --git a/akka-tutorials/akka-tutorial-first/pom.xml b/akka-tutorials/akka-tutorial-first/pom.xml index 8e25d972f3..1cec835a9c 100644 --- a/akka-tutorials/akka-tutorial-first/pom.xml +++ b/akka-tutorials/akka-tutorial-first/pom.xml @@ -13,7 +13,7 @@ - se.scalablesolutions.akka + com.typesafe.akka akka-actor 2.0-SNAPSHOT diff --git a/akka-tutorials/akka-tutorial-first/project/TutorialBuild.scala b/akka-tutorials/akka-tutorial-first/project/TutorialBuild.scala new file mode 100644 index 0000000000..5e5ef32493 --- /dev/null +++ b/akka-tutorials/akka-tutorial-first/project/TutorialBuild.scala @@ -0,0 +1,22 @@ +import sbt._ +import Keys._ + +object TutorialBuild extends Build { + lazy val buildSettings = Seq( + organization := "com.typesafe.akka", + version := "2.0-SNAPSHOT", + scalaVersion := "2.9.1" + ) + + lazy val akka = Project( + id = "akka-tutorial-first", + base = file("."), + settings = Defaults.defaultSettings ++ Seq( + libraryDependencies ++= Seq( + "com.typesafe.akka" % "akka-actor" % "2.0-SNAPSHOT", + "junit" % "junit" % "4.5" % "test", + "org.scalatest" % "scalatest_2.9.0" % "1.6.1" % "test", + "com.typesafe.akka" % "akka-testkit" % "2.0-SNAPSHOT" % "test") + ) + ) +} \ No newline at end of file diff --git a/akka-tutorials/akka-tutorial-first/project/build.properties b/akka-tutorials/akka-tutorial-first/project/build.properties index 4981c1c2c3..c6158f7be4 100644 --- a/akka-tutorials/akka-tutorial-first/project/build.properties +++ b/akka-tutorials/akka-tutorial-first/project/build.properties @@ -1,5 +1 @@ -project.organization=se.scalablesolutions.akka -project.name=akka-tutorial-first -project.version=2.0-SNAPSHOT -build.scala.versions=2.9.0 -sbt.version=0.7.7 +sbt.version=0.11.0 \ No newline at end of file diff --git a/akka-tutorials/akka-tutorial-first/project/build/Project.scala b/akka-tutorials/akka-tutorial-first/project/build/Project.scala deleted file mode 100644 index 975f2ce970..0000000000 --- a/akka-tutorials/akka-tutorial-first/project/build/Project.scala +++ /dev/null @@ -1,3 +0,0 @@ -import sbt._ - -class TutorialOneProject(info: ProjectInfo) extends DefaultProject(info) with AkkaProject diff --git a/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala b/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala deleted file mode 100644 index fb121fcd3e..0000000000 --- a/akka-tutorials/akka-tutorial-first/project/plugins/Plugins.scala +++ /dev/null @@ -1,6 +0,0 @@ -import sbt._ - -class Plugins(info: ProjectInfo) extends PluginDefinition(info) { - val akkaRepo = "Akka Repo" at "http://akka.io/repository" - val akkaPlugin = "se.scalablesolutions.akka" % "akka-sbt-plugin" % "2.0-SNAPSHOT" -} diff --git a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java index ca8fe597f7..d6b9e8c1f1 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java +++ b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java @@ -1,182 +1,184 @@ -// * -// * Copyright (C) 2009-2011 Typesafe Inc. +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.tutorial.first.java; -// package akka.tutorial.first.java; +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.UntypedActor; +import akka.actor.UntypedActorFactory; +import akka.japi.Creator; +import akka.routing.*; -// import static akka.actor.Actors.poisonPill; -// import static java.util.Arrays.asList; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; -// import akka.actor.ActorRef; -// import akka.actor.Actors; -// import akka.actor.ActorSystem; -// import akka.actor.UntypedActor; -// import akka.actor.UntypedActorFactory; -// import akka.routing.RoutedProps; -// import akka.routing.RouterType; -// import akka.routing.LocalConnectionManager; -// import akka.routing.Routing; -// import akka.routing.Routing.Broadcast; -// import scala.collection.JavaConversions; +public class Pi { -// import java.util.LinkedList; -// import java.util.concurrent.CountDownLatch; + private static final ActorSystem system = ActorSystem.apply(); -// public class Pi { + public static void main(String[] args) throws Exception { + Pi pi = new Pi(); + pi.calculate(4, 10000, 10000); + } -// private static final ActorSystem system = new ActorSystem(); + // ==================== + // ===== Messages ===== + // ==================== + static class Calculate { + } -// public static void main(String[] args) throws Exception { -// Pi pi = new Pi(); -// pi.calculate(4, 10000, 10000); -// } + static class Work { + private final int start; + private final int nrOfElements; -// // ==================== -// // ===== Messages ===== -// // ==================== -// static class Calculate {} + public Work(int start, int nrOfElements) { + this.start = start; + this.nrOfElements = nrOfElements; + } -// static class Work { -// private final int start; -// private final int nrOfElements; + public int getStart() { + return start; + } -// public Work(int start, int nrOfElements) { -// this.start = start; -// this.nrOfElements = nrOfElements; -// } + public int getNrOfElements() { + return nrOfElements; + } + } -// public int getStart() { return start; } -// public int getNrOfElements() { return nrOfElements; } -// } + static class Result { + private final double value; -// static class Result { -// private final double value; + public Result(double value) { + this.value = value; + } -// public Result(double value) { -// this.value = value; -// } + public double getValue() { + return value; + } + } -// public double getValue() { return value; } -// } + // ================== + // ===== Worker ===== + // ================== + public static class Worker extends UntypedActor { -// // ================== -// // ===== Worker ===== -// // ================== -// static class Worker extends UntypedActor { + // define the work + private double calculatePiFor(int start, int nrOfElements) { + double acc = 0.0; + for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1); + } + return acc; + } -// // define the work -// private double calculatePiFor(int start, int nrOfElements) { -// double acc = 0.0; -// for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) { -// acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1); -// } -// return acc; -// } + // message handler + public void onReceive(Object message) { + if (message instanceof Work) { + Work work = (Work) message; -// // message handler -// public void onReceive(Object message) { -// if (message instanceof Work) { -// Work work = (Work) message; + // perform the work + double result = calculatePiFor(work.getStart(), work.getNrOfElements()); -// // perform the work -// double result = calculatePiFor(work.getStart(), work.getNrOfElements()); + // reply with the result + getSender().tell(new Result(result)); -// // reply with the result -// getSender().tell(new Result(result)); + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); + } + } -// } else throw new IllegalArgumentException("Unknown message [" + message + "]"); -// } -// } + // ================== + // ===== Master ===== + // ================== + public static class Master extends UntypedActor { + private final int nrOfMessages; + private final int nrOfElements; + private final CountDownLatch latch; -// // ================== -// // ===== Master ===== -// // ================== -// static class Master extends UntypedActor { -// private final int nrOfMessages; -// private final int nrOfElements; -// private final CountDownLatch latch; + private double pi; + private int nrOfResults; + private long start; -// private double pi; -// private int nrOfResults; -// private long start; + private ActorRef router; -// private ActorRef router; + public Master(final int nrOfWorkers, int nrOfMessages, int nrOfElements, CountDownLatch latch) { + this.nrOfMessages = nrOfMessages; + this.nrOfElements = nrOfElements; + this.latch = latch; + Creator routerCreator = new Creator() { + public Router create() { + return new RoundRobinRouter(); + } + }; + LinkedList actors = new LinkedList() { + { + for (int i = 0; i < nrOfWorkers; i++) add(system.actorOf(Worker.class)); + } + }; + RoutedProps props = new RoutedProps(routerCreator, new LocalConnectionManager(actors), new akka.actor.Timeout(-1), true); + router = new RoutedActorRef(system, props, getSelf(), "pi"); + } -// public Master(int nrOfWorkers, int nrOfMessages, int nrOfElements, CountDownLatch latch) { -// this.nrOfMessages = nrOfMessages; -// this.nrOfElements = nrOfElements; -// this.latch = latch; + // message handler + public void onReceive(Object message) { -// LinkedList workers = new LinkedList(); -// for (int i = 0; i < nrOfWorkers; i++) { -// ActorRef worker = system.actorOf(Worker.class); -// workers.add(worker); -// } + if (message instanceof Calculate) { -// router = system.actorOf(new RoutedProps().withRoundRobinRouter().withLocalConnections(workers), "pi"); -// } + // schedule work + for (int start = 0; start < nrOfMessages; start++) { + router.tell(new Work(start, nrOfElements), getSelf()); + } -// // message handler -// public void onReceive(Object message) { + } else if (message instanceof Result) { -// if (message instanceof Calculate) { -// // schedule work -// for (int start = 0; start < nrOfMessages; start++) { -// router.tell(new Work(start, nrOfElements), getSelf()); -// } + // handle result from the worker + Result result = (Result) message; + pi += result.getValue(); + nrOfResults += 1; + if (nrOfResults == nrOfMessages) getSelf().stop(); -// // send a PoisonPill to all workers telling them to shut down themselves -// router.tell(new Broadcast(poisonPill())); + } else throw new IllegalArgumentException("Unknown message [" + message + "]"); + } -// // send a PoisonPill to the router, telling him to shut himself down -// router.tell(poisonPill()); + @Override + public void preStart() { + start = System.currentTimeMillis(); + } -// } else if (message instanceof Result) { + @Override + public void postStop() { + // tell the world that the calculation is complete + System.out.println(String.format( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", + pi, (System.currentTimeMillis() - start))); + latch.countDown(); + } + } -// // handle result from the worker -// Result result = (Result) message; -// pi += result.getValue(); -// nrOfResults += 1; -// if (nrOfResults == nrOfMessages) getSelf().stop(); + // ================== + // ===== Run it ===== + // ================== + public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) + throws Exception { -// } else throw new IllegalArgumentException("Unknown message [" + message + "]"); -// } + // this latch is only plumbing to know when the calculation is completed + final CountDownLatch latch = new CountDownLatch(1); -// @Override -// public void preStart() { -// start = System.currentTimeMillis(); -// } + // create the master + ActorRef master = system.actorOf(new UntypedActorFactory() { + public UntypedActor create() { + return new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch); + } + }); -// @Override -// public void postStop() { -// // tell the world that the calculation is complete -// System.out.println(String.format( -// "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis", -// pi, (System.currentTimeMillis() - start))); -// latch.countDown(); -// } -// } + // start the calculation + master.tell(new Calculate()); -// // ================== -// // ===== Run it ===== -// // ================== -// public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) -// throws Exception { + // wait for master to shut down + latch.await(); -// // this latch is only plumbing to know when the calculation is completed -// final CountDownLatch latch = new CountDownLatch(1); - -// // create the master -// ActorRef master = system.actorOf(new UntypedActorFactory() { -// public UntypedActor create() { -// return new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch); -// } -// }); - -// // start the calculation -// master.tell(new Calculate()); - -// // wait for master to shut down -// latch.await(); -// } -// } + // Shut down the system + system.stop(); + } +} diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index 836f766e12..d7f932a053 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -1,113 +1,114 @@ -// /** -// * Copyright (C) 2009-2011 Typesafe Inc. -// */ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.tutorial.first.scala -// package akka.tutorial.first.scala +import java.util.concurrent.CountDownLatch +import akka.routing.{ RoutedActorRef, LocalConnectionManager, RoundRobinRouter, RoutedProps } +import akka.actor.{ ActorSystemImpl, Actor, ActorSystem } -// import akka.actor.{ Actor, PoisonPill, ActorSystem } -// import Actor._ -// import java.util.concurrent.CountDownLatch -// import akka.routing.Routing.Broadcast -// import akka.routing.{ RoutedProps, Routing } +object Pi extends App { -// object Pi extends App { + val system = ActorSystem() -// val system = ActorSystem() + // Initiate the calculation + calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) -// calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) + // ==================== + // ===== Messages ===== + // ==================== + sealed trait PiMessage -// // ==================== -// // ===== Messages ===== -// // ==================== -// sealed trait PiMessage + case object Calculate extends PiMessage -// case object Calculate extends PiMessage + case class Work(start: Int, nrOfElements: Int) extends PiMessage -// case class Work(start: Int, nrOfElements: Int) extends PiMessage + case class Result(value: Double) extends PiMessage -// case class Result(value: Double) extends PiMessage + // ================== + // ===== Worker ===== + // ================== + class Worker extends Actor { -// // ================== -// // ===== Worker ===== -// // ================== -// class Worker extends Actor { + // define the work + def calculatePiFor(start: Int, nrOfElements: Int): Double = { + var acc = 0.0 + for (i ← start until (start + nrOfElements)) + acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) + acc + } -// // define the work -// def calculatePiFor(start: Int, nrOfElements: Int): Double = { -// var acc = 0.0 -// for (i ← start until (start + nrOfElements)) -// acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1) -// acc -// } + def receive = { + case Work(start, nrOfElements) ⇒ sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work + } + } -// def receive = { -// case Work(start, nrOfElements) ⇒ sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work -// } -// } + // ================== + // ===== Master ===== + // ================== + class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) + extends Actor { -// // ================== -// // ===== Master ===== -// // ================== -// class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, latch: CountDownLatch) -// extends Actor { + var pi: Double = _ + var nrOfResults: Int = _ + var start: Long = _ -// var pi: Double = _ -// var nrOfResults: Int = _ -// var start: Long = _ + // create the workers + val workers = Vector.fill(nrOfWorkers)(system.actorOf[Worker]) -// // create the workers -// val workers = Vector.fill(nrOfWorkers)(system.actorOf[Worker]) + // wrap them with a load-balancing router + val props = RoutedProps(routerFactory = () ⇒ new RoundRobinRouter, connectionManager = new LocalConnectionManager(workers)) + val router = new RoutedActorRef(system, props, self, "pi") -// // wrap them with a load-balancing router -// val router = system.actorOf(RoutedProps().withRoundRobinRouter.withLocalConnections(workers), "pi") + // message handler + def receive = { + case Calculate ⇒ + // schedule work + for (i ← 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + case Result(value) ⇒ + // handle result from the worker + pi += value + nrOfResults += 1 -// // message handler -// def receive = { -// case Calculate ⇒ -// // schedule work -// for (i ← 0 until nrOfMessages) router ! Work(i * nrOfElements, nrOfElements) + // Stop this actor and all its supervised children + if (nrOfResults == nrOfMessages) self.stop() + } -// // send a PoisonPill to all workers telling them to shut down themselves -// router ! Broadcast(PoisonPill) + override def preStart() { + start = System.currentTimeMillis + } -// // send a PoisonPill to the router, telling him to shut himself down -// router ! PoisonPill + override def postStop() { + // tell the world that the calculation is complete + println( + "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" + .format(pi, (System.currentTimeMillis - start))) + latch.countDown() + } + } -// case Result(value) ⇒ -// // handle result from the worker -// pi += value -// nrOfResults += 1 -// if (nrOfResults == nrOfMessages) self.stop() -// } + object Master { + val impl = system.asInstanceOf[ActorSystemImpl] + } -// override def preStart() { -// start = System.currentTimeMillis -// } + // ================== + // ===== Run it ===== + // ================== + def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { -// override def postStop() { -// // tell the world that the calculation is complete -// println( -// "\n\tPi estimate: \t\t%s\n\tCalculation time: \t%s millis" -// .format(pi, (System.currentTimeMillis - start))) -// latch.countDown() -// } -// } + // this latch is only plumbing to know when the calculation is completed + val latch = new CountDownLatch(1) -// // ================== -// // ===== Run it ===== -// // ================== -// def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + // create the master + val master = system.actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)) -// // this latch is only plumbing to know when the calculation is completed -// val latch = new CountDownLatch(1) + // start the calculation + master ! Calculate -// // create the master -// val master = system.actorOf(new Master(nrOfWorkers, nrOfMessages, nrOfElements, latch)) + // wait for master to shut down + latch.await() -// // start the calculation -// master ! Calculate - -// // wait for master to shut down -// latch.await() -// } -// } + // Shut down the system + system.stop() + } +} diff --git a/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala new file mode 100644 index 0000000000..608ba55481 --- /dev/null +++ b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.tutorial.first.scala + +import org.junit.runner.RunWith +import org.scalatest.matchers.MustMatchers +import org.scalatest.WordSpec +import akka.testkit.TestActorRef +import akka.tutorial.first.scala.Pi.Worker +import akka.actor.ActorSystem + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class WorkerSpec extends WordSpec with MustMatchers { + + implicit def system = ActorSystem() + + "Worker" must { + "calculate pi correctly" in { + val testActor = TestActorRef[Worker] + val actor = testActor.underlyingActor + actor.calculatePiFor(0, 0) must equal(0.0) + actor.calculatePiFor(1, 1) must equal(-1.3333333333333333) + } + } +} \ No newline at end of file diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index dd09fe3c80..e4fb06a8cc 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -243,23 +243,31 @@ object AkkaBuild extends Build { id = "akka-samples", base = file("akka-samples"), settings = parentSettings, - aggregate = Seq(fsmSample) - // aggregate = Seq(fsmSample, camelSample) + aggregate = Seq(antsSample, helloSample, osgiSample, fsmSample) ) - // lazy val antsSample = Project( - // id = "akka-sample-ants", - // base = file("akka-samples/akka-sample-ants"), - // dependencies = Seq(stm), - // settings = defaultSettings - // ) + lazy val antsSample = Project( + id = "akka-sample-ants", + base = file("akka-samples/akka-sample-ants"), + dependencies = Seq(actor, stm), + settings = defaultSettings + ) - // lazy val chatSample = Project( - // id = "akka-sample-chat", - // base = file("akka-samples/akka-sample-chat"), - // dependencies = Seq(cluster), - // settings = defaultSettings - // ) + lazy val helloSample = Project( + id = "akka-sample-hello", + base = file("akka-samples/akka-sample-hello"), + dependencies = Seq(actor), + settings = defaultSettings + ) + + lazy val osgiSample = Project( + id = "akka-sample-osgi", + base = file("akka-samples/akka-sample-osgi"), + dependencies = Seq(actor), + settings = defaultSettings ++ Seq( + libraryDependencies ++= Dependencies.sampleOsgi + ) + ) lazy val fsmSample = Project( id = "akka-sample-fsm", @@ -268,6 +276,21 @@ object AkkaBuild extends Build { settings = defaultSettings ) + // lazy val chatSample = Project( + // id = "akka-sample-chat", + // base = file("akka-samples/akka-sample-chat"), + // dependencies = Seq(cluster), + // settings = defaultSettings + // ) + + // lazy val samples = Project( + // id = "akka-samples", + // base = file("akka-samples"), + // settings = parentSettings, + // aggregate = Seq(fsmSample) + // // aggregate = Seq(fsmSample, camelSample) + // ) + // lazy val camelSample = Project( // id = "akka-sample-camel", // base = file("akka-samples/akka-sample-camel"), @@ -277,13 +300,6 @@ object AkkaBuild extends Build { // ) // ) - // lazy val helloSample = Project( - // id = "akka-sample-hello", - // base = file("akka-samples/akka-sample-hello"), - // dependencies = Seq(kernel), - // settings = defaultSettings - // ) - // lazy val remoteSample = Project( // id = "akka-sample-remote", // base = file("akka-samples/akka-sample-remote"), @@ -295,22 +311,24 @@ object AkkaBuild extends Build { id = "akka-tutorials", base = file("akka-tutorials"), settings = parentSettings, - aggregate = Seq(firstTutorial, secondTutorial) + aggregate = Seq(firstTutorial) ) lazy val firstTutorial = Project( id = "akka-tutorial-first", base = file("akka-tutorials/akka-tutorial-first"), - dependencies = Seq(actor), - settings = defaultSettings + dependencies = Seq(actor, testkit), + settings = defaultSettings ++ Seq( + libraryDependencies ++= Dependencies.tutorials + ) ) - lazy val secondTutorial = Project( - id = "akka-tutorial-second", - base = file("akka-tutorials/akka-tutorial-second"), - dependencies = Seq(actor), - settings = defaultSettings - ) + // lazy val secondTutorial = Project( + // id = "akka-tutorial-second", + // base = file("akka-tutorials/akka-tutorial-second"), + // dependencies = Seq(actor), + // settings = defaultSettings + // ) lazy val docs = Project( id = "akka-docs", @@ -448,6 +466,10 @@ object Dependencies { // val sampleCamel = Seq(camelCore, camelSpring, commonsCodec, Runtime.camelJms, Runtime.activemq, Runtime.springJms, // Test.junit, Test.scalatest, Test.logback) + val sampleOsgi = Seq(osgi) + + val tutorials = Seq(Test.scalatest, Test.junit) + val docs = Seq(Test.scalatest, Test.junit) } From c1f9e764a946aee5322751fb4ec4cdfd5af44eae Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Tue, 29 Nov 2011 12:41:52 +0100 Subject: [PATCH 05/30] Replaced removed visibility, see #1278 --- akka-actor/src/main/scala/akka/routing/Routing.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 8257002a42..424321e2c0 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -174,7 +174,7 @@ abstract private[akka] class AbstractRoutedActorRef(val system: ActorSystem, val * A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to send a message to * on (or more) of these actors. */ -class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, override val name: String) extends AbstractRoutedActorRef(system, routedProps) { +private[akka] class RoutedActorRef(system: ActorSystem, val routedProps: RoutedProps, val supervisor: ActorRef, override val name: String) extends AbstractRoutedActorRef(system, routedProps) { val path = supervisor.path / name From 1b3ee0828736bc6e4a96c39517efa2c736eda0f5 Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Tue, 29 Nov 2011 15:19:58 +0100 Subject: [PATCH 06/30] Updated after comments, see #1278 --- akka-samples/akka-sample-ants/README.md | 2 -- akka-samples/akka-sample-fsm/src/README | 2 -- .../src/main/scala/DiningHakkersOnBecome.scala | 2 +- .../src/main/scala/DiningHakkersOnFsm.scala | 2 +- akka-samples/akka-sample-hello/src/README | 2 -- .../src/main/scala/sample/hello/Main.scala | 2 +- .../src/main/java/akka/tutorial/first/java/Pi.java | 8 +++----- .../akka-tutorial-first/src/main/scala/Pi.scala | 7 +------ .../akka-tutorial-first/src/test/scala/WorkerSpec.scala | 2 +- 9 files changed, 8 insertions(+), 21 deletions(-) diff --git a/akka-samples/akka-sample-ants/README.md b/akka-samples/akka-sample-ants/README.md index 5a416e0ba4..2eaced5663 100644 --- a/akka-samples/akka-sample-ants/README.md +++ b/akka-samples/akka-sample-ants/README.md @@ -19,8 +19,6 @@ Here is an example. First type 'sbt' to start SBT interactively, the run 'update > % sbt -> > update - > > project akka-sample-ants > > run diff --git a/akka-samples/akka-sample-fsm/src/README b/akka-samples/akka-sample-fsm/src/README index 17971c005b..1391071f0b 100644 --- a/akka-samples/akka-sample-fsm/src/README +++ b/akka-samples/akka-sample-fsm/src/README @@ -15,8 +15,6 @@ Here is an example. First type 'sbt' to start SBT interactively, the run 'update > % sbt -> > update - > > project akka-sample-fsm > > run diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala index 3acbf473e6..78449edc1b 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala @@ -127,7 +127,7 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor { object DiningHakkers { val system = ActorSystem() - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { run } diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala index d0c8bca54a..b9cea1fe8f 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala @@ -168,7 +168,7 @@ object DiningHakkersOnFsm { val system = ActorSystem() - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { run } diff --git a/akka-samples/akka-sample-hello/src/README b/akka-samples/akka-sample-hello/src/README index cd271f1dde..81a6db8d3e 100644 --- a/akka-samples/akka-sample-hello/src/README +++ b/akka-samples/akka-sample-hello/src/README @@ -15,8 +15,6 @@ Here is an example. First type 'sbt' to start SBT interactively, the run 'update > % sbt -> > update - > > project akka-sample-hello > > run diff --git a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala index e3399e86fc..5df2661800 100644 --- a/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala +++ b/akka-samples/akka-sample-hello/src/main/scala/sample/hello/Main.scala @@ -8,7 +8,7 @@ import akka.actor.{ ActorSystem, Actor } case object Start object Main { - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val system = ActorSystem() system.actorOf[HelloActor] ! Start } diff --git a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java index d6b9e8c1f1..c8e9d88121 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java +++ b/akka-tutorials/akka-tutorial-first/src/main/java/akka/tutorial/first/java/Pi.java @@ -16,8 +16,6 @@ import java.util.concurrent.CountDownLatch; public class Pi { - private static final ActorSystem system = ActorSystem.apply(); - public static void main(String[] args) throws Exception { Pi pi = new Pi(); pi.calculate(4, 10000, 10000); @@ -113,18 +111,17 @@ public class Pi { }; LinkedList actors = new LinkedList() { { - for (int i = 0; i < nrOfWorkers; i++) add(system.actorOf(Worker.class)); + for (int i = 0; i < nrOfWorkers; i++) add(context().actorOf(Worker.class)); } }; RoutedProps props = new RoutedProps(routerCreator, new LocalConnectionManager(actors), new akka.actor.Timeout(-1), true); - router = new RoutedActorRef(system, props, getSelf(), "pi"); + router = new RoutedActorRef(system(), props, getSelf(), "pi"); } // message handler public void onReceive(Object message) { if (message instanceof Calculate) { - // schedule work for (int start = 0; start < nrOfMessages; start++) { router.tell(new Work(start, nrOfElements), getSelf()); @@ -161,6 +158,7 @@ public class Pi { // ================== public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) throws Exception { + final ActorSystem system = ActorSystem.create(); // this latch is only plumbing to know when the calculation is completed final CountDownLatch latch = new CountDownLatch(1); diff --git a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala index d7f932a053..3283a591f4 100644 --- a/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala +++ b/akka-tutorials/akka-tutorial-first/src/main/scala/Pi.scala @@ -9,8 +9,6 @@ import akka.actor.{ ActorSystemImpl, Actor, ActorSystem } object Pi extends App { - val system = ActorSystem() - // Initiate the calculation calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000) @@ -87,14 +85,11 @@ object Pi extends App { } } - object Master { - val impl = system.asInstanceOf[ActorSystemImpl] - } - // ================== // ===== Run it ===== // ================== def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) { + val system = ActorSystem() // this latch is only plumbing to know when the calculation is completed val latch = new CountDownLatch(1) diff --git a/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala index 608ba55481..de5851bfe7 100644 --- a/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala +++ b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala @@ -20,7 +20,7 @@ class WorkerSpec extends WordSpec with MustMatchers { val testActor = TestActorRef[Worker] val actor = testActor.underlyingActor actor.calculatePiFor(0, 0) must equal(0.0) - actor.calculatePiFor(1, 1) must equal(-1.3333333333333333) + actor.calculatePiFor(1, 1) must be(-1.3333333333333333 plusOrMinus 0.0000000001) } } } \ No newline at end of file From 9afc9dc768ac5c95735ea5663b2846029ae0bcee Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Tue, 29 Nov 2011 16:51:30 +0100 Subject: [PATCH 07/30] Making sure that all access to status and systemMessage is through Unsafe --- .../src/main/java/akka/dispatch/AbstractMailbox.java | 4 ++-- .../src/main/scala/akka/dispatch/Mailbox.scala | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/akka-actor/src/main/java/akka/dispatch/AbstractMailbox.java b/akka-actor/src/main/java/akka/dispatch/AbstractMailbox.java index 80cc4c9675..dbe87482dc 100644 --- a/akka-actor/src/main/java/akka/dispatch/AbstractMailbox.java +++ b/akka-actor/src/main/java/akka/dispatch/AbstractMailbox.java @@ -12,8 +12,8 @@ final class AbstractMailbox { static { try { - mailboxStatusOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_status")); - systemMessageOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_systemQueue")); + mailboxStatusOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_statusDoNotCallMeDirectly")); + systemMessageOffset = Unsafe.instance.objectFieldOffset(Mailbox.class.getDeclaredField("_systemQueueDoNotCallMeDirectly")); } catch(Throwable t){ throw new ExceptionInInitializerError(t); } diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 7d3b3c3d8b..49110c2974 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -40,13 +40,13 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes import Mailbox._ @volatile - protected var _status: Status = _ //0 by default + protected var _statusDoNotCallMeDirectly: Status = _ //0 by default @volatile - protected var _systemQueue: SystemMessage = _ //null by default + protected var _systemQueueDoNotCallMeDirectly: SystemMessage = _ //null by default @inline - final def status: Mailbox.Status = _status + final def status: Mailbox.Status = Unsafe.instance.getIntVolatile(this, AbstractMailbox.mailboxStatusOffset) @inline final def shouldProcessMessage: Boolean = (status & 3) == Open @@ -65,7 +65,8 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes Unsafe.instance.compareAndSwapInt(this, AbstractMailbox.mailboxStatusOffset, oldStatus, newStatus) @inline - protected final def setStatus(newStatus: Status): Unit = _status = newStatus + protected final def setStatus(newStatus: Status): Unit = + Unsafe.instance.putIntVolatile(this, AbstractMailbox.mailboxStatusOffset, newStatus) /** * set new primary status Open. Caller does not need to worry about whether @@ -130,7 +131,8 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes /* * AtomicReferenceFieldUpdater for system queue */ - protected final def systemQueueGet: SystemMessage = _systemQueue + protected final def systemQueueGet: SystemMessage = + Unsafe.instance.getObjectVolatile(this, AbstractMailbox.systemMessageOffset).asInstanceOf[SystemMessage] protected final def systemQueuePut(_old: SystemMessage, _new: SystemMessage): Boolean = Unsafe.instance.compareAndSwapObject(this, AbstractMailbox.systemMessageOffset, _old, _new) From 5cc36fa4765db966dc216a4b02615d15a0407f06 Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Tue, 29 Nov 2011 17:12:57 +0100 Subject: [PATCH 08/30] Added todos for 2.0 release, see #1278 --- .../akka-sample-camel/src/main/scala/sample/camel/Actors.scala | 1 + .../akka-sample-camel/src/main/scala/sample/camel/Boot.scala | 1 + .../src/main/scala/sample/camel/ClientApplication.scala | 1 + .../src/main/scala/sample/camel/ServerApplication.scala | 1 + .../src/main/scala/sample/camel/StandaloneApplication.scala | 1 + akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala | 1 + .../src/main/scala/ServerManagedRemoteActorSample.scala | 1 + 7 files changed, 7 insertions(+) diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala index c091d7dfd8..05252fe308 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Actors.scala @@ -3,6 +3,7 @@ */ // CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.camel // diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala index 31b76835a4..845141b4fc 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/Boot.scala @@ -3,6 +3,7 @@ */ // CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.camel // diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala index a4c5edf398..a5bbce4919 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ClientApplication.scala @@ -3,6 +3,7 @@ */ // CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.camel // diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala index 1181e661d4..c852e0d167 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/ServerApplication.scala @@ -3,6 +3,7 @@ */ // CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.camel // diff --git a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala index c87e6bdcab..9c995a95ef 100644 --- a/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala +++ b/akka-samples/akka-sample-camel/src/main/scala/sample/camel/StandaloneApplication.scala @@ -3,6 +3,7 @@ */ // CAMEL IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.camel // diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index 49b5da2138..80cf2f7ab0 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -3,6 +3,7 @@ */ // REMOTING IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 // // package sample.chat diff --git a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala index 9b3a27a7ae..abfb148a89 100644 --- a/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala +++ b/akka-samples/akka-sample-remote/src/main/scala/ServerManagedRemoteActorSample.scala @@ -3,6 +3,7 @@ */ // REMOTING IS NOT PART OF MILESTONE 1 OF AKKA 2.0 +// TODO FIXME 2.0 //package sample.remote // From 9e5c2f15fd3f80faa16cdcf232d46ddd5fbafae9 Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Wed, 30 Nov 2011 08:35:40 +0100 Subject: [PATCH 09/30] Changed LinkedList to Iterable in constructor, see #1278 --- .../src/main/scala/akka/routing/ConnectionManager.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala index e8649dc3db..6e45a50cad 100644 --- a/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala +++ b/akka-actor/src/main/scala/akka/routing/ConnectionManager.scala @@ -86,8 +86,8 @@ trait ConnectionManager { */ class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends ConnectionManager { - def this(linkedList: java.util.LinkedList[ActorRef]) { - this(JavaConverters.iterableAsScalaIterableConverter(linkedList).asScala) + def this(iterable: java.lang.Iterable[ActorRef]) { + this(JavaConverters.iterableAsScalaIterableConverter(iterable).asScala) } case class State(version: Long, connections: Iterable[ActorRef]) extends VersionedIterable[ActorRef] { From 80ac1737cd604f4f4e1e263450ee361e0354a22c Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 30 Nov 2011 10:20:57 +0100 Subject: [PATCH 10/30] First walk throught of FIXME. See #1378 * Fixed obvious * Created tickets for several, #1408, #1409, #1410, #1412, #1415, 1416, #1418 * Moved LoggingReceive from akka.actor to akka.event * Touched several of the FIXME to make them visible in code review --- .../{actor => event}/LoggingReceiveSpec.scala | 19 +++++--- .../main/resources/akka-actor-reference.conf | 2 +- .../src/main/scala/akka/actor/Actor.scala | 35 --------------- .../src/main/scala/akka/actor/ActorCell.scala | 6 ++- .../src/main/scala/akka/actor/ActorRef.scala | 17 ++++---- .../scala/akka/actor/ActorRefProvider.scala | 8 ++-- .../main/scala/akka/actor/ActorSystem.scala | 4 +- .../actor/BootableActorLoaderService.scala | 3 +- .../src/main/scala/akka/actor/Deployer.scala | 3 -- .../akka/dispatch/AbstractDispatcher.scala | 1 + .../src/main/scala/akka/dispatch/Future.scala | 8 +++- .../main/scala/akka/dispatch/Mailbox.scala | 6 --- .../scala/akka/event/LoggingReceive.scala | 43 +++++++++++++++++++ .../src/main/scala/akka/routing/Routing.scala | 2 +- .../akka/util/BoundedBlockingQueue.scala | 5 ++- akka-docs/scala/testing.rst | 5 ++- .../actor/mailbox/FiledBasedMailbox.scala | 3 +- .../cluster/zookeeper/ZooKeeperQueue.java | 2 +- .../actor/mailbox/ZooKeeperBasedMailbox.scala | 2 +- .../main/resources/akka-remote-reference.conf | 12 +++++- .../src/main/scala/akka/remote/Gossiper.scala | 13 +++--- .../akka/remote/NetworkEventStream.scala | 4 +- .../src/main/scala/akka/remote/Remote.scala | 23 +++++----- .../akka/remote/RemoteActorRefProvider.scala | 14 +++--- .../scala/akka/remote/RemoteExtension.scala | 8 ++-- .../remote/netty/NettyRemoteSupport.scala | 16 +++---- .../scala/akka/remote/RemoteConfigSpec.scala | 10 ++--- .../testkit/CallingThreadDispatcher.scala | 1 - 28 files changed, 146 insertions(+), 129 deletions(-) rename akka-actor-tests/src/test/scala/akka/{actor => event}/LoggingReceiveSpec.scala (93%) create mode 100644 akka-actor/src/main/scala/akka/event/LoggingReceive.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala similarity index 93% rename from akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala rename to akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala index 0e5602a899..f917c22bad 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/LoggingReceiveSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala @@ -1,18 +1,27 @@ /** * Copyright (C) 2009-2011 Typesafe Inc. */ -package akka.actor +package akka.event import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach } import akka.util.duration._ import akka.testkit._ import org.scalatest.WordSpec -import akka.event.Logging import akka.util.Duration import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import scala.collection.JavaConverters._ import java.util.Properties +import akka.actor.Actor +import akka.actor.ActorSystem +import akka.actor.HotSwap +import akka.actor.UnhandledMessageException +import akka.actor.PoisonPill +import akka.actor.ActorSystemImpl +import akka.actor.Props +import akka.actor.OneForOneStrategy +import akka.actor.ActorKilledException +import akka.actor.Kill object LoggingReceiveSpec { class TestLogActor extends Actor { @@ -58,7 +67,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd val r: Actor.Receive = { case null ⇒ } - val log = Actor.LoggingReceive("funky", r) + val log = LoggingReceive("funky")(r) log.isDefinedAt("hallo") expectMsg(1 second, Logging.Debug("funky", "received unhandled message hallo")) } @@ -70,7 +79,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd system.eventStream.subscribe(testActor, classOf[Logging.Debug]) system.eventStream.subscribe(testActor, classOf[Logging.Error]) val actor = TestActorRef(new Actor { - def receive = loggable(this) { + def receive = LoggingReceive(this) { case x ⇒ sender ! "x" } @@ -100,7 +109,7 @@ class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAnd new TestKit(appLogging) with ImplicitSender { system.eventStream.subscribe(testActor, classOf[Logging.Debug]) val actor = TestActorRef(new Actor { - def receive = loggable(this)(loggable(this) { + def receive = LoggingReceive(this)(LoggingReceive(this) { case _ ⇒ sender ! "x" }) }) diff --git a/akka-actor/src/main/resources/akka-actor-reference.conf b/akka-actor/src/main/resources/akka-actor-reference.conf index fbfd7d7e9c..9db5a8dcff 100644 --- a/akka-actor/src/main/resources/akka-actor-reference.conf +++ b/akka-actor/src/main/resources/akka-actor-reference.conf @@ -81,7 +81,7 @@ akka { # optional replication { # use replication or not? only makes sense for a stateful actor - # FIXME should we have this config option here? If so, implement it all through. + # serialize-mailbox not implemented, ticket #1412 serialize-mailbox = off # should the actor mailbox be part of the serialized snapshot? # default is 'off' diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index b8c0bbb327..2980d48691 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -160,25 +160,6 @@ object Actor { type Receive = PartialFunction[Any, Unit] - /** - * This decorator adds invocation logging to a Receive function. - */ - class LoggingReceive(source: AnyRef, r: Receive)(implicit system: ActorSystem) extends Receive { - def isDefinedAt(o: Any) = { - val handled = r.isDefinedAt(o) - system.eventStream.publish(Debug(LogSource.fromAnyRef(source), "received " + (if (handled) "handled" else "unhandled") + " message " + o)) - handled - } - def apply(o: Any): Unit = r(o) - } - - object LoggingReceive { - def apply(source: AnyRef, r: Receive)(implicit system: ActorSystem): Receive = r match { - case _: LoggingReceive ⇒ r - case _ ⇒ new LoggingReceive(source, r) - } - } - object emptyBehavior extends Receive { def isDefinedAt(x: Any) = false def apply(x: Any) = throw new UnsupportedOperationException("empty behavior apply()") @@ -235,22 +216,6 @@ trait Actor { */ implicit def defaultTimeout = system.settings.ActorTimeout - /** - * Wrap a Receive partial function in a logging enclosure, which sends a - * debug message to the EventHandler each time before a message is matched. - * This includes messages which are not handled. - * - *

-   * def receive = loggable {
-   *   case x => ...
-   * }
-   * 
- * - * This method does NOT modify the given Receive unless - * akka.actor.debug.receive is set within akka.conf. - */ - def loggable(self: AnyRef)(r: Receive): Receive = if (system.settings.AddLoggingReceive) LoggingReceive(self, r) else r //TODO FIXME Shouldn't this be in a Loggable-trait? - /** * The 'self' field holds the ActorRef for this actor. *

diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index afd462ff1e..0f9cd38370 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -190,6 +190,7 @@ private[akka] class ActorCell( checkReceiveTimeout if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.toString, "started (" + actor + ")")) } catch { + // FIXME catching all and continue isn't good for OOME, ticket #1418 case e ⇒ try { system.eventStream.publish(Error(e, self.toString, "error while creating actor")) @@ -222,6 +223,7 @@ private[akka] class ActorCell( props.faultHandler.handleSupervisorRestarted(cause, self, children) } catch { + // FIXME catching all and continue isn't good for OOME, ticket #1418 case e ⇒ try { system.eventStream.publish(Error(e, self.toString, "error while creating actor")) // prevent any further messages to be processed until the actor has been restarted @@ -283,7 +285,7 @@ private[akka] class ActorCell( } catch { case e ⇒ //Should we really catch everything here? system.eventStream.publish(Error(e, self.toString, "error while processing " + message)) - //TODO FIXME How should problems here be handled? + //TODO FIXME How should problems here be handled??? throw e } } @@ -294,7 +296,7 @@ private[akka] class ActorCell( currentMessage = messageHandle try { try { - cancelReceiveTimeout() // FIXME: leave this here? + cancelReceiveTimeout() // FIXME: leave this here??? messageHandle.message match { case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle) case msg if stopping ⇒ // receiving Terminated in response to stopping children is too common to generate noise diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index e62a04938a..aa54f79205 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -111,12 +111,12 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable /** * Suspends the actor. It will not process messages while suspended. */ - def suspend(): Unit //TODO FIXME REMOVE THIS + def suspend(): Unit //TODO FIXME REMOVE THIS, ticket #1415 /** * Resumes a suspended actor. */ - def resume(): Unit //TODO FIXME REMOVE THIS + def resume(): Unit //TODO FIXME REMOVE THIS, ticket #1415 /** * Shuts down the actor its dispatcher and message queue. @@ -135,7 +135,7 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable * * @return the same ActorRef that is provided to it, to allow for cleaner invocations */ - def startsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS + def startsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS, ticket #1416 /** * Deregisters this actor from being a death monitor of the provided ActorRef @@ -144,7 +144,7 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable * * @return the same ActorRef that is provided to it, to allow for cleaner invocations */ - def stopsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS + def stopsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS, ticket #1416 override def hashCode: Int = HashCode.hash(HashCode.SEED, address) @@ -201,13 +201,13 @@ class LocalActorRef private[akka] ( * message sends done from the same thread after calling this method will not * be processed until resumed. */ - //FIXME TODO REMOVE THIS, NO REPLACEMENT + //FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415 def suspend(): Unit = actorCell.suspend() /** * Resumes a suspended actor. */ - //FIXME TODO REMOVE THIS, NO REPLACEMENT + //FIXME TODO REMOVE THIS, NO REPLACEMENT, ticket #1415 def resume(): Unit = actorCell.resume() /** @@ -237,7 +237,7 @@ class LocalActorRef private[akka] ( protected[akka] def underlying: ActorCell = actorCell - // FIXME TODO: remove this method + // FIXME TODO: remove this method. It is used in testkit. // @deprecated("This method does a spin-lock to block for the actor, which might never be there, do not use this", "2.0") protected[akka] def underlyingActorInstance: Actor = { var instance = actorCell.actor @@ -308,7 +308,6 @@ case class SerializedActorRef(hostname: String, port: Int, path: String) { import akka.serialization.Serialization.currentSystem def this(remoteAddress: RemoteAddress, path: String) = this(remoteAddress.hostname, remoteAddress.port, path) - def this(remoteAddress: InetSocketAddress, path: String) = this(remoteAddress.getAddress.getHostAddress, remoteAddress.getPort, path) //TODO FIXME REMOVE @throws(classOf[java.io.ObjectStreamException]) def readResolve(): AnyRef = currentSystem.value match { @@ -330,9 +329,11 @@ trait MinimalActorRef extends ActorRef with ScalaActorRef { private[akka] val uuid: Uuid = newUuid() def name: String = uuid.toString + //FIXME REMOVE THIS, ticket #1416 def startsWatching(actorRef: ActorRef): ActorRef = actorRef def stopsWatching(actorRef: ActorRef): ActorRef = actorRef + //FIXME REMOVE THIS, ticket #1415 def suspend(): Unit = () def resume(): Unit = () diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 764838cdb8..5ebe475cd0 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -36,10 +36,10 @@ trait ActorRefProvider { def deathWatch: DeathWatch - // FIXME: remove/replace? + // FIXME: remove/replace??? def nodename: String - // FIXME: remove/replace? + // FIXME: remove/replace??? def clustername: String /** @@ -64,7 +64,7 @@ trait ActorRefProvider { /** * Create an Actor with the given full path below the given supervisor. * - * FIXME: Remove! this is dangerous! + * FIXME: Remove! this is dangerous!? */ private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, path: ActorPath, systemService: Boolean): ActorRef @@ -296,7 +296,7 @@ class LocalActorRefProvider( private[akka] def actorOf(system: ActorSystemImpl, props: Props, supervisor: ActorRef, path: ActorPath, systemService: Boolean): ActorRef = { val name = path.name - val newFuture = Promise[ActorRef](5000)(dispatcher) // FIXME is this proper timeout? + val newFuture = Promise[ActorRef](system.settings.ActorTimeout)(dispatcher) actors.putIfAbsent(path.toString, newFuture) match { case null ⇒ diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 4f02473b4f..f17a4fca8d 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -209,8 +209,6 @@ abstract class ActorSystem extends ActorRefFactory { * effort basis and hence not strictly guaranteed. */ def deadLetters: ActorRef - // FIXME: do not publish this - def deadLetterMailbox: Mailbox /** * Light-weight scheduler for running asynchronous tasks after some deadline @@ -328,7 +326,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A val dispatcherFactory = new Dispatchers(settings, DefaultDispatcherPrerequisites(eventStream, deadLetterMailbox, scheduler)) implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher - //FIXME Set this to a Failure when things bubble to the top + //FIXME Set this to a Failure when things bubble to the top. What does this mean? def terminationFuture: Future[Unit] = provider.terminationFuture def guardian: ActorRef = provider.guardian def systemGuardian: ActorRef = provider.systemGuardian diff --git a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala index 6e0f99b50d..6f8608378d 100644 --- a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala +++ b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala @@ -58,8 +58,7 @@ trait BootableActorLoaderService extends Bootable { abstract override def onUnload() = { super.onUnload() - // FIXME shutdown all actors - // system.registry.local.shutdownAll + system.stop() } } diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index ec1d8dfc4c..709d5dbe48 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -18,7 +18,6 @@ import com.typesafe.config.Config trait ActorDeployer { private[akka] def init(deployments: Seq[Deploy]): Unit - private[akka] def shutdown(): Unit //TODO Why should we have "shutdown", should be crash only? private[akka] def deploy(deployment: Deploy): Unit private[akka] def lookupDeploymentFor(path: String): Option[Deploy] def lookupDeployment(path: String): Option[Deploy] = path match { @@ -49,8 +48,6 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, private[akka] def init(deployments: Seq[Deploy]) = instance.init(deployments) - def shutdown(): Unit = instance.shutdown() //TODO FIXME Why should we have "shutdown", should be crash only? - def deploy(deployment: Deploy): Unit = instance.deploy(deployment) def isLocal(deployment: Deploy): Boolean = deployment match { diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index 208eae51ca..fd45069e68 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -69,6 +69,7 @@ final case class TaskInvocation(eventStream: EventStream, function: () ⇒ Unit, try { function() } catch { + // FIXME catching all and continue isn't good for OOME, ticket #1418 case e ⇒ eventStream.publish(Error(e, "TaskInvocation", e.getMessage)) } finally { cleanup() diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index e9a3035ea8..274f82fe6c 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -163,6 +163,7 @@ object Future { try { Right(body) } catch { + // FIXME catching all and continue isn't good for OOME, ticket #1418 case e ⇒ Left(e) } } @@ -411,7 +412,9 @@ object Future { try { next.apply() } catch { - case e ⇒ e.printStackTrace() //TODO FIXME strategy for handling exceptions in callbacks + case e ⇒ + // FIXME catching all and continue isn't good for OOME, ticket #1418 + dispatcher.prerequisites.eventStream.publish(Error(e, "Future.dispatchTask", "Failed to dispatch task, due to: " + e.getMessage)) } } } finally { _taskStack set None } @@ -984,7 +987,7 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi def run() { if (!isCompleted) { if (!isExpired) dispatcher.prerequisites.scheduler.scheduleOnce(this, Duration(timeLeftNoinline(), TimeUnit.NANOSECONDS)) - else promise complete (try { Right(fallback) } catch { case e ⇒ Left(e) }) + else promise complete (try { Right(fallback) } catch { case e ⇒ Left(e) }) // FIXME catching all and continue isn't good for OOME, ticket #1418 } } } @@ -994,6 +997,7 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi } else this private def notifyCompleted(func: Future[T] ⇒ Unit) { + // FIXME catching all and continue isn't good for OOME, ticket #1418 try { func(this) } catch { case e ⇒ dispatcher.prerequisites.eventStream.publish(Error(e, "Future", "Future onComplete-callback raised an exception")) } //TODO catch, everything? Really? } diff --git a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala index 49110c2974..3e55fc720e 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Mailbox.scala @@ -28,9 +28,6 @@ object Mailbox { // secondary status: Scheduled bit may be added to Open/Suspended final val Scheduled = 4 - // mailbox debugging helper using println (see below) - // FIXME TODO take this out before release - final val debug = false } /** @@ -167,7 +164,6 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes var processedMessages = 0 val deadlineNs = if (dispatcher.isThroughputDeadlineTimeDefined) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0 do { - if (debug) println(actor.self + " processing message " + nextMessage) actor invoke nextMessage processAllSystemMessages() //After we're done, process all system messages @@ -190,7 +186,6 @@ abstract class Mailbox(val actor: ActorCell) extends MessageQueue with SystemMes var nextMessage = systemDrain() try { while (nextMessage ne null) { - if (debug) println(actor.self + " processing system message " + nextMessage + " with children " + actor.childrenRefs) actor systemInvoke nextMessage nextMessage = nextMessage.next // don’t ever execute normal message when system message present! @@ -245,7 +240,6 @@ trait DefaultSystemMessageQueue { self: Mailbox ⇒ @tailrec final def systemEnqueue(receiver: ActorRef, message: SystemMessage): Unit = { assert(message.next eq null) - if (Mailbox.debug) println(actor.self + " having enqueued " + message) val head = systemQueueGet /* * this write is safely published by the compareAndSet contained within diff --git a/akka-actor/src/main/scala/akka/event/LoggingReceive.scala b/akka-actor/src/main/scala/akka/event/LoggingReceive.scala new file mode 100644 index 0000000000..250af89812 --- /dev/null +++ b/akka-actor/src/main/scala/akka/event/LoggingReceive.scala @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.event + +import akka.actor.Actor.Receive +import akka.actor.ActorSystem +import akka.event.Logging.Debug + +object LoggingReceive { + + /** + * Wrap a Receive partial function in a logging enclosure, which sends a + * debug message to the event bus each time before a message is matched. + * This includes messages which are not handled. + * + *


+   * def receive = LoggingReceive(this) {
+   *   case x => ...
+   * }
+   * 
+ * + * This method does NOT modify the given Receive unless + * akka.actor.debug.receive is set within akka.conf. + */ + def apply(source: AnyRef)(r: Receive)(implicit system: ActorSystem): Receive = r match { + case _: LoggingReceive ⇒ r + case _ if !system.settings.AddLoggingReceive ⇒ r + case _ ⇒ new LoggingReceive(source, r) + } +} + +/** + * This decorator adds invocation logging to a Receive function. + */ +class LoggingReceive(source: AnyRef, r: Receive)(implicit system: ActorSystem) extends Receive { + def isDefinedAt(o: Any) = { + val handled = r.isDefinedAt(o) + system.eventStream.publish(Debug(LogSource.fromAnyRef(source), "received " + (if (handled) "handled" else "unhandled") + " message " + o)) + handled + } + def apply(o: Any): Unit = r(o) +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/routing/Routing.scala b/akka-actor/src/main/scala/akka/routing/Routing.scala index 293c1abb4b..10eecbfc56 100644 --- a/akka-actor/src/main/scala/akka/routing/Routing.scala +++ b/akka-actor/src/main/scala/akka/routing/Routing.scala @@ -245,7 +245,7 @@ trait BasicRouter extends Router { next match { case Some(connection) ⇒ try { - connection.?(message, timeout).asInstanceOf[Future[T]] //FIXME this does not preserve the original sender, shouldn't it? + connection.?(message, timeout).asInstanceOf[Future[T]] //FIXME this does not preserve the original sender, shouldn't it?? } catch { case e: Exception ⇒ connectionManager.remove(connection) diff --git a/akka-actor/src/main/scala/akka/util/BoundedBlockingQueue.scala b/akka-actor/src/main/scala/akka/util/BoundedBlockingQueue.scala index 5b17ca5c7d..3c0f386b84 100644 --- a/akka-actor/src/main/scala/akka/util/BoundedBlockingQueue.scala +++ b/akka-actor/src/main/scala/akka/util/BoundedBlockingQueue.scala @@ -108,6 +108,7 @@ class BoundedBlockingQueue[E <: AnyRef]( throw ie } false + // FIXME catching all and continue isn't good for OOME, ticket #1418 case e ⇒ notFull.signal() result = e @@ -234,7 +235,7 @@ class BoundedBlockingQueue[E <: AnyRef]( if (backing.removeAll(c)) { val sz = backing.size() if (sz < maxCapacity) notFull.signal() - if (sz > 0) notEmpty.signal() //FIXME needed? + if (sz > 0) notEmpty.signal() //FIXME needed?? true } else false } finally { @@ -247,7 +248,7 @@ class BoundedBlockingQueue[E <: AnyRef]( try { if (backing.retainAll(c)) { val sz = backing.size() - if (sz < maxCapacity) notFull.signal() //FIXME needed? + if (sz < maxCapacity) notFull.signal() //FIXME needed?? if (sz > 0) notEmpty.signal() true } else false diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index ca1469217c..7ff69fc9a9 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -717,11 +717,12 @@ options: ``akka.actor.debug.receive`` — which enables the :meth:`loggable` statement to be applied to an actor’s :meth:`receive` function:: - def receive = Actor.loggable(this) { // `Actor` unnecessary with import Actor._ + import akka.event.LoggingReceive + def receive = LoggingReceive(this) { case msg => ... } - The first argument to :meth:`loggable` defines the source to be used in the + The first argument to :meth:`LoggingReceive` defines the source to be used in the logging events, which should be the current actor. If the abovementioned setting is not given in ``akka.conf``, this method will diff --git a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FiledBasedMailbox.scala b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FiledBasedMailbox.scala index 8a81b2f8e4..8fa7f81e25 100644 --- a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FiledBasedMailbox.scala +++ b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/FiledBasedMailbox.scala @@ -62,7 +62,8 @@ class FileBasedMailbox(val owner: ActorCell) extends DurableMailbox(owner) with queue.remove true } catch { - case e ⇒ false //review why catch Throwable? And swallow potential Errors? + // FIXME catching all and continue isn't good for OOME, ticket #1418 + case e ⇒ false } } diff --git a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/java/akka/cluster/zookeeper/ZooKeeperQueue.java b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/java/akka/cluster/zookeeper/ZooKeeperQueue.java index af76896f4b..4e06b64e6b 100644 --- a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/java/akka/cluster/zookeeper/ZooKeeperQueue.java +++ b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/java/akka/cluster/zookeeper/ZooKeeperQueue.java @@ -66,7 +66,7 @@ public class ZooKeeperQueue { return element.getData(); } else { throw new UnsupportedOperationException("Non-blocking ZooKeeperQueue is not yet supported"); - /* FIXME DOES NOT WORK + /* TODO DOES NOT WORK try { String headName = getSmallestElement(_zkClient.getChildren(_elementsPath)); String headPath = getElementPath(headName); diff --git a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailbox.scala b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailbox.scala index 8350f743d5..c5efa62358 100644 --- a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailbox.scala +++ b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/main/scala/akka/actor/mailbox/ZooKeeperBasedMailbox.scala @@ -59,7 +59,7 @@ class ZooKeeperBasedMailbox(val owner: ActorCell) extends DurableMailbox(owner) queue.clear true } catch { - case e ⇒ false + case e: Exception ⇒ false } override def cleanUp() { diff --git a/akka-remote/src/main/resources/akka-remote-reference.conf b/akka-remote/src/main/resources/akka-remote-reference.conf index 39fcda8cd7..130f1f4526 100644 --- a/akka-remote/src/main/resources/akka-remote-reference.conf +++ b/akka-remote/src/main/resources/akka-remote-reference.conf @@ -8,8 +8,7 @@ akka { remote { - # FIXME rename to transport - layer = "akka.cluster.netty.NettyRemoteSupport" + transport = "akka.cluster.netty.NettyRemoteSupport" use-compression = off @@ -27,6 +26,15 @@ akka { # generates fewer mistakes but needs more time to detect actual crashes max-sample-size = 1000 } + + gossip { + initialDelay = 5s + frequency = 1s + } + + compute-grid-dispatcher { # The dispatcher used for remote system messages + name = ComputeGridDispatcher # defaults to same settings as default-dispatcher + } server { hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty diff --git a/akka-remote/src/main/scala/akka/remote/Gossiper.scala b/akka-remote/src/main/scala/akka/remote/Gossiper.scala index 8050b24fe1..119a4a43a6 100644 --- a/akka-remote/src/main/scala/akka/remote/Gossiper.scala +++ b/akka-remote/src/main/scala/akka/remote/Gossiper.scala @@ -15,6 +15,7 @@ import akka.config.ConfigurationException import akka.serialization.SerializationExtension import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.TimeUnit.SECONDS import java.security.SecureRandom import System.{ currentTimeMillis ⇒ newTimestamp } @@ -122,19 +123,15 @@ class Gossiper(remote: Remote) { private val nodeFingerprint = address.## private val random = SecureRandom.getInstance("SHA1PRNG") - private val initalDelayForGossip = 5 seconds // FIXME make configurable - private val gossipFrequency = 1 seconds // FIXME make configurable - private val timeUnit = { - assert(gossipFrequency.unit == initalDelayForGossip.unit) - initalDelayForGossip.unit - } + private val initalDelayForGossip = remoteExtension.InitalDelayForGossip + private val gossipFrequency = remoteExtension.GossipFrequency private val state = new AtomicReference[State](State(currentGossip = newGossip())) { // start periodic gossip and cluster scrutinization - default is run them every second with 1/2 second in between - system.scheduler schedule (() ⇒ initateGossip(), Duration(initalDelayForGossip.toSeconds, timeUnit), Duration(gossipFrequency.toSeconds, timeUnit)) - system.scheduler schedule (() ⇒ scrutinize(), Duration(initalDelayForGossip.toSeconds, timeUnit), Duration(gossipFrequency.toSeconds, timeUnit)) + system.scheduler schedule (() ⇒ initateGossip(), Duration(initalDelayForGossip.toSeconds, SECONDS), Duration(gossipFrequency.toSeconds, SECONDS)) + system.scheduler schedule (() ⇒ scrutinize(), Duration(initalDelayForGossip.toSeconds, SECONDS), Duration(gossipFrequency.toSeconds, SECONDS)) } /** diff --git a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala index 3376ad9416..cde377cda7 100644 --- a/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala +++ b/akka-remote/src/main/scala/akka/remote/NetworkEventStream.scala @@ -45,7 +45,7 @@ object NetworkEventStream { case event: RemoteClientLifeCycleEvent ⇒ listeners(event.remoteAddress) foreach (_ notify event) - case event: RemoteServerLifeCycleEvent ⇒ // FIXME handle RemoteServerLifeCycleEvent + case event: RemoteServerLifeCycleEvent ⇒ // FIXME handle RemoteServerLifeCycleEvent, ticket #1408 and #1190 case Register(listener, connectionAddress) ⇒ listeners(connectionAddress) += listener @@ -62,7 +62,7 @@ class NetworkEventStream(system: ActorSystemImpl) { import NetworkEventStream._ - // FIXME: check that this supervision is correct + // FIXME: check that this supervision is correct, ticket #1408 private[akka] val sender = system.provider.actorOf(system, Props[Channel].copy(dispatcher = system.dispatcherFactory.newPinnedDispatcher("NetworkEventStream")), system.systemGuardian, "network-event-sender", systemService = true) diff --git a/akka-remote/src/main/scala/akka/remote/Remote.scala b/akka-remote/src/main/scala/akka/remote/Remote.scala index 3db093efd9..82daeaf820 100644 --- a/akka-remote/src/main/scala/akka/remote/Remote.scala +++ b/akka-remote/src/main/scala/akka/remote/Remote.scala @@ -47,10 +47,9 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { val remoteDaemonServiceName = "akka-system-remote-daemon".intern - // FIXME configure computeGridDispatcher to what? - val computeGridDispatcher = dispatcherFactory.newDispatcher("akka:compute-grid").build + val computeGridDispatcher = dispatcherFactory.fromConfig("akka.remote.compute-grid-dispatcher") - // FIXME it is probably better to create another supervisor for handling the children created by handle_* + // FIXME it is probably better to create another supervisor for handling the children created by handle_*, ticket #1408 private[remote] lazy val remoteDaemonSupervisor = system.actorOf(Props( OneForOneStrategy(List(classOf[Exception]), None, None)), "akka-system-remote-supervisor") // is infinite restart what we want? @@ -73,13 +72,11 @@ class Remote(val system: ActorSystemImpl, val nodename: String) { lazy val server: RemoteSupport = { val remote = new akka.remote.netty.NettyRemoteSupport(system) - remote.start() //TODO FIXME Any application loader here? + remote.start() //TODO Any application loader here? system.eventStream.subscribe(eventStream.sender, classOf[RemoteLifeCycleEvent]) system.eventStream.subscribe(remoteClientLifeCycleHandler, classOf[RemoteLifeCycleEvent]) - // TODO actually register this provider in system in remote mode - //provider.register(ActorRefProvider.RemoteProvider, new RemoteActorRefProvider) remote } @@ -157,9 +154,9 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { sender ! Success(remoteAddress) } catch { - case error: Throwable ⇒ //FIXME doesn't seem sensible - sender ! Failure(error) - throw error + case exc: Exception ⇒ + sender ! Failure(exc) + throw exc } } @@ -192,7 +189,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { def tempName = "$_" + Helpers.base64(tempNumber.getAndIncrement()) def tempPath = remoteDaemon.path / tempName - // FIXME: handle real remote supervision + // FIXME: handle real remote supervision, ticket #1408 def handle_fun0_unit(message: RemoteSystemDaemonMessageProtocol) { new LocalActorRef(systemImpl, Props( @@ -201,7 +198,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Function0[Unit]]) } - // FIXME: handle real remote supervision + // FIXME: handle real remote supervision, ticket #1408 def handle_fun0_any(message: RemoteSystemDaemonMessageProtocol) { new LocalActorRef(systemImpl, Props( @@ -210,7 +207,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) forward payloadFor(message, classOf[Function0[Any]]) } - // FIXME: handle real remote supervision + // FIXME: handle real remote supervision, ticket #1408 def handle_fun1_arg_unit(message: RemoteSystemDaemonMessageProtocol) { new LocalActorRef(systemImpl, Props( @@ -219,7 +216,7 @@ class RemoteSystemDaemon(remote: Remote) extends Actor { }).copy(dispatcher = computeGridDispatcher), remoteDaemon, tempPath, systemService = true) ! payloadFor(message, classOf[Tuple2[Function1[Any, Unit], Any]]) } - // FIXME: handle real remote supervision + // FIXME: handle real remote supervision, ticket #1408 def handle_fun1_arg_any(message: RemoteSystemDaemonMessageProtocol) { new LocalActorRef(systemImpl, Props( diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 358568e13c..3850e43e83 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -86,7 +86,7 @@ class RemoteActorRefProvider( if (systemService) local.actorOf(system, props, supervisor, path, systemService) else { val name = path.name - val newFuture = Promise[ActorRef](5000)(dispatcher) // FIXME is this proper timeout? + val newFuture = Promise[ActorRef](system.settings.ActorTimeout)(dispatcher) actors.putIfAbsent(path.toString, newFuture) match { // we won the race -- create the actor and resolve the future case null ⇒ @@ -100,7 +100,7 @@ class RemoteActorRefProvider( if (isReplicaNode) { // we are on one of the replica node for this remote actor - local.actorOf(system, props, supervisor, name, true) //FIXME systemService = true here to bypass Deploy, should be fixed when create-or-get is replaced by get-or-create + local.actorOf(system, props, supervisor, name, true) //FIXME systemService = true here to bypass Deploy, should be fixed when create-or-get is replaced by get-or-create (is this fixed now?) } else { implicit val dispatcher = if (props.dispatcher == Props.defaultDispatcher) system.dispatcher else props.dispatcher @@ -177,7 +177,7 @@ class RemoteActorRefProvider( /** * Copied from LocalActorRefProvider... */ - // FIXME: implement supervision + // FIXME: implement supervision, ticket #1408 def actorOf(system: ActorSystem, props: RoutedProps, supervisor: ActorRef, name: String): ActorRef = { if (props.connectionManager.isEmpty) throw new ConfigurationException("RoutedProps used for creating actor [" + name + "] has zero connections configured; can't create a router") new RoutedActorRef(system, props, supervisor, name) @@ -266,7 +266,7 @@ class RemoteActorRefProvider( } } - private[akka] def createDeathWatch(): DeathWatch = local.createDeathWatch() //FIXME Implement Remote DeathWatch + private[akka] def createDeathWatch(): DeathWatch = local.createDeathWatch() //FIXME Implement Remote DeathWatch, ticket ##1190 private[akka] def ask(message: Any, recipient: ActorRef, within: Timeout): Future[Any] = local.ask(message, recipient, within) @@ -306,7 +306,7 @@ private[akka] case class RemoteActorRef private[akka] ( def resume(): Unit = () - def stop() { //FIXME send the cause as well! + def stop() { //FIXME send the cause as well! (WDYM?) synchronized { if (running) { running = false @@ -318,9 +318,9 @@ private[akka] case class RemoteActorRef private[akka] ( @throws(classOf[java.io.ObjectStreamException]) private def writeReplace(): AnyRef = provider.serialize(this) - def startsWatching(actorRef: ActorRef): ActorRef = unsupported //FIXME Implement + def startsWatching(actorRef: ActorRef): ActorRef = unsupported ////FIXME Implement Remote DeathWatch, ticket #1190 - def stopsWatching(actorRef: ActorRef): ActorRef = unsupported //FIXME Implement + def stopsWatching(actorRef: ActorRef): ActorRef = unsupported ////FIXME Implement Remote DeathWatch, ticket #1190 protected[akka] def restart(cause: Throwable): Unit = () diff --git a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala index 53efaba4b4..ab07f9c3da 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala @@ -13,7 +13,6 @@ import java.net.InetAddress import akka.config.ConfigurationException import com.eaio.uuid.UUID import akka.actor._ - import scala.collection.JavaConverters._ object RemoteExtension extends ExtensionId[RemoteExtensionSettings] with ExtensionIdProvider { @@ -29,19 +28,20 @@ class RemoteExtensionSettings(cfg: Config) extends Extension { import config._ - val RemoteTransport = getString("akka.remote.layer") + val RemoteTransport = getString("akka.remote.transport") val FailureDetectorThreshold = getInt("akka.remote.failure-detector.threshold") val FailureDetectorMaxSampleSize = getInt("akka.remote.failure-detector.max-sample-size") val ShouldCompressData = config.getBoolean("akka.remote.use-compression") val RemoteSystemDaemonAckTimeout = Duration(config.getMilliseconds("akka.remote.remote-daemon-ack-timeout"), MILLISECONDS) + val InitalDelayForGossip = Duration(config.getMilliseconds("akka.remote.gossip.initialDelay"), MILLISECONDS) + val GossipFrequency = Duration(config.getMilliseconds("akka.remote.gossip.frequency"), MILLISECONDS) // TODO cluster config will go into akka-cluster-reference.conf when we enable that module val ClusterName = getString("akka.cluster.name") val SeedNodes = Set.empty[RemoteAddress] ++ getStringList("akka.cluster.seed-nodes").asScala.toSeq.map(RemoteAddress(_)) - // FIXME remove nodename from config - should only be passed as command line arg or read from properties file etc. val NodeName: String = config.getString("akka.cluster.nodename") match { - case "" ⇒ new UUID().toString + case "" ⇒ throw new ConfigurationException("akka.cluster.nodename configuration property must be defined") case value ⇒ value } diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index d12f5ea7e4..99e5f11097 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -66,7 +66,7 @@ abstract class RemoteClient private[akka] ( * Sends the message across the wire */ def send(request: RemoteMessageProtocol) { - if (isRunning) { //TODO FIXME RACY + if (isRunning) { //FIXME RACY, ticket #1409 log.debug("Sending message: " + new RemoteMessage(request, remoteSupport)) try { @@ -125,7 +125,7 @@ class ActiveRemoteClient private[akka] ( import remoteSupport.clientSettings._ - //FIXME rewrite to a wrapper object (minimize volatile access and maximize encapsulation) + //TODO rewrite to a wrapper object (minimize volatile access and maximize encapsulation) @volatile private var bootstrap: ClientBootstrap = _ @volatile @@ -161,7 +161,7 @@ class ActiveRemoteClient private[akka] ( def closeChannel(connection: ChannelFuture) = { val channel = connection.getChannel openChannels.remove(channel) - channel.close + channel.close() } def attemptReconnect(): Boolean = { @@ -345,7 +345,7 @@ class ActiveRemoteClientHandler( client.remoteSupport.shutdownClientConnection(remoteAddress) // spawn in another thread } case e: Exception ⇒ - event.getChannel.close //FIXME Is this the correct behavior? + event.getChannel.close() //FIXME Is this the correct behavior??? } } else client.notifyListeners(RemoteClientError(new Exception("Unknown cause"), client.remoteSupport, client.remoteAddress)) @@ -648,7 +648,7 @@ class RemoteServerHandler( val inbound = RemoteAddress(origin.getHostname, origin.getPort) val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) - case CommandType.SHUTDOWN ⇒ //TODO FIXME Dispose passive connection here + case CommandType.SHUTDOWN ⇒ //FIXME Dispose passive connection here, ticket #1410 case _ ⇒ //Unknown command } case _ ⇒ //ignore @@ -659,7 +659,7 @@ class RemoteServerHandler( override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { remoteSupport.notifyListeners(RemoteServerError(event.getCause, remoteSupport)) - event.getChannel.close + event.getChannel.close() } private def getClientAddress(c: Channel): Option[RemoteAddress] = @@ -679,7 +679,7 @@ class DefaultDisposableChannelGroup(name: String) extends DefaultChannelGroup(na if (open.get) { super.add(channel) } else { - channel.close + channel.close() false } } finally { @@ -690,7 +690,7 @@ class DefaultDisposableChannelGroup(name: String) extends DefaultChannelGroup(na override def close(): ChannelGroupFuture = { guard.writeLock().lock() try { - if (open.getAndSet(false)) super.close else throw new IllegalStateException("ChannelGroup already closed, cannot add new channel") + if (open.getAndSet(false)) super.close() else throw new IllegalStateException("ChannelGroup already closed, cannot add new channel") } finally { guard.writeLock().unlock() } diff --git a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala index f72904fc3f..63b631473d 100644 --- a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala @@ -3,16 +3,16 @@ package akka.remote import akka.testkit.AkkaSpec @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class RemoteConfigSpec extends AkkaSpec { +class RemoteConfigSpec extends AkkaSpec("akka.cluster.nodename = node1") { - "ClusterSpec: A Deployer" must { - "be able to parse 'akka.actor.cluster._' config elements" in { + "RemoteExtension" must { + "be able to parse remote and cluster config elements" in { val config = RemoteExtension(system).config import config._ //akka.remote - getString("akka.remote.layer") must equal("akka.cluster.netty.NettyRemoteSupport") + getString("akka.remote.transport") must equal("akka.cluster.netty.NettyRemoteSupport") getString("akka.remote.secure-cookie") must equal("") getBoolean("akka.remote.use-passive-connections") must equal(true) // getMilliseconds("akka.remote.remote-daemon-ack-timeout") must equal(30 * 1000) @@ -35,7 +35,7 @@ class RemoteConfigSpec extends AkkaSpec { // TODO cluster config will go into akka-cluster-reference.conf when we enable that module //akka.cluster getString("akka.cluster.name") must equal("default-cluster") - getString("akka.cluster.nodename") must equal("") + getString("akka.cluster.nodename") must equal("node1") getStringList("akka.cluster.seed-nodes") must equal(new java.util.ArrayList[String]) // getMilliseconds("akka.cluster.max-time-to-wait-until-connected") must equal(30 * 1000) diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index 382c25523d..edbc589099 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -214,7 +214,6 @@ class CallingThreadDispatcher( } if (handle ne null) { try { - if (Mailbox.debug) println(mbox.actor.self + " processing message " + handle) mbox.actor.invoke(handle) true } catch { From 070d44616541a02c341e5bcefd367a9fdf10c6f3 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 30 Nov 2011 10:52:51 +0100 Subject: [PATCH 11/30] #1417 - Added a test to attempt to statistically verify memory consistency for actors --- .../scala/akka/actor/ConsistencySpec.scala | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala diff --git a/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala new file mode 100644 index 0000000000..c04e193c2b --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala @@ -0,0 +1,49 @@ +package akka.actor + +import akka.testkit.AkkaSpec +import akka.dispatch.UnboundedMailbox +import akka.util.duration._ + +object ConsistencySpec { + class CacheMisaligned(var value: Long, var padding1: Long, var padding2: Long, var padding3: Int) //Vars, no final fences + + class ConsistencyCheckingActor extends Actor { + var left = new CacheMisaligned(42, 0, 0, 0) //var + var right = new CacheMisaligned(0, 0, 0, 0) //var + def receive = { + case "check" ⇒ + var shouldBeFortyTwo = left.value + right.value + if (shouldBeFortyTwo != 42) + sender ! "Test failed" + else { + left.value += 1 + right.value -= 1 + } + case "done" ⇒ sender ! "done"; self.stop() + } + } +} + +class ConsistencySpec extends AkkaSpec { + import ConsistencySpec._ + "The Akka actor model implementation" must { + "provide memory consistency" in { + val dispatcher = system + .dispatcherFactory + .newDispatcher("consistency-dispatcher", 1, UnboundedMailbox()) + .withNewThreadPoolWithArrayBlockingQueueWithCapacityAndFairness(1000, true) + .setCorePoolSize(10) + .setMaxPoolSize(10) + .setKeepAliveTimeInMillis(1) + .setAllowCoreThreadTimeout(true) + .build + + val props = Props[ConsistencyCheckingActor].withDispatcher(dispatcher) + val actors = Vector.fill(3)(system.actorOf(props)) + for (i ← 1 to 1000000) actors(i % actors.size).tell("check", testActor) + for (a ← actors) a.tell("done", testActor) + + for (a ← actors) expectMsg(5 minutes, "done") + } + } +} \ No newline at end of file From 99e5d88ace3466ba75a8a2e906463f4549058cd0 Mon Sep 17 00:00:00 2001 From: Henrik Engstrom Date: Wed, 30 Nov 2011 11:37:42 +0100 Subject: [PATCH 12/30] Removed obsolete samples, see #1278 --- akka-samples/akka-sample-ants/README.md | 44 ---- .../src/main/scala/Ants.scala | 222 ------------------ akka-samples/akka-sample-fsm/{src => }/README | 0 .../akka-sample-hello/{src => }/README | 0 .../src/main/scala/OsgiExample.scala | 29 --- project/AkkaBuild.scala | 2 - 6 files changed, 297 deletions(-) delete mode 100644 akka-samples/akka-sample-ants/README.md delete mode 100644 akka-samples/akka-sample-ants/src/main/scala/Ants.scala rename akka-samples/akka-sample-fsm/{src => }/README (100%) rename akka-samples/akka-sample-hello/{src => }/README (100%) delete mode 100644 akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala diff --git a/akka-samples/akka-sample-ants/README.md b/akka-samples/akka-sample-ants/README.md deleted file mode 100644 index 2eaced5663..0000000000 --- a/akka-samples/akka-sample-ants/README.md +++ /dev/null @@ -1,44 +0,0 @@ -Ants -==== - -Ants is written by Peter Vlugter. - -Ants is roughly based on the Clojure [ants simulation][ants.clj] by Rich Hickey, and ported to Scala using [Akka][akka] and [Spde][spde]. - -Requirements ------------- - -To build and run Ants you need [Simple Build Tool][sbt] (sbt). - -Running -------- - -First time, 'sbt update' to get dependencies, then to run Ants use 'sbt run'. -Here is an example. First type 'sbt' to start SBT interactively, the run 'update' and 'run': -> cd $AKKA_HOME - -> % sbt - -> > project akka-sample-ants - -> > run - - -Notice ------- - -Ants is roughly based on the Clojure ants simulation by Rich Hickey. - -Copyright (c) Rich Hickey. All rights reserved. -The use and distribution terms for this software are covered by the -Common Public License 1.0 ([http://opensource.org/licenses/cpl1.0.php][cpl]) -which can be found in the file cpl.txt at the root of this distribution. -By using this software in any fashion, you are agreeing to be bound by -the terms of this license. -You must not remove this notice, or any other, from this software. - -[ants.clj]: http://clojure.googlegroups.com/web/ants.clj -[akka]: http://akka.io -[spde]: http://technically.us/spde/ -[sbt]: http://code.google.com/p/simple-build-tool/ -[cpl]: http://opensource.org/licenses/cpl1.0.php \ No newline at end of file diff --git a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala b/akka-samples/akka-sample-ants/src/main/scala/Ants.scala deleted file mode 100644 index 4b9cd8dd88..0000000000 --- a/akka-samples/akka-sample-ants/src/main/scala/Ants.scala +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ - -package sample.ants - -import scala.util.Random.{ nextInt ⇒ randomInt } -import akka.actor.{ ActorSystem, Actor, ActorRef } -import akka.util.Duration -import akka.util.duration._ -import java.util.concurrent.TimeUnit -import akka.stm._ - -object Config { - val Dim = 80 // dimensions of square world - val AntsSqrt = 20 // number of ants = AntsSqrt^2 - val FoodPlaces = 35 // number of places with food - val FoodRange = 100 // range of amount of food at a place - val PherScale = 10 // scale factor for pheromone drawing - val AntMillis = 100 // how often an ant behaves (milliseconds) - val EvapMillis = 1000 // how often pheromone evaporation occurs (milliseconds) - val EvapRate = 0.99f // pheromone evaporation rate - val StartDelay = 1000 milliseconds // delay before everything kicks off (milliseconds) -} - -case class Ant(dir: Int, food: Boolean = false) { - def turn(i: Int) = copy(dir = Util.dirBound(dir + i)) - def turnAround = turn(4) - def pickUp = copy(food = true) - def dropOff = copy(food = false) -} - -case class Cell(food: Int = 0, pher: Float = 0, ant: Option[Ant] = None, home: Boolean = false) { - def addFood(i: Int) = copy(food = food + i) - def addPher(x: Float) = copy(pher = pher + x) - def alterPher(f: Float ⇒ Float) = copy(pher = f(pher)) - def putAnt(antOpt: Option[Ant]) = copy(ant = antOpt) - def makeHome = copy(home = true) -} - -object EmptyCell extends Cell - -class Place(initCell: Cell = EmptyCell) extends Ref(initCell) { - def cell: Cell = getOrElse(EmptyCell) - def food: Int = cell.food - def food(i: Int) = alter(_.addFood(i)) - def hasFood = food > 0 - def pher: Float = cell.pher - def pher(f: Float ⇒ Float) = alter(_.alterPher(f)) - def trail = alter(_.addPher(1)) - def ant: Option[Ant] = cell.ant - def ant(f: Ant ⇒ Ant): Cell = alter(_.putAnt(ant map f)) - def enter(antOpt: Option[Ant]): Cell = alter(_.putAnt(antOpt)) - def enter(ant: Ant): Cell = enter(Some(ant)) - def leave = enter(None) - def occupied: Boolean = ant.isDefined - def makeHome = alter(_.makeHome) - def home: Boolean = cell.home -} - -case object Ping - -object World { - import Config._ - - val system = ActorSystem() - - val homeOff = Dim / 4 - lazy val places = Vector.fill(Dim, Dim)(new Place) - lazy val ants = setup - lazy val evaporator = system.actorOf[Evaporator] - - private val snapshotFactory = TransactionFactory(readonly = true, familyName = "snapshot") - - def snapshot = atomic(snapshotFactory) { Array.tabulate(Dim, Dim)(place(_, _).opt) } - - def place(loc: (Int, Int)) = places(loc._1)(loc._2) - - private def setup = atomic { - for (i ← 1 to FoodPlaces) { - place(randomInt(Dim), randomInt(Dim)) food (randomInt(FoodRange)) - } - val homeRange = homeOff until (AntsSqrt + homeOff) - for (x ← homeRange; y ← homeRange) yield { - place(x, y).makeHome - place(x, y) enter Ant(randomInt(8)) - system.actorOf(new AntActor(x, y)) - } - } - - def start = { - ants foreach pingEvery(AntMillis) - pingEvery(EvapMillis)(evaporator) - } - - private def pingEvery(millis: Long)(actor: ActorRef) = - system.scheduler.schedule(actor, Ping, Config.StartDelay, Duration(millis, TimeUnit.MILLISECONDS)) -} - -object Util { - import Config._ - - def bound(b: Int, n: Int) = { - val x = n % b - if (x < 0) x + b else x - } - - def dirBound(n: Int) = bound(8, n) - def dimBound(n: Int) = bound(Dim, n) - - val dirDelta = Map(0 -> (0, -1), 1 -> (1, -1), 2 -> (1, 0), 3 -> (1, 1), - 4 -> (0, 1), 5 -> (-1, 1), 6 -> (-1, 0), 7 -> (-1, -1)) - def deltaLoc(x: Int, y: Int, dir: Int) = { - val (dx, dy) = dirDelta(dirBound(dir)) - (dimBound(x + dx), dimBound(y + dy)) - } - - def rankBy[A, B: Ordering](xs: Seq[A], f: A ⇒ B) = Map(xs.sortBy(f).zip(Stream from 1): _*) - - def roulette(slices: Seq[Int]) = { - val total = slices.sum - val r = randomInt(total) - var i, sum = 0 - while ((sum + slices(i)) <= r) { - sum += slices(i) - i += 1 - } - i - } -} - -trait WorldActor extends Actor { - def act - def receive = { case Ping ⇒ act } -} - -class AntActor(initLoc: (Int, Int)) extends WorldActor { - import World._ - import Util._ - - val locRef = Ref(initLoc) - - val name = "ant-from-" + initLoc._1 + "-" + initLoc._2 - implicit val txFactory = TransactionFactory(familyName = name) - - val homing = (p: Place) ⇒ p.pher + (100 * (if (p.home) 0 else 1)) - val foraging = (p: Place) ⇒ p.pher + p.food - - def loc = locRef.getOrElse(initLoc) - def newLoc(l: (Int, Int)) = locRef swap l - - def act = atomic { - val (x, y) = loc - val current = place(x, y) - for (ant ← current.ant) { - val ahead = place(deltaLoc(x, y, ant.dir)) - if (ant.food) { // homing - if (current.home) dropFood - else if (ahead.home && !ahead.occupied) move - else random(homing) - } else { // foraging - if (!current.home && current.hasFood) pickUpFood - else if (!ahead.home && ahead.hasFood && !ahead.occupied) move - else random(foraging) - } - } - } - - def move = { - val (x, y) = loc - val from = place(x, y) - for (ant ← from.ant) { - val toLoc = deltaLoc(x, y, ant.dir) - val to = place(toLoc) - to enter ant - from.leave - if (!from.home) from.trail - newLoc(toLoc) - } - } - - def pickUpFood = { - val current = place(loc) - current food -1 - current ant (_.pickUp.turnAround) - } - - def dropFood = { - val current = place(loc) - current food +1 - current ant (_.dropOff.turnAround) - } - - def random[A: Ordering](ranking: Place ⇒ A) = { - val (x, y) = loc - val current = place(x, y) - for (ant ← current.ant) { - val delta = (turn: Int) ⇒ place(deltaLoc(x, y, ant.dir + turn)) - val ahead = delta(0) - val aheadLeft = delta(-1) - val aheadRight = delta(+1) - val locations = Seq(ahead, aheadLeft, aheadRight) - val ranks = rankBy(locations, ranking) - val ranked = Seq(ranks(aheadLeft), (if (ahead.occupied) 0 else ranks(ahead)), ranks(aheadRight)) - val dir = roulette(ranked) - 1 - if (dir == 0) move - else current ant (_.turn(dir)) - } - } -} - -class Evaporator extends WorldActor { - import Config._ - import World._ - - implicit val txFactory = TransactionFactory(familyName = "evaporator") - val evaporate = (pher: Float) ⇒ pher * EvapRate - - def act = for (x ← 0 until Dim; y ← 0 until Dim) { - atomic { place(x, y) pher evaporate } - } -} diff --git a/akka-samples/akka-sample-fsm/src/README b/akka-samples/akka-sample-fsm/README similarity index 100% rename from akka-samples/akka-sample-fsm/src/README rename to akka-samples/akka-sample-fsm/README diff --git a/akka-samples/akka-sample-hello/src/README b/akka-samples/akka-sample-hello/README similarity index 100% rename from akka-samples/akka-sample-hello/src/README rename to akka-samples/akka-sample-hello/README diff --git a/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala b/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala deleted file mode 100644 index bf257ffd49..0000000000 --- a/akka-samples/akka-sample-osgi/src/main/scala/OsgiExample.scala +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (C) 2009-2011 Typesafe Inc. - */ -package sample.osgi - -import org.osgi.framework.{ BundleActivator, BundleContext } -import akka.actor.{ Timeout, ActorSystem, Actor } - -class Activator extends BundleActivator { - val system = ActorSystem() - - def start(context: BundleContext) { - println("Starting the OSGi example ...") - val echo = system.actorOf[EchoActor] - val answer = (echo ? ("OSGi example", Timeout(100))).as[String] - println(answer getOrElse "No answer!") - } - - def stop(context: BundleContext) { - system.stop() - println("Stopped the OSGi example.") - } -} - -class EchoActor extends Actor { - override def receive = { - case x ⇒ sender ! x - } -} diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index e07570f620..5359a05e66 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -415,8 +415,6 @@ object Dependencies { // val sampleCamel = Seq(camelCore, camelSpring, commonsCodec, Runtime.camelJms, Runtime.activemq, Runtime.springJms, // Test.junit, Test.scalatest, Test.logback) - val sampleOsgi = Seq(osgi) - val tutorials = Seq(Test.scalatest, Test.junit) val docs = Seq(Test.scalatest, Test.junit) From 16dee0e481b01b567473a60a5a127f0cb1d3ed70 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 30 Nov 2011 18:05:11 +0100 Subject: [PATCH 13/30] #1409 - offsetting the raciness and also refrain from having a separate Timer for each Active connection handler --- .../main/scala/akka/actor/ActorSystem.scala | 4 +- .../remote/netty/NettyRemoteSupport.scala | 60 +++++++++---------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 4f02473b4f..0e9e8211cb 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -235,7 +235,7 @@ abstract class ActorSystem extends ActorRefFactory { * Register a block of code to run after all actors in this actor system have * been stopped. */ - def registerOnTermination(code: ⇒ Unit) + def registerOnTermination[T](code: ⇒ T) /** * Register a block of code to run after all actors in this actor system have @@ -354,7 +354,7 @@ class ActorSystemImpl(val name: String, val applicationConfig: Config) extends A def start() = _start - def registerOnTermination(code: ⇒ Unit) { terminationFuture onComplete (_ ⇒ code) } + def registerOnTermination[T](code: ⇒ T) { terminationFuture onComplete (_ ⇒ code) } def registerOnTermination(code: Runnable) { terminationFuture onComplete (_ ⇒ code.run) } // TODO shutdown all that other stuff, whatever that may be diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index d12f5ea7e4..2bf25c6186 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -59,35 +59,34 @@ abstract class RemoteClient private[akka] ( /** * Converts the message to the wireprotocol and sends the message across the wire */ - def send(message: Any, senderOption: Option[ActorRef], recipient: ActorRef): Unit = + def send(message: Any, senderOption: Option[ActorRef], recipient: ActorRef): Unit = if (isRunning) { send(remoteSupport.createRemoteMessageProtocolBuilder(Left(recipient), Right(message), senderOption).build) + } else { + val exception = new RemoteClientException("RemoteModule client is not running, make sure you have invoked 'RemoteClient.connect()' before using it.", remoteSupport, remoteAddress) + remoteSupport.notifyListeners(RemoteClientError(exception, remoteSupport, remoteAddress)) + throw exception + } /** * Sends the message across the wire */ - def send(request: RemoteMessageProtocol) { - if (isRunning) { //TODO FIXME RACY - log.debug("Sending message: " + new RemoteMessage(request, remoteSupport)) + def send(request: RemoteMessageProtocol): Unit = { + log.debug("Sending message: {}", new RemoteMessage(request, remoteSupport)) - try { - val payload = remoteSupport.createMessageSendEnvelope(request) - currentChannel.write(payload).addListener( - new ChannelFutureListener { - def operationComplete(future: ChannelFuture) { - if (future.isCancelled) { - //Not interesting at the moment - } else if (!future.isSuccess) { - remoteSupport.notifyListeners(RemoteClientWriteFailed(payload, future.getCause, remoteSupport, remoteAddress)) - } + try { + val payload = remoteSupport.createMessageSendEnvelope(request) + currentChannel.write(payload).addListener( + new ChannelFutureListener { + def operationComplete(future: ChannelFuture) { + if (future.isCancelled) { + //Not interesting at the moment + } else if (!future.isSuccess) { + remoteSupport.notifyListeners(RemoteClientWriteFailed(payload, future.getCause, remoteSupport, remoteAddress)) } - }) - } catch { - case e: Exception ⇒ remoteSupport.notifyListeners(RemoteClientError(e, remoteSupport, remoteAddress)) - } - } else { - val exception = new RemoteClientException("RemoteModule client is not running, make sure you have invoked 'RemoteClient.connect()' before using it.", remoteSupport, remoteAddress) - remoteSupport.notifyListeners(RemoteClientError(exception, remoteSupport, remoteAddress)) - throw exception + } + }) + } catch { + case e: Exception ⇒ remoteSupport.notifyListeners(RemoteClientError(e, remoteSupport, remoteAddress)) } } @@ -132,8 +131,7 @@ class ActiveRemoteClient private[akka] ( private[remote] var connection: ChannelFuture = _ @volatile private[remote] var openChannels: DefaultChannelGroup = _ - @volatile - private var timer: HashedWheelTimer = _ + @volatile private var reconnectionTimeWindowStart = 0L @@ -180,10 +178,9 @@ class ActiveRemoteClient private[akka] ( runSwitch switchOn { openChannels = new DefaultDisposableChannelGroup(classOf[RemoteClient].getName) - timer = new HashedWheelTimer bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool, Executors.newCachedThreadPool)) - bootstrap.setPipelineFactory(new ActiveRemoteClientPipelineFactory(name, bootstrap, remoteAddress, timer, this)) + bootstrap.setPipelineFactory(new ActiveRemoteClientPipelineFactory(name, bootstrap, remoteAddress, this)) bootstrap.setOption("tcpNoDelay", true) bootstrap.setOption("keepAlive", true) @@ -219,8 +216,6 @@ class ActiveRemoteClient private[akka] ( log.debug("Shutting down remote client [{}]", name) notifyListeners(RemoteClientShutdown(remoteSupport, remoteAddress)) - timer.stop() - timer = null openChannels.close.awaitUninterruptibly openChannels = null bootstrap.releaseExternalResources() @@ -253,18 +248,17 @@ class ActiveRemoteClientPipelineFactory( name: String, bootstrap: ClientBootstrap, remoteAddress: RemoteAddress, - timer: HashedWheelTimer, client: ActiveRemoteClient) extends ChannelPipelineFactory { import client.remoteSupport.clientSettings._ def getPipeline: ChannelPipeline = { - val timeout = new ReadTimeoutHandler(timer, ReadTimeout.length, ReadTimeout.unit) + val timeout = new ReadTimeoutHandler(client.remoteSupport.timer, ReadTimeout.length, ReadTimeout.unit) val lenDec = new LengthFieldBasedFrameDecoder(MessageFrameSize, 0, 4, 0, 4) val lenPrep = new LengthFieldPrepender(4) val protobufDec = new ProtobufDecoder(AkkaRemoteProtocol.getDefaultInstance) val protobufEnc = new ProtobufEncoder - val remoteClient = new ActiveRemoteClientHandler(name, bootstrap, remoteAddress, timer, client) + val remoteClient = new ActiveRemoteClientHandler(name, bootstrap, remoteAddress, client.remoteSupport.timer, client) new StaticChannelPipeline(timeout, lenDec, protobufDec, lenPrep, protobufEnc, remoteClient) } @@ -361,6 +355,10 @@ class NettyRemoteSupport(_system: ActorSystem) extends RemoteSupport(_system) wi val serverSettings = RemoteExtension(system).serverSettings val clientSettings = RemoteExtension(system).clientSettings + val timer: HashedWheelTimer = new HashedWheelTimer + + _system.registerOnTermination(timer.stop()) //Shut this guy down at the end + private val remoteClients = new HashMap[RemoteAddress, RemoteClient] private val clientsLock = new ReentrantReadWriteLock From b42c6b6bd1abcf3b468d7c6d8caff33d1ebe9d0b Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Wed, 30 Nov 2011 18:23:58 +0100 Subject: [PATCH 14/30] Adding the origin address to the SHUTDOWN CommandType when sent and also removed a wasteful FIXME --- .../main/scala/akka/remote/netty/NettyRemoteSupport.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 2bf25c6186..1f85b4683c 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -517,6 +517,10 @@ class NettyRemoteServer(val remoteSupport: NettyRemoteSupport, val loader: Optio try { val shutdownSignal = { val b = RemoteControlProtocol.newBuilder.setCommandType(CommandType.SHUTDOWN) + b.setOrigin(RemoteProtocol.AddressProtocol.newBuilder + .setHostname(address.hostname) + .setPort(address.port) + .build) if (SecureCookie.nonEmpty) b.setCookie(SecureCookie.get) b.build @@ -646,8 +650,8 @@ class RemoteServerHandler( val inbound = RemoteAddress(origin.getHostname, origin.getPort) val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) - case CommandType.SHUTDOWN ⇒ //TODO FIXME Dispose passive connection here - case _ ⇒ //Unknown command + case CommandType.SHUTDOWN ⇒ //No need to do anything here + case _ ⇒ //Unknown command } case _ ⇒ //ignore } From e590a4877a8e3fac570e8a82f8c90b538e5da7cc Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 1 Dec 2011 10:27:28 +0100 Subject: [PATCH 15/30] Making the ConsistencySpec a tad more awesomized --- .../scala/akka/actor/ConsistencySpec.scala | 19 +++++++++++++++---- .../remote/netty/NettyRemoteSupport.scala | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala index c04e193c2b..c3f1b772db 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala @@ -10,15 +10,22 @@ object ConsistencySpec { class ConsistencyCheckingActor extends Actor { var left = new CacheMisaligned(42, 0, 0, 0) //var var right = new CacheMisaligned(0, 0, 0, 0) //var + var lastStep = -1L def receive = { - case "check" ⇒ + case step: Long ⇒ + + if (lastStep != (step - 1)) + sender.tell("Test failed: Last step %s, this step %s".format(lastStep, step)) + var shouldBeFortyTwo = left.value + right.value if (shouldBeFortyTwo != 42) - sender ! "Test failed" + sender ! "Test failed: 42 failed" else { left.value += 1 right.value -= 1 } + + lastStep = step case "done" ⇒ sender ! "done"; self.stop() } } @@ -40,8 +47,12 @@ class ConsistencySpec extends AkkaSpec { val props = Props[ConsistencyCheckingActor].withDispatcher(dispatcher) val actors = Vector.fill(3)(system.actorOf(props)) - for (i ← 1 to 1000000) actors(i % actors.size).tell("check", testActor) - for (a ← actors) a.tell("done", testActor) + + for (i ← 0L until 1000000L) { + actors.foreach(_.tell(i, testActor)) + } + + for (a ← actors) { a.tell("done", testActor) } for (a ← actors) expectMsg(5 minutes, "done") } diff --git a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala index 1f85b4683c..2732cf1ebf 100644 --- a/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/netty/NettyRemoteSupport.scala @@ -651,7 +651,7 @@ class RemoteServerHandler( val client = new PassiveRemoteClient(event.getChannel, remoteSupport, inbound) remoteSupport.bindClient(inbound, client) case CommandType.SHUTDOWN ⇒ //No need to do anything here - case _ ⇒ //Unknown command + case _ ⇒ //Unknown command } case _ ⇒ //ignore } From e3e694d1dcca5c66aaa98d6bfd7cc0bd2ef556a8 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 1 Dec 2011 17:02:30 +0100 Subject: [PATCH 16/30] Tweaking the consistency spec for using more cores --- .../src/test/scala/akka/actor/ConsistencySpec.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala index c3f1b772db..1118daff1c 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ConsistencySpec.scala @@ -35,10 +35,11 @@ class ConsistencySpec extends AkkaSpec { import ConsistencySpec._ "The Akka actor model implementation" must { "provide memory consistency" in { + val noOfActors = 7 val dispatcher = system .dispatcherFactory .newDispatcher("consistency-dispatcher", 1, UnboundedMailbox()) - .withNewThreadPoolWithArrayBlockingQueueWithCapacityAndFairness(1000, true) + .withNewThreadPoolWithArrayBlockingQueueWithCapacityAndFairness(noOfActors, true) .setCorePoolSize(10) .setMaxPoolSize(10) .setKeepAliveTimeInMillis(1) @@ -46,9 +47,9 @@ class ConsistencySpec extends AkkaSpec { .build val props = Props[ConsistencyCheckingActor].withDispatcher(dispatcher) - val actors = Vector.fill(3)(system.actorOf(props)) + val actors = Vector.fill(noOfActors)(system.actorOf(props)) - for (i ← 0L until 1000000L) { + for (i ← 0L until 600000L) { actors.foreach(_.tell(i, testActor)) } From ef27f865d456f2ab3c677c968157adb7b04ba2b0 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 1 Dec 2011 17:03:30 +0100 Subject: [PATCH 17/30] Adding support for ForkJoinPoolConfig so you can use ForkJoin --- .../akka/actor/dispatch/ActorModelSpec.scala | 36 ++++++++++++++ .../main/scala/akka/dispatch/Dispatcher.scala | 9 ++-- .../akka/dispatch/ThreadPoolBuilder.scala | 49 +++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala index e21f965c51..6b16042b69 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala @@ -492,3 +492,39 @@ class BalancingDispatcherModelSpec extends ActorModelSpec { } } } + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class FJDispatcherModelSpec extends ActorModelSpec { + import ActorModelSpec._ + + def newInterceptedDispatcher = + (new Dispatcher(system.dispatcherFactory.prerequisites, "foo", system.settings.DispatcherThroughput, + system.settings.DispatcherThroughputDeadlineTime, system.dispatcherFactory.MailboxType, + new ForkJoinPoolConfig(), system.settings.DispatcherDefaultShutdown) with MessageDispatcherInterceptor).asInstanceOf[MessageDispatcherInterceptor] + + def dispatcherType = "FJDispatcher" + + "A " + dispatcherType must { + "process messages in parallel" in { + implicit val dispatcher = newInterceptedDispatcher + val aStart, aStop, bParallel = new CountDownLatch(1) + val a, b = newTestActor(dispatcher) + + a ! Meet(aStart, aStop) + assertCountDown(aStart, 3.seconds.dilated.toMillis, "Should process first message within 3 seconds") + + b ! CountDown(bParallel) + assertCountDown(bParallel, 3.seconds.dilated.toMillis, "Should process other actors in parallel") + + aStop.countDown() + + a.stop + b.stop + + while (!a.isTerminated && !b.isTerminated) {} //Busy wait for termination + + assertRefDefaultZero(a)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + assertRefDefaultZero(b)(registers = 1, unregisters = 1, msgsReceived = 1, msgsProcessed = 1) + } + } +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala index ee28fd586e..1a40ee23cd 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Dispatcher.scala @@ -6,12 +6,9 @@ package akka.dispatch import akka.event.Logging.Warning import java.util.concurrent.atomic.AtomicReference -import java.util.concurrent.{ TimeUnit, ExecutorService, RejectedExecutionException, ConcurrentLinkedQueue } -import akka.actor.{ ActorCell, ActorKilledException } -import akka.actor.ActorSystem -import akka.event.EventStream -import akka.actor.Scheduler +import akka.actor.ActorCell import akka.util.Duration +import java.util.concurrent._ /** * Default settings are: @@ -156,4 +153,4 @@ abstract class PriorityGenerator extends java.util.Comparator[Envelope] { final def compare(thisMessage: Envelope, thatMessage: Envelope): Int = gen(thisMessage.message) - gen(thatMessage.message) -} +} \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala index f543e5c016..f92be79b2d 100644 --- a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala +++ b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala @@ -11,6 +11,9 @@ import akka.event.Logging.{ Warning, Error } import akka.actor.ActorSystem import java.util.concurrent._ import akka.event.EventStream +import concurrent.forkjoin.ForkJoinPool._ +import concurrent.forkjoin.{ ForkJoinTask, ForkJoinWorkerThread, ForkJoinPool } +import concurrent.forkjoin.ForkJoinTask._ object ThreadPoolConfig { type Bounds = Int @@ -184,6 +187,52 @@ class MonitorableThread(runnable: Runnable, name: String) } } +case class ForkJoinPoolConfig(targetParallelism: Int = Runtime.getRuntime.availableProcessors()) extends ExecutorServiceFactoryProvider { + final def createExecutorServiceFactory(name: String): ExecutorServiceFactory = new ExecutorServiceFactory { + def createExecutorService: ExecutorService = { + new ForkJoinPool(targetParallelism) with ExecutorService { + setAsyncMode(true) + setMaintainsParallelism(true) + + override def execute(r: Runnable) { + r match { + case fjmbox: FJMailbox ⇒ + //fjmbox.fjTask.reinitialize() + Thread.currentThread match { + case fjwt: ForkJoinWorkerThread if fjwt.getPool eq this ⇒ + fjmbox.fjTask.fork() //We should do fjwt.pushTask(fjmbox.fjTask) but it's package protected + case _ ⇒ super.execute[Unit](fjmbox.fjTask) + } + case _ ⇒ + super.execute(r) + } + } + + import java.util.{ Collection ⇒ JCollection } + + def invokeAny[T](callables: JCollection[_ <: Callable[T]]) = + throw new UnsupportedOperationException("invokeAny. NOT!") + + def invokeAny[T](callables: JCollection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = + throw new UnsupportedOperationException("invokeAny. NOT!") + + def invokeAll[T](callables: JCollection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = + throw new UnsupportedOperationException("invokeAny. NOT!") + } + } + } +} + +trait FJMailbox { self: Mailbox ⇒ + val fjTask = new ForkJoinTask[Unit] with Runnable { + var result: Unit = () + def getRawResult() = result + def setRawResult(v: Unit) { result = v } + def exec() = { self.run(); true } + def run() { invoke() } + } +} + /** * As the name says */ From bf7befc6905930c4f1eabf2f868730b64c82ee73 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 1 Dec 2011 23:43:56 +0100 Subject: [PATCH 18/30] Sprinkling some final magic sauce --- .../scala/akka/dispatch/ThreadPoolBuilder.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala index f92be79b2d..c45cc74593 100644 --- a/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala +++ b/akka-actor/src/main/scala/akka/dispatch/ThreadPoolBuilder.scala @@ -194,7 +194,7 @@ case class ForkJoinPoolConfig(targetParallelism: Int = Runtime.getRuntime.availa setAsyncMode(true) setMaintainsParallelism(true) - override def execute(r: Runnable) { + override final def execute(r: Runnable) { r match { case fjmbox: FJMailbox ⇒ //fjmbox.fjTask.reinitialize() @@ -224,12 +224,12 @@ case class ForkJoinPoolConfig(targetParallelism: Int = Runtime.getRuntime.availa } trait FJMailbox { self: Mailbox ⇒ - val fjTask = new ForkJoinTask[Unit] with Runnable { - var result: Unit = () - def getRawResult() = result - def setRawResult(v: Unit) { result = v } - def exec() = { self.run(); true } - def run() { invoke() } + final val fjTask = new ForkJoinTask[Unit] with Runnable { + private[this] var result: Unit = () + final def getRawResult() = result + final def setRawResult(v: Unit) { result = v } + final def exec() = { self.run(); true } + final def run() { invoke() } } } From 571d856f5403caef5b7ee07c87900a6ec7c82238 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 1 Dec 2011 23:52:16 +0100 Subject: [PATCH 19/30] Removing one use-site of startsWatching --- .../test/scala/akka/actor/dispatch/ActorModelSpec.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala index 6b16042b69..f07ed9dfa1 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/dispatch/ActorModelSpec.scala @@ -341,9 +341,11 @@ abstract class ActorModelSpec extends AkkaSpec { val cachedMessage = CountDownNStop(new CountDownLatch(num)) val stopLatch = new CountDownLatch(num) val waitTime = (30 seconds).dilated.toMillis - val boss = actorOf(Props(context ⇒ { - case "run" ⇒ for (_ ← 1 to num) (context.self startsWatching context.actorOf(props)) ! cachedMessage - case Terminated(child) ⇒ stopLatch.countDown() + val boss = actorOf(Props(new Actor { + def receive = { + case "run" ⇒ for (_ ← 1 to num) (watch(context.actorOf(props))) ! cachedMessage + case Terminated(child) ⇒ stopLatch.countDown() + } }).withDispatcher(system.dispatcherFactory.newPinnedDispatcher("boss"))) boss ! "run" try { From 54e2e9a35740e46d33b3358348b50b0076286b5a Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 2 Dec 2011 00:25:17 +0100 Subject: [PATCH 20/30] Switching more test code to use watch instead of startsWatching --- .../src/test/scala/akka/actor/FSMTransitionSpec.scala | 2 +- .../src/test/scala/akka/actor/RestartStrategySpec.scala | 4 +--- .../src/test/scala/akka/actor/SupervisorHierarchySpec.scala | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala index 917a65ec25..fdeabd2a47 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMTransitionSpec.scala @@ -58,7 +58,7 @@ class FSMTransitionSpec extends AkkaSpec with ImplicitSender { val forward = actorOf(new Forwarder(testActor)) val fsm = actorOf(new MyFSM(testActor)) val sup = actorOf(Props(new Actor { - self startsWatching fsm + watch(fsm) def receive = { case _ ⇒ } }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, None))) diff --git a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala index f7ad0d34cb..dd9e9ac79f 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/RestartStrategySpec.scala @@ -206,7 +206,7 @@ class RestartStrategySpec extends AkkaSpec { val boss = actorOf(Props(new Actor { def receive = { - case p: Props ⇒ sender ! context.actorOf(p) + case p: Props ⇒ sender ! watch(context.actorOf(p)) case t: Terminated ⇒ maxNoOfRestartsLatch.open } }).withFaultHandler(OneForOneStrategy(List(classOf[Throwable]), None, Some(1000)))) @@ -228,8 +228,6 @@ class RestartStrategySpec extends AkkaSpec { }) val slave = (boss ? slaveProps).as[ActorRef].get - boss startsWatching slave - slave ! Ping slave ! Crash slave ! Ping diff --git a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala index e3d2bf1eea..7b6299ab69 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/SupervisorHierarchySpec.scala @@ -52,8 +52,7 @@ class SupervisorHierarchySpec extends AkkaSpec { val countDownMessages = new CountDownLatch(1) val countDownMax = new CountDownLatch(1) val boss = actorOf(Props(new Actor { - val crasher = context.actorOf(Props(new CountDownActor(countDownMessages))) - self startsWatching crasher + val crasher = watch(context.actorOf(Props(new CountDownActor(countDownMessages)))) protected def receive = { case "killCrasher" ⇒ crasher ! Kill From fcc6169edea2a0e89b47e0590404d56095b7eec6 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 2 Dec 2011 01:00:55 +0100 Subject: [PATCH 21/30] Removing the final usages of startsWatching/stopsWatching --- .../scala/akka/actor/DeathWatchSpec.scala | 34 +++++++--------- .../src/main/scala/akka/actor/Actor.scala | 10 ++--- .../src/main/scala/akka/actor/ActorCell.scala | 8 +++- .../src/main/scala/akka/actor/ActorRef.scala | 39 ------------------- .../akka/camel/component/ActorComponent.scala | 3 -- .../scala/akka/testkit/TestActorRef.scala | 18 +++++++++ .../scala/akka/testkit/TestActorRefSpec.scala | 5 ++- 7 files changed, 48 insertions(+), 69 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala index 90e398e4cb..88b31f25d9 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeathWatchSpec.scala @@ -11,6 +11,10 @@ import java.util.concurrent.atomic._ @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender { + def startWatching(target: ActorRef) = actorOf(Props(new Actor { + watch(target) + def receive = { case x ⇒ testActor forward x } + })) "The Death Watch" must { def expectTerminationOf(actorRef: ActorRef) = expectMsgPF(5 seconds, actorRef + ": Stopped or Already terminated when linking") { @@ -19,8 +23,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende "notify with one Terminated message when an Actor is stopped" in { val terminal = actorOf(Props(context ⇒ { case _ ⇒ })) - - testActor startsWatching terminal + startWatching(terminal) testActor ! "ping" expectMsg("ping") @@ -32,11 +35,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende "notify with all monitors with one Terminated message when an Actor is stopped" in { val terminal = actorOf(Props(context ⇒ { case _ ⇒ })) - val monitor1, monitor2, monitor3 = - actorOf(Props(new Actor { - watch(terminal) - def receive = { case t: Terminated ⇒ testActor ! t } - })) + val monitor1, monitor2, monitor3 = startWatching(terminal) terminal ! PoisonPill @@ -51,11 +50,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende "notify with _current_ monitors with one Terminated message when an Actor is stopped" in { val terminal = actorOf(Props(context ⇒ { case _ ⇒ })) - val monitor1, monitor3 = - actorOf(Props(new Actor { - watch(terminal) - def receive = { case t: Terminated ⇒ testActor ! t } - })) + val monitor1, monitor3 = startWatching(terminal) val monitor2 = actorOf(Props(new Actor { watch(terminal) unwatch(terminal) @@ -85,10 +80,7 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende val terminalProps = Props(context ⇒ { case x ⇒ context.sender ! x }) val terminal = (supervisor ? terminalProps).as[ActorRef].get - val monitor = actorOf(Props(new Actor { - watch(terminal) - def receive = { case t: Terminated ⇒ testActor ! t } - })) + val monitor = startWatching(terminal) terminal ! Kill terminal ! Kill @@ -113,9 +105,13 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende } })) - val failed, brother = (supervisor ? Props.empty).as[ActorRef].get - brother startsWatching failed - testActor startsWatching brother + val failed = (supervisor ? Props.empty).as[ActorRef].get + val brother = (supervisor ? Props(new Actor { + watch(failed) + def receive = Actor.emptyBehavior + })).as[ActorRef].get + + startWatching(brother) failed ! Kill val result = receiveWhile(3 seconds, messages = 3) { diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index b8c0bbb327..8f613e02f5 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -363,24 +363,24 @@ trait Actor { * Puts the behavior on top of the hotswap stack. * If "discardOld" is true, an unbecome will be issued prior to pushing the new behavior to the stack */ - def become(behavior: Receive, discardOld: Boolean = true) { context.become(behavior, discardOld) } + final def become(behavior: Receive, discardOld: Boolean = true) { context.become(behavior, discardOld) } /** * Reverts the Actor behavior to the previous one in the hotswap stack. */ - def unbecome() { context.unbecome() } + final def unbecome() { context.unbecome() } /** * Registers this actor as a Monitor for the provided ActorRef * @return the provided ActorRef */ - def watch(subject: ActorRef): ActorRef = self startsWatching subject + final def watch(subject: ActorRef): ActorRef = context startsWatching subject /** * Unregisters this actor as Monitor for the provided ActorRef * @return the provided ActorRef */ - def unwatch(subject: ActorRef): ActorRef = self stopsWatching subject + final def unwatch(subject: ActorRef): ActorRef = context stopsWatching subject // ========================================= // ==== INTERNAL IMPLEMENTATION DETAILS ==== @@ -395,6 +395,6 @@ trait Actor { } } - private val processingBehavior = receive //ProcessingBehavior is the original behavior + private[this] val processingBehavior = receive //ProcessingBehavior is the original behavior } diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index afd462ff1e..704d78d38b 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -47,6 +47,10 @@ trait ActorContext extends ActorRefFactory { def system: ActorSystem def parent: ActorRef + + def startsWatching(subject: ActorRef): ActorRef + + def stopsWatching(subject: ActorRef): ActorRef } private[akka] object ActorCell { @@ -136,13 +140,13 @@ private[akka] class ActorCell( // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ private[akka] def stop(): Unit = dispatcher.systemDispatch(this, Terminate()) - final def startsWatching(subject: ActorRef): ActorRef = { + override final def startsWatching(subject: ActorRef): ActorRef = { // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ dispatcher.systemDispatch(this, Link(subject)) subject } - final def stopsWatching(subject: ActorRef): ActorRef = { + override final def stopsWatching(subject: ActorRef): ActorRef = { // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅ dispatcher.systemDispatch(this, Unlink(subject)) subject diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index e62a04938a..c86a2dc66e 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -128,24 +128,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable */ def isTerminated: Boolean - /** - * Registers this actor to be a death monitor of the provided ActorRef - * This means that this actor will get a Terminated()-message when the provided actor - * is permanently terminated. - * - * @return the same ActorRef that is provided to it, to allow for cleaner invocations - */ - def startsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS - - /** - * Deregisters this actor from being a death monitor of the provided ActorRef - * This means that this actor will not get a Terminated()-message when the provided actor - * is permanently terminated. - * - * @return the same ActorRef that is provided to it, to allow for cleaner invocations - */ - def stopsWatching(subject: ActorRef): ActorRef //TODO FIXME REMOVE THIS - override def hashCode: Int = HashCode.hash(HashCode.SEED, address) override def equals(that: Any): Boolean = { @@ -215,24 +197,6 @@ class LocalActorRef private[akka] ( */ def stop(): Unit = actorCell.stop() - /** - * Registers this actor to be a death monitor of the provided ActorRef - * This means that this actor will get a Terminated()-message when the provided actor - * is permanently terminated. - * - * @return the same ActorRef that is provided to it, to allow for cleaner invocations - */ - def startsWatching(subject: ActorRef): ActorRef = actorCell.startsWatching(subject) - - /** - * Deregisters this actor from being a death monitor of the provided ActorRef - * This means that this actor will not get a Terminated()-message when the provided actor - * is permanently terminated. - * - * @return the same ActorRef that is provided to it, to allow for cleaner invocations - */ - def stopsWatching(subject: ActorRef): ActorRef = actorCell.stopsWatching(subject) - // ========= AKKA PROTECTED FUNCTIONS ========= protected[akka] def underlying: ActorCell = actorCell @@ -330,9 +294,6 @@ trait MinimalActorRef extends ActorRef with ScalaActorRef { private[akka] val uuid: Uuid = newUuid() def name: String = uuid.toString - def startsWatching(actorRef: ActorRef): ActorRef = actorRef - def stopsWatching(actorRef: ActorRef): ActorRef = actorRef - def suspend(): Unit = () def resume(): Unit = () diff --git a/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala b/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala index 795fbf5a54..c4ec7dcf31 100644 --- a/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala +++ b/akka-camel/src/main/scala/akka/camel/component/ActorComponent.scala @@ -293,9 +293,6 @@ private[akka] class AsyncCallbackAdapter(exchange: Exchange, callback: AsyncCall callback.done(false) } - def startsWatching(actorRef: ActorRef): ActorRef = unsupported - def stopsWatching(actorRef: ActorRef): ActorRef = unsupported - def ?(message: Any)(implicit timeout: Timeout): Future[Any] = new KeptPromise[Any](Left(new UnsupportedOperationException("Ask/? is not supported for %s".format(getClass.getName)))) def restart(reason: Throwable): Unit = unsupported diff --git a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala index 6f03df59b2..18a181eb3f 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestActorRef.scala @@ -42,6 +42,24 @@ class TestActorRef[T <: Actor]( */ def underlyingActor: T = underlyingActorInstance.asInstanceOf[T] + /** + * Registers this actor to be a death monitor of the provided ActorRef + * This means that this actor will get a Terminated()-message when the provided actor + * is permanently terminated. + * + * @return the same ActorRef that is provided to it, to allow for cleaner invocations + */ + def startsWatching(subject: ActorRef): ActorRef = underlying.startsWatching(subject) + + /** + * Deregisters this actor from being a death monitor of the provided ActorRef + * This means that this actor will not get a Terminated()-message when the provided actor + * is permanently terminated. + * + * @return the same ActorRef that is provided to it, to allow for cleaner invocations + */ + def stopsWatching(subject: ActorRef): ActorRef = underlying.stopsWatching(subject) + override def toString = "TestActor[" + address + "]" override def equals(other: Any) = other.isInstanceOf[TestActorRef[_]] && other.asInstanceOf[TestActorRef[_]].address == address diff --git a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala index a6248ff63c..12096a61b1 100644 --- a/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/TestActorRefSpec.scala @@ -155,7 +155,10 @@ class TestActorRefSpec extends AkkaSpec with BeforeAndAfterEach { "stop when sent a poison pill" in { EventFilter[ActorKilledException]() intercept { val a = TestActorRef(Props[WorkerActor]) - testActor startsWatching a + val forwarder = actorOf(Props(new Actor { + watch(a) + def receive = { case x ⇒ testActor forward x } + })) a.!(PoisonPill)(testActor) expectMsgPF(5 seconds) { case Terminated(`a`) ⇒ true From 879ea7c2b4b2ecaeaca0651ec92d306bfa7aa6c2 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 2 Dec 2011 01:04:33 +0100 Subject: [PATCH 22/30] Removing startsWatching and stopsWatching from docs and removing cruft --- akka-docs/java/remote-actors.rst | 3 --- akka-docs/scala/remote-actors.rst | 3 --- .../main/scala/akka/remote/RemoteActorRefProvider.scala | 8 +------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/akka-docs/java/remote-actors.rst b/akka-docs/java/remote-actors.rst index 745679c537..2147e03db1 100644 --- a/akka-docs/java/remote-actors.rst +++ b/akka-docs/java/remote-actors.rst @@ -178,10 +178,7 @@ The messages that it prevents are all that extends 'LifeCycleMessage': * case object ReceiveTimeout It also prevents the client from invoking any life-cycle and side-effecting methods, such as: -* start * stop -* startsWatching -* stopsWatching * etc. Using secure cookie for remote client authentication diff --git a/akka-docs/scala/remote-actors.rst b/akka-docs/scala/remote-actors.rst index ab2fe345c8..c408436b1e 100644 --- a/akka-docs/scala/remote-actors.rst +++ b/akka-docs/scala/remote-actors.rst @@ -180,10 +180,7 @@ The messages that it prevents are all that extends 'LifeCycleMessage': * class ReceiveTimeout..) It also prevents the client from invoking any life-cycle and side-effecting methods, such as: -* start * stop -* startsWatching -* stopsWatching * etc. Using secure cookie for remote client authentication diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index 358568e13c..a10f8ca63a 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -296,7 +296,7 @@ private[akka] case class RemoteActorRef private[akka] ( def isTerminated: Boolean = !running - protected[akka] def sendSystemMessage(message: SystemMessage): Unit = unsupported + protected[akka] def sendSystemMessage(message: SystemMessage): Unit = throw new UnsupportedOperationException("Not supported for RemoteActorRef") override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), remoteAddress, this, loader) @@ -318,11 +318,5 @@ private[akka] case class RemoteActorRef private[akka] ( @throws(classOf[java.io.ObjectStreamException]) private def writeReplace(): AnyRef = provider.serialize(this) - def startsWatching(actorRef: ActorRef): ActorRef = unsupported //FIXME Implement - - def stopsWatching(actorRef: ActorRef): ActorRef = unsupported //FIXME Implement - protected[akka] def restart(cause: Throwable): Unit = () - - private def unsupported = throw new UnsupportedOperationException("Not supported for RemoteActorRef") } From d626cc24554cfada952d9f7aa45a0cbf35ba5405 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 2 Dec 2011 01:27:42 +0100 Subject: [PATCH 23/30] Removing suspend and resume from user-facing API --- .../src/main/scala/akka/actor/ActorRef.scala | 20 ++++++---------- .../main/scala/akka/actor/FaultHandling.scala | 10 ++++---- akka-docs/java/dispatchers.rst | 23 +++++++++---------- akka-docs/scala/dispatchers.rst | 19 ++++++--------- .../akka/remote/RemoteActorRefProvider.scala | 2 +- 5 files changed, 31 insertions(+), 43 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRef.scala b/akka-actor/src/main/scala/akka/actor/ActorRef.scala index c86a2dc66e..30a20ad769 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRef.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRef.scala @@ -46,7 +46,7 @@ import akka.event.DeathWatch * @author Jonas Bonér */ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable { - scalaRef: ScalaActorRef ⇒ + scalaRef: ScalaActorRef with RefInternals ⇒ // Only mutable for RemoteServer in order to maintain identity across nodes /** @@ -108,16 +108,6 @@ abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable */ def forward(message: Any)(implicit context: ActorContext) = tell(message, context.sender) - /** - * Suspends the actor. It will not process messages while suspended. - */ - def suspend(): Unit //TODO FIXME REMOVE THIS - - /** - * Resumes a suspended actor. - */ - def resume(): Unit //TODO FIXME REMOVE THIS - /** * Shuts down the actor its dispatcher and message queue. */ @@ -151,7 +141,7 @@ class LocalActorRef private[akka] ( val systemService: Boolean = false, _receiveTimeout: Option[Long] = None, _hotswap: Stack[PartialFunction[Any, Unit]] = Props.noHotSwap) - extends ActorRef with ScalaActorRef { + extends ActorRef with ScalaActorRef with RefInternals { def name = path.name @@ -260,7 +250,11 @@ trait ScalaActorRef { ref: ActorRef ⇒ * implicit timeout */ def ?(message: Any, timeout: Timeout)(implicit ignore: Int = 0): Future[Any] = ?(message)(timeout) +} +private[akka] trait RefInternals { + def resume(): Unit + def suspend(): Unit protected[akka] def restart(cause: Throwable): Unit } @@ -289,7 +283,7 @@ case class SerializedActorRef(hostname: String, port: Int, path: String) { /** * Trait for ActorRef implementations where all methods contain default stubs. */ -trait MinimalActorRef extends ActorRef with ScalaActorRef { +trait MinimalActorRef extends ActorRef with ScalaActorRef with RefInternals { private[akka] val uuid: Uuid = newUuid() def name: String = uuid.toString diff --git a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala index 4656f5a3e3..b079550998 100644 --- a/akka-actor/src/main/scala/akka/actor/FaultHandling.scala +++ b/akka-actor/src/main/scala/akka/actor/FaultHandling.scala @@ -122,12 +122,12 @@ abstract class FaultHandlingStrategy { def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.suspend()) + children.foreach(_.asInstanceOf[RefInternals].suspend()) } def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = { if (children.nonEmpty) - children.foreach(_.restart(cause)) + children.foreach(_.asInstanceOf[RefInternals].restart(cause)) } /** @@ -136,7 +136,7 @@ abstract class FaultHandlingStrategy { def handleFailure(child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = { val action = if (decider.isDefinedAt(cause)) decider(cause) else Escalate action match { - case Resume ⇒ child.resume(); true + case Resume ⇒ child.asInstanceOf[RefInternals].resume(); true case Restart ⇒ processFailure(true, child, cause, stats, children); true case Stop ⇒ processFailure(false, child, cause, stats, children); true case Escalate ⇒ false @@ -194,7 +194,7 @@ case class AllForOneStrategy(decider: FaultHandlingStrategy.Decider, def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { if (children.nonEmpty) { if (restart && children.forall(_.requestRestartPermission(retriesWindow))) - children.foreach(_.child.restart(cause)) + children.foreach(_.child.asInstanceOf[RefInternals].restart(cause)) else children.foreach(_.child.stop()) } @@ -247,7 +247,7 @@ case class OneForOneStrategy(decider: FaultHandlingStrategy.Decider, def processFailure(restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = { if (restart && stats.requestRestartPermission(retriesWindow)) - child.restart(cause) + child.asInstanceOf[RefInternals].restart(cause) else child.stop() //TODO optimization to drop child here already? } diff --git a/akka-docs/java/dispatchers.rst b/akka-docs/java/dispatchers.rst index d9f32f38c9..dc2684f9d8 100644 --- a/akka-docs/java/dispatchers.rst +++ b/akka-docs/java/dispatchers.rst @@ -155,6 +155,15 @@ Creating a Dispatcher with a priority mailbox using PriorityGenerator: public class Main { // A simple Actor that just prints the messages it processes public static class MyActor extends UntypedActor { + public MyActor() { + self.tell("lowpriority"); + getSelf().tell("lowpriority"); + getSelf().tell("highpriority"); + getSelf().tell("pigdog"); + getSelf().tell("pigdog2"); + getSelf().tell("pigdog3"); + getSelf().tell("highpriority"); + } public void onReceive(Object message) throws Exception { System.out.println(message); } @@ -170,19 +179,9 @@ Creating a Dispatcher with a priority mailbox using PriorityGenerator: } }; // We create an instance of the actor that will print out the messages it processes - ActorRef ref = Actors.actorOf(MyActor.class); - // We create a new Priority dispatcher and seed it with the priority generator - ref.setDispatcher(new Dispatcher("foo", 5, new UnboundedPriorityMailbox(gen))); + // We create a new Priority dispatcher and seed it with the priority generator + ActorRef ref = Actors.actorOf((new Props()).withCreator(MyActor.class).withDispatcher(new Dispatcher("foo", 5, new UnboundedPriorityMailbox(gen)))); - ref.getDispatcher().suspend(ref); // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-) - ref.tell("lowpriority"); - ref.tell("lowpriority"); - ref.tell("highpriority"); - ref.tell("pigdog"); - ref.tell("pigdog2"); - ref.tell("pigdog3"); - ref.tell("highpriority"); - ref.getDispatcher().resume(ref); // Resuming the actor so it will start treating its messages } } diff --git a/akka-docs/scala/dispatchers.rst b/akka-docs/scala/dispatchers.rst index fa00c746f5..e16c336753 100644 --- a/akka-docs/scala/dispatchers.rst +++ b/akka-docs/scala/dispatchers.rst @@ -155,23 +155,18 @@ Creating a Dispatcher using PriorityGenerator: val a = Actor.actorOf( // We create a new Actor that just prints out what it processes Props(new Actor { + self ! 'lowpriority + self ! 'lowpriority + self ! 'highpriority + self ! 'pigdog + self ! 'pigdog2 + self ! 'pigdog3 + self ! 'highpriority def receive = { case x => println(x) } }).withDispatcher(new Dispatcher("foo", 5, UnboundedPriorityMailbox(gen)))) // We create a new Priority dispatcher and seed it with the priority generator - a.dispatcher.suspend(a) // Suspending the actor so it doesn't start to treat the messages before we have enqueued all of them :-) - - a ! 'lowpriority - a ! 'lowpriority - a ! 'highpriority - a ! 'pigdog - a ! 'pigdog2 - a ! 'pigdog3 - a ! 'highpriority - - a.dispatcher.resume(a) // Resuming the actor so it will start treating its messages - Prints: 'highpriority diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index a10f8ca63a..385a27f29a 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -285,7 +285,7 @@ private[akka] case class RemoteActorRef private[akka] ( remoteAddress: RemoteAddress, path: ActorPath, loader: Option[ClassLoader]) - extends ActorRef with ScalaActorRef { + extends ActorRef with ScalaActorRef with RefInternals { @volatile private var running: Boolean = true From 66bf11681c88b7ba58e9a3134374d21065130937 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 08:51:51 +0100 Subject: [PATCH 24/30] Changed config.toValue -> config.root --- .../src/test/scala/akka/performance/workbench/Report.scala | 2 +- akka-actor/src/main/scala/akka/actor/Deployer.scala | 2 +- .../src/main/scala/akka/serialization/Serialization.scala | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala index 36ba40c2e9..49d3b23b42 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/workbench/Report.scala @@ -222,7 +222,7 @@ class Report( sb.append("Akka version: ").append(system.settings.ConfigVersion) sb.append("\n") sb.append("Akka config:") - for ((key, value) ← system.settings.config.toValue) { + for ((key, value) ← system.settings.config.root) { sb.append("\n ").append(key).append("=").append(value) } diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index da666c2522..4220904a14 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -88,7 +88,7 @@ class Deployer(val settings: ActorSystem.Settings, val eventStream: EventStream, } import scala.collection.JavaConverters._ - settings.config.getConfig("akka.actor.deployment").toValue.keySet.asScala + settings.config.getConfig("akka.actor.deployment").root.keySet.asScala .filterNot("default" ==) .map(path ⇒ pathSubstring(path)) .toSet.toList // toSet to force uniqueness diff --git a/akka-actor/src/main/scala/akka/serialization/Serialization.scala b/akka-actor/src/main/scala/akka/serialization/Serialization.scala index f7ca6aae9b..d1764e8390 100644 --- a/akka-actor/src/main/scala/akka/serialization/Serialization.scala +++ b/akka-actor/src/main/scala/akka/serialization/Serialization.scala @@ -32,7 +32,7 @@ object Serialization { hasPath(configPath) match { case false ⇒ Map() case true ⇒ - val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).toValue.unwrapped.asScala.toMap.map { + val serializationBindings: Map[String, Seq[String]] = getConfig(configPath).root.unwrapped.asScala.toMap.map { case (k: String, v: java.util.Collection[_]) ⇒ (k -> v.asScala.toSeq.asInstanceOf[Seq[String]]) case invalid ⇒ throw new ConfigurationException("Invalid serialization-bindings [%s]".format(invalid)) } @@ -42,7 +42,7 @@ object Serialization { } private def toStringMap(mapConfig: Config): Map[String, String] = - mapConfig.toValue.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } + mapConfig.root.unwrapped.asScala.toMap.map { case (k, v) ⇒ (k, v.toString) } } } From fd82251501138c29168d0c26fa27df21f612cd98 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 09:26:56 +0100 Subject: [PATCH 25/30] Minor fixes from review comments. --- akka-actor/src/main/scala/akka/actor/ActorSystem.scala | 1 - .../main/scala/akka/actor/BootableActorLoaderService.scala | 2 -- akka-actor/src/main/scala/akka/dispatch/Future.scala | 5 +---- .../src/main/scala/akka/remote/RemoteActorRefProvider.scala | 2 +- akka-remote/src/main/scala/akka/remote/RemoteExtension.scala | 1 - .../src/test/scala/akka/remote/RemoteConfigSpec.scala | 1 - 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index b2cf89c39e..7534a6de77 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -347,7 +347,6 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor // TODO why implicit val dispatcher? implicit val dispatcher = dispatcherFactory.defaultGlobalDispatcher - //FIXME Set this to a Failure when things bubble to the top. What does this mean? def terminationFuture: Future[Unit] = provider.terminationFuture def guardian: ActorRef = provider.guardian def systemGuardian: ActorRef = provider.systemGuardian diff --git a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala index 6f8608378d..47c2cd86c7 100644 --- a/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala +++ b/akka-actor/src/main/scala/akka/actor/BootableActorLoaderService.scala @@ -57,8 +57,6 @@ trait BootableActorLoaderService extends Bootable { abstract override def onUnload() = { super.onUnload() - - system.stop() } } diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index 274f82fe6c..b9def99301 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -27,10 +27,7 @@ class FutureTimeoutException(message: String, cause: Throwable = null) extends A def this(message: String) = this(message, null) } -class FutureFactory(dispatcher: MessageDispatcher, timeout: Timeout) { - - // TODO: remove me ASAP !!! - implicit val _dispatcher = dispatcher +class FutureFactory()(implicit dispatcher: MessageDispatcher, timeout: Timeout) { /** * Java API, equivalent to Future.apply diff --git a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala index e08adeebb4..a0d63ecb2d 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala @@ -306,7 +306,7 @@ private[akka] case class RemoteActorRef private[akka] ( def resume(): Unit = () - def stop() { //FIXME send the cause as well! (WDYM?) + def stop() { synchronized { if (running) { running = false diff --git a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala index 00bd3eadcf..3b0088d55c 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala @@ -21,7 +21,6 @@ class RemoteExtensionSettings(val config: Config) extends Extension { import config._ - val RemoteTransport = getString("akka.remote.transport") val FailureDetectorThreshold = getInt("akka.remote.failure-detector.threshold") val FailureDetectorMaxSampleSize = getInt("akka.remote.failure-detector.max-sample-size") val ShouldCompressData = config.getBoolean("akka.remote.use-compression") diff --git a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala index d11d07ddd3..156ad94ff7 100644 --- a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala @@ -12,7 +12,6 @@ class RemoteConfigSpec extends AkkaSpec("akka.cluster.nodename = node1") { import config._ //akka.remote - getString("akka.remote.transport") must equal("akka.cluster.netty.NettyRemoteSupport") getString("akka.remote.secure-cookie") must equal("") getBoolean("akka.remote.use-passive-connections") must equal(true) // getMilliseconds("akka.remote.remote-daemon-ack-timeout") must equal(30 * 1000) From b488d70f548ff073f0c643b9d9e9ec5cc27fb5b1 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Wed, 30 Nov 2011 15:16:20 +0100 Subject: [PATCH 26/30] Fixed several memory and thread leaks. See #1404 * Dispatchers need Scheduler to be able to shutdown themselves. Stop Scheduler after dispatchers. * Changed CallingThreadDispatcher global object to Extension, since it holds map of references to mailboxes. Will be GC:ed when system is GC:ed. * Made testActor lazy, since it is not used in all tests, and it creates CallingThreadDispatcher. * Activated some java tests that were not running * Many tests were not stopping created ActorSystems. VERY IMPORTANT TO STOP ActorSystem in tests. Use AkkaSpec as much as possible. * Used profiler to verify (and find) dangling ActorSystemImpl and threads from dispatchers. * FutureSpec creates ForkJoinPool threads that are not cleared, but number of threads don't grow so it's not a problem. --- .../src/test/java/akka/actor/JavaAPI.java | 23 +++++-- .../test/java/akka/actor/JavaExtension.java | 23 ++++++- .../java/akka/dispatch/JavaFutureTests.java | 24 ++++++- .../src/test/java/akka/util/JavaDuration.java | 3 +- .../scala/akka/actor/ActorSystemSpec.scala | 16 ++--- .../src/test/scala/akka/actor/Bench.scala | 3 +- .../test/scala/akka/actor/DeployerSpec.scala | 4 +- .../test/scala/akka/actor/FSMActorSpec.scala | 67 ++++++++++--------- .../test/scala/akka/actor/JavaAPISpec.scala | 8 +++ .../scala/akka/event/LoggingReceiveSpec.scala | 2 +- .../TellThroughput10000PerformanceSpec.scala | 2 +- ...ThroughputComputationPerformanceSpec.scala | 2 +- .../TellThroughputPerformanceSpec.scala | 2 +- ...utSeparateDispatchersPerformanceSpec.scala | 2 +- .../akka/serialization/SerializeSpec.scala | 20 +++--- .../scala/akka/util/JavaDurationSpec.scala | 8 +++ .../main/scala/akka/actor/ActorSystem.scala | 12 +++- .../akka/dispatch/AbstractDispatcher.scala | 4 +- .../src/main/scala/akka/event/Logging.scala | 6 +- .../mailbox/filequeue/tools/QDumper.scala | 2 + .../actor/mailbox/DurableMailboxSpec.scala | 3 +- .../mailbox/ZooKeeperBasedMailboxSpec.scala | 2 +- .../java/akka/stm/example/RetryExample.java | 10 +-- .../example/UntypedCoordinatedExample.java | 7 +- .../example/UntypedTransactorExample.java | 7 +- .../test/UntypedCoordinatedIncrementTest.java | 17 ++++- .../test/UntypedTransactorTest.java | 23 +++++-- .../testkit/CallingThreadDispatcher.scala | 17 +++-- .../src/main/scala/akka/testkit/TestKit.scala | 2 +- .../test/scala/akka/testkit/AkkaSpec.scala | 5 +- .../src/test/scala/WorkerSpec.scala | 11 ++- 31 files changed, 232 insertions(+), 105 deletions(-) create mode 100644 akka-actor-tests/src/test/scala/akka/actor/JavaAPISpec.scala create mode 100644 akka-actor-tests/src/test/scala/akka/util/JavaDurationSpec.scala diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaAPI.java b/akka-actor-tests/src/test/java/akka/actor/JavaAPI.java index d0ef5104cd..f8600f3e1b 100644 --- a/akka-actor-tests/src/test/java/akka/actor/JavaAPI.java +++ b/akka-actor-tests/src/test/java/akka/actor/JavaAPI.java @@ -2,21 +2,36 @@ package akka.actor; import akka.actor.ActorSystem; import akka.japi.Creator; +import akka.testkit.AkkaSpec; + +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; public class JavaAPI { - private ActorSystem system = ActorSystem.create(); + private static ActorSystem system; + + @BeforeClass + public static void beforeAll() { + system = ActorSystem.create("JavaAPI", AkkaSpec.testConf()); + } + + @AfterClass + public static void afterAll() { + system.stop(); + system = null; + } @Test - void mustBeAbleToCreateActorRefFromClass() { + public void mustBeAbleToCreateActorRefFromClass() { ActorRef ref = system.actorOf(JavaAPITestActor.class); assertNotNull(ref); } @Test - void mustBeAbleToCreateActorRefFromFactory() { + public void mustBeAbleToCreateActorRefFromFactory() { ActorRef ref = system.actorOf(new Props().withCreator(new Creator() { public Actor create() { return new JavaAPITestActor(); @@ -26,7 +41,7 @@ public class JavaAPI { } @Test - void mustAcceptSingleArgTell() { + public void mustAcceptSingleArgTell() { ActorRef ref = system.actorOf(JavaAPITestActor.class); ref.tell("hallo"); ref.tell("hallo", ref); diff --git a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java index a08060f52d..20d760c46e 100644 --- a/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java +++ b/akka-actor-tests/src/test/java/akka/actor/JavaExtension.java @@ -3,8 +3,12 @@ */ package akka.actor; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; +import akka.testkit.AkkaSpec; + import com.typesafe.config.ConfigFactory; import com.typesafe.config.Config; @@ -13,7 +17,9 @@ import static org.junit.Assert.*; public class JavaExtension { static class Provider implements ExtensionIdProvider { - public ExtensionId lookup() { return defaultInstance; } + public ExtensionId lookup() { + return defaultInstance; + } } public final static TestExtensionId defaultInstance = new TestExtensionId(); @@ -32,9 +38,20 @@ public class JavaExtension { } } - private Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]"); + private static ActorSystem system; - private ActorSystem system = ActorSystem.create("JavaExtension", c); + @BeforeClass + public static void beforeAll() { + Config c = ConfigFactory.parseString("akka.extensions = [ \"akka.actor.JavaExtension$Provider\" ]").withFallback( + AkkaSpec.testConf()); + system = ActorSystem.create("JavaExtension", c); + } + + @AfterClass + public static void afterAll() { + system.stop(); + system = null; + } @Test public void mustBeAccessible() { diff --git a/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java b/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java index 12dbe736d6..d534d87103 100644 --- a/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java +++ b/akka-actor-tests/src/test/java/akka/dispatch/JavaFutureTests.java @@ -2,6 +2,9 @@ package akka.dispatch; import akka.actor.Timeout; import akka.actor.ActorSystem; + +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import java.util.concurrent.Callable; @@ -14,15 +17,30 @@ import akka.japi.Function; import akka.japi.Function2; import akka.japi.Procedure; import akka.japi.Option; +import akka.testkit.AkkaSpec; public class JavaFutureTests { - private final ActorSystem system = ActorSystem.create(); - private final Timeout t = system.settings().ActorTimeout(); - private final FutureFactory ff = new FutureFactory(system.dispatcher(), t); + private static ActorSystem system; + private static FutureFactory ff; + private static Timeout t; + + @BeforeClass + public static void beforeAll() { + system = ActorSystem.create("JavaFutureTests", AkkaSpec.testConf()); + t = system.settings().ActorTimeout(); + ff = new FutureFactory(system.dispatcher(), t); + } + + @AfterClass + public static void afterAll() { + system.stop(); + system = null; + } @Test public void mustBeAbleToMapAFuture() { + Future f1 = ff.future(new Callable() { public String call() { return "Hello"; diff --git a/akka-actor-tests/src/test/java/akka/util/JavaDuration.java b/akka-actor-tests/src/test/java/akka/util/JavaDuration.java index 56e7f68bf6..5a833f5b51 100644 --- a/akka-actor-tests/src/test/java/akka/util/JavaDuration.java +++ b/akka-actor-tests/src/test/java/akka/util/JavaDuration.java @@ -7,7 +7,8 @@ import org.junit.Test; public class JavaDuration { - @Test void testCreation() { + @Test + public void testCreation() { final Duration fivesec = Duration.create(5, "seconds"); final Duration threemillis = Duration.parse("3 millis"); final Duration diff = fivesec.minus(threemillis); diff --git a/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala index 3565cde2fb..6f8c364ff8 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ActorSystemSpec.scala @@ -9,17 +9,15 @@ import com.typesafe.config.ConfigFactory class JavaExtensionSpec extends JavaExtension with JUnitSuite -object ActorSystemSpec { - object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider { - def lookup = this - def createExtension(s: ActorSystemImpl) = new TestExtension(s) - } - - class TestExtension(val system: ActorSystemImpl) extends Extension +object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider { + def lookup = this + def createExtension(s: ActorSystemImpl) = new TestExtension(s) } -class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.ActorSystemSpec$TestExtension$"]""") { - import ActorSystemSpec._ +// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains +class TestExtension(val system: ActorSystemImpl) extends Extension + +class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.TestExtension$"]""") { "An ActorSystem" must { diff --git a/akka-actor-tests/src/test/scala/akka/actor/Bench.scala b/akka-actor-tests/src/test/scala/akka/actor/Bench.scala index 8e0e3e61fd..52a18e0f3b 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/Bench.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/Bench.scala @@ -107,9 +107,10 @@ object Chameneos { def run { // System.setProperty("akka.config", "akka.conf") Chameneos.start = System.currentTimeMillis - ActorSystem().actorOf(new Mall(1000000, 4)) + val system = ActorSystem().actorOf(new Mall(1000000, 4)) Thread.sleep(10000) println("Elapsed: " + (end - start)) + system.stop() } def main(args: Array[String]): Unit = run diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala index 68a81d9797..ecec2c0e3e 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -160,9 +160,9 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { nr-of-instances = boom } } - """, ConfigParseOptions.defaults) + """, ConfigParseOptions.defaults).withFallback(AkkaSpec.testConf) - ActorSystem("invalid", invalidDeployerConf) + ActorSystem("invalid", invalidDeployerConf).stop() } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala index 7d829ec622..aa89ac5c89 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/FSMActorSpec.scala @@ -194,41 +194,46 @@ class FSMActorSpec extends AkkaSpec(Map("akka.actor.debug.fsm" -> true)) with Im "log events and transitions if asked to do so" in { import scala.collection.JavaConverters._ val config = ConfigFactory.parseMap(Map("akka.loglevel" -> "DEBUG", - "akka.actor.debug.fsm" -> true).asJava) - new TestKit(ActorSystem("fsm event", config)) { - EventFilter.debug() intercept { - val fsm = TestActorRef(new Actor with LoggingFSM[Int, Null] { - startWith(1, null) - when(1) { - case Ev("go") ⇒ - setTimer("t", Shutdown, 1.5 seconds, false) - goto(2) + "akka.actor.debug.fsm" -> true).asJava).withFallback(AkkaSpec.testConf) + val fsmEventSystem = ActorSystem("fsm event", config) + try { + new TestKit(fsmEventSystem) { + EventFilter.debug() intercept { + val fsm = TestActorRef(new Actor with LoggingFSM[Int, Null] { + startWith(1, null) + when(1) { + case Ev("go") ⇒ + setTimer("t", Shutdown, 1.5 seconds, false) + goto(2) + } + when(2) { + case Ev("stop") ⇒ + cancelTimer("t") + stop + } + onTermination { + case StopEvent(r, _, _) ⇒ testActor ! r + } + }) + val name = fsm.toString + system.eventStream.subscribe(testActor, classOf[Logging.Debug]) + fsm ! "go" + expectMsgPF(1 second, hint = "processing Event(go,null)") { + case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(go,null) from Actor[") ⇒ true } - when(2) { - case Ev("stop") ⇒ - cancelTimer("t") - stop + expectMsg(1 second, Logging.Debug(name, "setting timer 't'/1500 milliseconds: Shutdown")) + expectMsg(1 second, Logging.Debug(name, "transition 1 -> 2")) + fsm ! "stop" + expectMsgPF(1 second, hint = "processing Event(stop,null)") { + case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(stop,null) from Actor[") ⇒ true } - onTermination { - case StopEvent(r, _, _) ⇒ testActor ! r - } - }) - val name = fsm.toString - system.eventStream.subscribe(testActor, classOf[Logging.Debug]) - fsm ! "go" - expectMsgPF(1 second, hint = "processing Event(go,null)") { - case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(go,null) from Actor[") ⇒ true + expectMsgAllOf(1 second, Logging.Debug(name, "canceling timer 't'"), Normal) + expectNoMsg(1 second) + system.eventStream.unsubscribe(testActor) } - expectMsg(1 second, Logging.Debug(name, "setting timer 't'/1500 milliseconds: Shutdown")) - expectMsg(1 second, Logging.Debug(name, "transition 1 -> 2")) - fsm ! "stop" - expectMsgPF(1 second, hint = "processing Event(stop,null)") { - case Logging.Debug(`name`, s: String) if s.startsWith("processing Event(stop,null) from Actor[") ⇒ true - } - expectMsgAllOf(1 second, Logging.Debug(name, "canceling timer 't'"), Normal) - expectNoMsg(1 second) - system.eventStream.unsubscribe(testActor) } + } finally { + fsmEventSystem.stop() } } diff --git a/akka-actor-tests/src/test/scala/akka/actor/JavaAPISpec.scala b/akka-actor-tests/src/test/scala/akka/actor/JavaAPISpec.scala new file mode 100644 index 0000000000..49fcc6d638 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/JavaAPISpec.scala @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.actor + +import org.scalatest.junit.JUnitSuite + +class JavaAPISpec extends JavaAPI with JUnitSuite diff --git a/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala b/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala index f25920240b..20526984b3 100644 --- a/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/event/LoggingReceiveSpec.scala @@ -32,7 +32,7 @@ object LoggingReceiveSpec { class LoggingReceiveSpec extends WordSpec with BeforeAndAfterEach with BeforeAndAfterAll { import LoggingReceiveSpec._ - val config = ConfigFactory.parseMap(Map("akka.logLevel" -> "DEBUG").asJava) + val config = ConfigFactory.parseMap(Map("akka.logLevel" -> "DEBUG").asJava).withFallback(AkkaSpec.testConf) val appLogging = ActorSystem("logging", ConfigFactory.parseMap(Map("akka.actor.debug.receive" -> true).asJava).withFallback(config)) val appAuto = ActorSystem("autoreceive", ConfigFactory.parseMap(Map("akka.actor.debug.autoreceive" -> true).asJava).withFallback(config)) val appLifecycle = ActorSystem("lifecycle", ConfigFactory.parseMap(Map("akka.actor.debug.lifecycle" -> true).asJava).withFallback(config)) diff --git a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughput10000PerformanceSpec.scala b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughput10000PerformanceSpec.scala index e47d7987bd..1854a0a32e 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughput10000PerformanceSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughput10000PerformanceSpec.scala @@ -34,7 +34,7 @@ class TellThroughput10000PerformanceSpec extends PerformanceSpec { def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config ⇒ new Dispatcher(system.dispatcherFactory.prerequisites, name, 10000, - Duration.Zero, UnboundedMailbox(), config, Duration(60, TimeUnit.SECONDS)), ThreadPoolConfig()) + Duration.Zero, UnboundedMailbox(), config, Duration(1, TimeUnit.SECONDS)), ThreadPoolConfig()) .withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity .setCorePoolSize(maxClients * 2) .build diff --git a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputComputationPerformanceSpec.scala b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputComputationPerformanceSpec.scala index 52bb3d169b..f5b7b3ae4d 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputComputationPerformanceSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputComputationPerformanceSpec.scala @@ -14,7 +14,7 @@ class TellThroughputComputationPerformanceSpec extends PerformanceSpec { def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config ⇒ new Dispatcher(system.dispatcherFactory.prerequisites, name, 5, - Duration.Zero, UnboundedMailbox(), config, 60 seconds), ThreadPoolConfig()) + Duration.Zero, UnboundedMailbox(), config, 1 seconds), ThreadPoolConfig()) .withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity .setCorePoolSize(maxClients) .build diff --git a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputPerformanceSpec.scala b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputPerformanceSpec.scala index a49e837ac4..f2e547ab71 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputPerformanceSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputPerformanceSpec.scala @@ -14,7 +14,7 @@ class TellThroughputPerformanceSpec extends PerformanceSpec { def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config ⇒ new Dispatcher(system.dispatcherFactory.prerequisites, name, 5, - Duration.Zero, UnboundedMailbox(), config, 60 seconds), ThreadPoolConfig()) + Duration.Zero, UnboundedMailbox(), config, 1 seconds), ThreadPoolConfig()) .withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity .setCorePoolSize(maxClients) .build diff --git a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputSeparateDispatchersPerformanceSpec.scala b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputSeparateDispatchersPerformanceSpec.scala index 0de1e1be2d..eb0eaffc46 100644 --- a/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputSeparateDispatchersPerformanceSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/performance/microbench/TellThroughputSeparateDispatchersPerformanceSpec.scala @@ -18,7 +18,7 @@ class TellThroughputSeparateDispatchersPerformanceSpec extends PerformanceSpec { def createDispatcher(name: String) = ThreadPoolConfigDispatcherBuilder(config ⇒ new Dispatcher(system.dispatcherFactory.prerequisites, name, 5, - Duration.Zero, UnboundedMailbox(), config, Duration(60, TimeUnit.SECONDS)), ThreadPoolConfig()) + Duration.Zero, UnboundedMailbox(), config, Duration(1, TimeUnit.SECONDS)), ThreadPoolConfig()) .withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity .setCorePoolSize(1) .build diff --git a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala index 73afd0dc0a..499d214ff8 100644 --- a/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/serialization/SerializeSpec.scala @@ -97,15 +97,19 @@ class SerializeSpec extends AkkaSpec(SerializeSpec.serializationConf) { "serialize DeadLetterActorRef" in { val outbuf = new ByteArrayOutputStream() val out = new ObjectOutputStream(outbuf) - val a = ActorSystem() - out.writeObject(a.deadLetters) - out.flush() - out.close() + val a = ActorSystem("SerializeDeadLeterActorRef", AkkaSpec.testConf) + try { + out.writeObject(a.deadLetters) + out.flush() + out.close() - val in = new ObjectInputStream(new ByteArrayInputStream(outbuf.toByteArray)) - Serialization.currentSystem.withValue(a.asInstanceOf[ActorSystemImpl]) { - val deadLetters = in.readObject().asInstanceOf[DeadLetterActorRef] - (deadLetters eq a.deadLetters) must be(true) + val in = new ObjectInputStream(new ByteArrayInputStream(outbuf.toByteArray)) + Serialization.currentSystem.withValue(a.asInstanceOf[ActorSystemImpl]) { + val deadLetters = in.readObject().asInstanceOf[DeadLetterActorRef] + (deadLetters eq a.deadLetters) must be(true) + } + } finally { + a.stop() } } } diff --git a/akka-actor-tests/src/test/scala/akka/util/JavaDurationSpec.scala b/akka-actor-tests/src/test/scala/akka/util/JavaDurationSpec.scala new file mode 100644 index 0000000000..aafbf3d133 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/util/JavaDurationSpec.scala @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2009-2011 Typesafe Inc. + */ +package akka.util + +import org.scalatest.junit.JUnitSuite + +class JavaDurationSpec extends JavaDuration with JUnitSuite diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 7534a6de77..0da9137a5a 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -378,8 +378,11 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor // TODO shutdown all that other stuff, whatever that may be def stop() { guardian.stop() + try terminationFuture.await(10 seconds) catch { + case _: FutureTimeoutException ⇒ log.warning("Failed to stop [{}] within 10 seconds", name) + } + // Dispatchers shutdown themselves, but requires the scheduler terminationFuture onComplete (_ ⇒ stopScheduler()) - terminationFuture onComplete (_ ⇒ dispatcher.shutdown()) } protected def createScheduler(): Scheduler = { @@ -400,8 +403,11 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor } protected def stopScheduler(): Unit = scheduler match { - case x: DefaultScheduler ⇒ x.stop() - case _ ⇒ + case x: DefaultScheduler ⇒ + // Let dispatchers shutdown first. + // Dispatchers schedule shutdown and may also reschedule, therefore wait 4 times the shutdown delay. + x.scheduleOnce(() ⇒ { x.stop; dispatcher.shutdown() }, settings.DispatcherDefaultShutdown * 4) + case _ ⇒ } private val extensions = new ConcurrentIdentityHashMap[ExtensionId[_], AnyRef] diff --git a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala index fd45069e68..99557e33c8 100644 --- a/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala +++ b/akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala @@ -136,7 +136,7 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext shutdownScheduleUpdater.get(this) match { case UNSCHEDULED ⇒ if (shutdownScheduleUpdater.compareAndSet(this, UNSCHEDULED, SCHEDULED)) { - scheduler.scheduleOnce(shutdownAction, Duration(shutdownTimeout.toMillis, TimeUnit.MILLISECONDS)) + scheduler.scheduleOnce(shutdownAction, shutdownTimeout) () } else ifSensibleToDoSoThenScheduleShutdown() case SCHEDULED ⇒ @@ -211,7 +211,7 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext } case RESCHEDULED ⇒ if (shutdownScheduleUpdater.compareAndSet(MessageDispatcher.this, RESCHEDULED, SCHEDULED)) - scheduler.scheduleOnce(this, Duration(shutdownTimeout.toMillis, TimeUnit.MILLISECONDS)) + scheduler.scheduleOnce(this, shutdownTimeout) else run() } } diff --git a/akka-actor/src/main/scala/akka/event/Logging.scala b/akka-actor/src/main/scala/akka/event/Logging.scala index bae930cb17..1bff28ea44 100644 --- a/akka-actor/src/main/scala/akka/event/Logging.scala +++ b/akka-actor/src/main/scala/akka/event/Logging.scala @@ -81,7 +81,7 @@ trait LoggingBus extends ActorEventBus { loggers = Seq(StandardOutLogger) _logLevel = level } - publish(Info(simpleName(this), "StandardOutLogger started")) + publish(Debug(simpleName(this), "StandardOutLogger started")) } private[akka] def startDefaultLoggers(system: ActorSystemImpl) { @@ -114,7 +114,7 @@ trait LoggingBus extends ActorEventBus { loggers = myloggers _logLevel = level } - publish(Info(simpleName(this), "Default Loggers started")) + publish(Debug(simpleName(this), "Default Loggers started")) if (!(defaultLoggers contains StandardOutLoggerName)) { unsubscribe(StandardOutLogger) } @@ -154,7 +154,7 @@ trait LoggingBus extends ActorEventBus { if (response != LoggerInitialized) throw new LoggerInitializationException("Logger " + name + " did not respond with LoggerInitialized, sent instead " + response) AllLogLevels filter (level >= _) foreach (l ⇒ subscribe(actor, classFor(l))) - publish(Info(simpleName(this), "logger " + name + " started")) + publish(Debug(simpleName(this), "logger " + name + " started")) actor } diff --git a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/filequeue/tools/QDumper.scala b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/filequeue/tools/QDumper.scala index eb8a7f9e0b..54c5ba36b6 100644 --- a/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/filequeue/tools/QDumper.scala +++ b/akka-durable-mailboxes/akka-file-mailbox/src/main/scala/akka/actor/mailbox/filequeue/tools/QDumper.scala @@ -148,5 +148,7 @@ object QDumper { println("Queue: " + filename) new QueueDumper(filename, system.log)() } + + system.stop() } } diff --git a/akka-durable-mailboxes/akka-mailboxes-common/src/test/scala/akka/actor/mailbox/DurableMailboxSpec.scala b/akka-durable-mailboxes/akka-mailboxes-common/src/test/scala/akka/actor/mailbox/DurableMailboxSpec.scala index ab0f0206d3..9d118e3a96 100644 --- a/akka-durable-mailboxes/akka-mailboxes-common/src/test/scala/akka/actor/mailbox/DurableMailboxSpec.scala +++ b/akka-durable-mailboxes/akka-mailboxes-common/src/test/scala/akka/actor/mailbox/DurableMailboxSpec.scala @@ -44,7 +44,8 @@ abstract class DurableMailboxSpec(val backendName: String, val mailboxType: Dura sender ! PoisonPill } - "handle reply to ! for multiple messages" in { + // FIXME ignored due to zookeeper issue, ticket #1423 + "handle reply to ! for multiple messages" ignore { val latch = new CountDownLatch(5) val queueActor = createMailboxTestActor(backendName + " should handle reply to !") val sender = actorOf(Props(new Sender(latch))) diff --git a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/test/scala/akka/actor/mailbox/ZooKeeperBasedMailboxSpec.scala b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/test/scala/akka/actor/mailbox/ZooKeeperBasedMailboxSpec.scala index da363beaaf..3cdc734830 100644 --- a/akka-durable-mailboxes/akka-zookeeper-mailbox/src/test/scala/akka/actor/mailbox/ZooKeeperBasedMailboxSpec.scala +++ b/akka-durable-mailboxes/akka-zookeeper-mailbox/src/test/scala/akka/actor/mailbox/ZooKeeperBasedMailboxSpec.scala @@ -19,7 +19,7 @@ class ZooKeeperBasedMailboxSpec extends DurableMailboxSpec("ZooKeeper", ZooKeepe } override def atTermination() { - zkServer.shutdown + zkServer.shutdown() super.atTermination() } } diff --git a/akka-stm/src/test/java/akka/stm/example/RetryExample.java b/akka-stm/src/test/java/akka/stm/example/RetryExample.java index ad86126deb..f15850d232 100644 --- a/akka-stm/src/test/java/akka/stm/example/RetryExample.java +++ b/akka-stm/src/test/java/akka/stm/example/RetryExample.java @@ -1,5 +1,8 @@ package akka.stm.example; +import org.junit.AfterClass; +import org.junit.BeforeClass; + import akka.actor.ActorSystem; import akka.stm.*; import akka.actor.*; @@ -7,11 +10,8 @@ import akka.testkit.AkkaSpec; public class RetryExample { public static void main(String[] args) { - System.out.println(); - System.out.println("Retry example"); - System.out.println(); - ActorSystem application = ActorSystem.create("RetryExample", AkkaSpec.testConf()); + ActorSystem application = ActorSystem.create("RetryExample", AkkaSpec.testConf()); final Ref account1 = new Ref(100.0); final Ref account2 = new Ref(100.0); @@ -47,5 +47,7 @@ public class RetryExample { // Account 2: 600.0 transferer.stop(); + + application.stop(); } } diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java index 344c98dfee..9baf0f1485 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedCoordinatedExample.java @@ -9,11 +9,8 @@ import akka.transactor.Coordinated; public class UntypedCoordinatedExample { public static void main(String[] args) throws InterruptedException { - System.out.println(); - System.out.println("Untyped transactor example"); - System.out.println(); - ActorSystem application = ActorSystem.create("UntypedCoordinatedExample", AkkaSpec.testConf()); + ActorSystem application = ActorSystem.create("UntypedCoordinatedExample", AkkaSpec.testConf()); ActorRef counter1 = application.actorOf(new Props().withCreator(UntypedCoordinatedCounter.class)); ActorRef counter2 = application.actorOf(new Props().withCreator(UntypedCoordinatedCounter.class)); @@ -45,5 +42,7 @@ public class UntypedCoordinatedExample { counter1.stop(); counter2.stop(); + + application.stop(); } } diff --git a/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java b/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java index 882d5b7b1f..55e28f872f 100644 --- a/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java +++ b/akka-stm/src/test/java/akka/transactor/example/UntypedTransactorExample.java @@ -8,11 +8,8 @@ import akka.testkit.AkkaSpec; public class UntypedTransactorExample { public static void main(String[] args) throws InterruptedException { - System.out.println(); - System.out.println("Untyped transactor example"); - System.out.println(); - ActorSystem application = ActorSystem.create("UntypedTransactorExample", AkkaSpec.testConf()); + ActorSystem application = ActorSystem.create("UntypedTransactorExample", AkkaSpec.testConf()); ActorRef counter1 = application.actorOf(new Props().withCreator(UntypedCounter.class)); ActorRef counter2 = application.actorOf(new Props().withCreator(UntypedCounter.class)); @@ -44,5 +41,7 @@ public class UntypedTransactorExample { counter1.stop(); counter2.stop(); + + application.stop(); } } diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java index 0d44d16496..a90e0a1952 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedCoordinatedIncrementTest.java @@ -3,6 +3,8 @@ package akka.transactor.test; import static org.junit.Assert.*; import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.Before; @@ -31,7 +33,20 @@ import scala.collection.JavaConverters; import scala.collection.Seq; public class UntypedCoordinatedIncrementTest { - ActorSystem application = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf()); + ActorSystem application = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf()); + + private static ActorSystem system; + + @BeforeClass + public static void beforeAll() { + system = ActorSystem.create("UntypedTransactorTest", AkkaSpec.testConf()); + } + + @AfterClass + public static void afterAll() { + system.stop(); + system = null; + } List counters; ActorRef failer; diff --git a/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java b/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java index 8d2a3e4db8..528a2a14f8 100644 --- a/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java +++ b/akka-stm/src/test/java/akka/transactor/test/UntypedTransactorTest.java @@ -1,6 +1,9 @@ package akka.transactor.test; import static org.junit.Assert.*; + +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.Before; @@ -28,7 +31,19 @@ import scala.collection.Seq; import akka.testkit.AkkaSpec; public class UntypedTransactorTest { - ActorSystem application = ActorSystem.create("UntypedTransactorTest", AkkaSpec.testConf()); + + private static ActorSystem system; + + @BeforeClass + public static void beforeAll() { + system = ActorSystem.create("UntypedTransactorTest", AkkaSpec.testConf()); + } + + @AfterClass + public static void afterAll() { + system.stop(); + system = null; + } List counters; ActorRef failer; @@ -42,14 +57,14 @@ public class UntypedTransactorTest { counters = new ArrayList(); for (int i = 1; i <= numCounters; i++) { final String name = "counter" + i; - ActorRef counter = application.actorOf(new Props().withCreator(new UntypedActorFactory() { + ActorRef counter = system.actorOf(new Props().withCreator(new UntypedActorFactory() { public UntypedActor create() { return new UntypedCounter(name); } })); counters.add(counter); } - failer = application.actorOf(new Props().withCreator(UntypedFailer.class)); + failer = system.actorOf(new Props().withCreator(UntypedFailer.class)); } @Test @@ -80,7 +95,7 @@ public class UntypedTransactorTest { EventFilter expectedFailureFilter = (EventFilter) new ErrorFilter(ExpectedFailureException.class); EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(CoordinatedTransactionException.class); Seq ignoreExceptions = seq(expectedFailureFilter, coordinatedFilter); - application.eventStream().publish(new TestEvent.Mute(ignoreExceptions)); + system.eventStream().publish(new TestEvent.Mute(ignoreExceptions)); CountDownLatch incrementLatch = new CountDownLatch(numCounters); List actors = new ArrayList(counters); actors.add(failer); diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index edbc589099..82e38511d3 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -16,6 +16,10 @@ import akka.actor.Scheduler import akka.event.EventStream import akka.util.Duration import java.util.concurrent.TimeUnit +import akka.actor.ExtensionId +import akka.actor.ExtensionIdProvider +import akka.actor.ActorSystemImpl +import akka.actor.Extension /* * Locking rules: @@ -34,7 +38,12 @@ import java.util.concurrent.TimeUnit * within one of its methods taking a closure argument. */ -private[testkit] object CallingThreadDispatcher { +private[testkit] object CallingThreadDispatcherQueues extends ExtensionId[CallingThreadDispatcherQueues] with ExtensionIdProvider { + override def lookup = CallingThreadDispatcherQueues + override def createExtension(system: ActorSystemImpl): CallingThreadDispatcherQueues = new CallingThreadDispatcherQueues +} + +private[testkit] class CallingThreadDispatcherQueues extends Extension { // PRIVATE DATA @@ -127,7 +136,7 @@ class CallingThreadDispatcher( protected[akka] override def throughputDeadlineTime = Duration.Zero protected[akka] override def registerForExecution(mbox: Mailbox, hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = false - protected[akka] override def shutdownTimeout = Duration(100L, TimeUnit.MILLISECONDS) + protected[akka] override def shutdownTimeout = Duration(1000L, TimeUnit.MILLISECONDS) override def suspend(actor: ActorCell) { getMailbox(actor) foreach (_.suspendSwitch.switchOn) @@ -139,7 +148,7 @@ class CallingThreadDispatcher( val queue = mbox.queue val wasActive = queue.isActive val switched = mbox.suspendSwitch.switchOff { - gatherFromAllOtherQueues(mbox, queue) + CallingThreadDispatcherQueues(actor.system).gatherFromAllOtherQueues(mbox, queue) } if (switched && !wasActive) { runQueue(mbox, queue) @@ -267,7 +276,7 @@ class CallingThreadMailbox(_receiver: ActorCell) extends Mailbox(_receiver) with private val q = new ThreadLocal[NestingQueue]() { override def initialValue = { val queue = new NestingQueue - CallingThreadDispatcher.registerQueue(CallingThreadMailbox.this, queue) + CallingThreadDispatcherQueues(actor.system).registerQueue(CallingThreadMailbox.this, queue) queue } } diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index dba8437ef6..c0476a74cc 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -92,7 +92,7 @@ class TestKit(_system: ActorSystem) { * ActorRef of the test actor. Access is provided to enable e.g. * registration as message target. */ - val testActor: ActorRef = { + lazy val testActor: ActorRef = { val impl = system.asInstanceOf[ActorSystemImpl] impl.systemActorOf(Props(new TestActor(queue)) .copy(dispatcher = new CallingThreadDispatcher(system.dispatcherFactory.prerequisites)), diff --git a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala index e43ea378cb..ce8dad2b4c 100644 --- a/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala +++ b/akka-testkit/src/test/scala/akka/testkit/AkkaSpec.scala @@ -25,8 +25,7 @@ object AkkaSpec { stdout-loglevel = "WARNING" actor { default-dispatcher { - core-pool-size = 4 - max-pool-size = 32 + core-pool-size-factor = 2 } } } @@ -53,7 +52,7 @@ abstract class AkkaSpec(_system: ActorSystem = ActorSystem(getClass.getSimpleNam final override def afterAll { system.stop() try system.asInstanceOf[ActorSystemImpl].terminationFuture.await(5 seconds) catch { - case _: FutureTimeoutException ⇒ system.log.warning("failed to stop within 5 seconds") + case _: FutureTimeoutException ⇒ system.log.warning("Failed to stop [{}] within 5 seconds", system.name) } atTermination() } diff --git a/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala index de5851bfe7..a752b3c783 100644 --- a/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala +++ b/akka-tutorials/akka-tutorial-first/src/test/scala/WorkerSpec.scala @@ -5,15 +5,20 @@ package akka.tutorial.first.scala import org.junit.runner.RunWith import org.scalatest.matchers.MustMatchers +import org.scalatest.BeforeAndAfterAll import org.scalatest.WordSpec import akka.testkit.TestActorRef import akka.tutorial.first.scala.Pi.Worker import akka.actor.ActorSystem @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class WorkerSpec extends WordSpec with MustMatchers { +class WorkerSpec extends WordSpec with MustMatchers with BeforeAndAfterAll { - implicit def system = ActorSystem() + implicit val system = ActorSystem() + + override def afterAll { + system.stop() + } "Worker" must { "calculate pi correctly" in { @@ -23,4 +28,4 @@ class WorkerSpec extends WordSpec with MustMatchers { actor.calculatePiFor(1, 1) must be(-1.3333333333333333 plusOrMinus 0.0000000001) } } -} \ No newline at end of file +} From 639c5d6a54a880118771c5fb94861a0f11f2eb19 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 10:16:21 +0100 Subject: [PATCH 27/30] Revert the removal of akka.remote.transport. Will be used in ticket 1424 --- akka-remote/src/main/scala/akka/remote/RemoteExtension.scala | 1 + akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala | 1 + 2 files changed, 2 insertions(+) diff --git a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala index 3b0088d55c..00bd3eadcf 100644 --- a/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala +++ b/akka-remote/src/main/scala/akka/remote/RemoteExtension.scala @@ -21,6 +21,7 @@ class RemoteExtensionSettings(val config: Config) extends Extension { import config._ + val RemoteTransport = getString("akka.remote.transport") val FailureDetectorThreshold = getInt("akka.remote.failure-detector.threshold") val FailureDetectorMaxSampleSize = getInt("akka.remote.failure-detector.max-sample-size") val ShouldCompressData = config.getBoolean("akka.remote.use-compression") diff --git a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala index 156ad94ff7..d11d07ddd3 100644 --- a/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/RemoteConfigSpec.scala @@ -12,6 +12,7 @@ class RemoteConfigSpec extends AkkaSpec("akka.cluster.nodename = node1") { import config._ //akka.remote + getString("akka.remote.transport") must equal("akka.cluster.netty.NettyRemoteSupport") getString("akka.remote.secure-cookie") must equal("") getBoolean("akka.remote.use-passive-connections") must equal(true) // getMilliseconds("akka.remote.remote-daemon-ack-timeout") must equal(30 * 1000) From 79866e57294c3bc1aa9a5be59129ff62eb1584b5 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 10:32:17 +0100 Subject: [PATCH 28/30] Made DefaultScheduler Closeable --- akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala | 5 +++-- akka-actor/src/main/scala/akka/actor/ActorSystem.scala | 5 +++-- .../main/scala/akka/testkit/CallingThreadDispatcher.scala | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala index 644c95976b..c397d67be6 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorRefProvider.scala @@ -20,6 +20,7 @@ import akka.remote.LocalOnly import akka.event._ import akka.event.Logging.Error._ import akka.event.Logging.Warning +import java.io.Closeable /** * Interface for all ActorRef providers to implement. @@ -414,7 +415,7 @@ class LocalDeathWatch extends DeathWatch with ActorClassification { * Note that dispatcher is by-name parameter, because dispatcher might not be initialized * when the scheduler is created. */ -class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler { +class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable { def schedule(receiver: ActorRef, message: Any, initialDelay: Duration, delay: Duration): Cancellable = new DefaultCancellable(hashedWheelTimer.newTimeout(createContinuousTask(receiver, message, delay), initialDelay)) @@ -475,7 +476,7 @@ class DefaultScheduler(hashedWheelTimer: HashedWheelTimer, log: LoggingAdapter, } } - private[akka] def stop() = hashedWheelTimer.stop() + def close() = hashedWheelTimer.stop() } class DefaultCancellable(val timeout: org.jboss.netty.akka.util.Timeout) extends Cancellable { diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index 0da9137a5a..ac6b23dab4 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -24,6 +24,7 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors import scala.annotation.tailrec import org.jboss.netty.akka.util.internal.ConcurrentIdentityHashMap +import java.io.Closeable object ActorSystem { @@ -403,10 +404,10 @@ class ActorSystemImpl(val name: String, applicationConfig: Config) extends Actor } protected def stopScheduler(): Unit = scheduler match { - case x: DefaultScheduler ⇒ + case x: Closeable ⇒ // Let dispatchers shutdown first. // Dispatchers schedule shutdown and may also reschedule, therefore wait 4 times the shutdown delay. - x.scheduleOnce(() ⇒ { x.stop; dispatcher.shutdown() }, settings.DispatcherDefaultShutdown * 4) + x.scheduleOnce(() ⇒ { x.close(); dispatcher.shutdown() }, settings.DispatcherDefaultShutdown * 4) case _ ⇒ } diff --git a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala index 82e38511d3..2a3933c93b 100644 --- a/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala +++ b/akka-testkit/src/main/scala/akka/testkit/CallingThreadDispatcher.scala @@ -15,6 +15,7 @@ import akka.dispatch._ import akka.actor.Scheduler import akka.event.EventStream import akka.util.Duration +import akka.util.duration._ import java.util.concurrent.TimeUnit import akka.actor.ExtensionId import akka.actor.ExtensionIdProvider @@ -136,7 +137,7 @@ class CallingThreadDispatcher( protected[akka] override def throughputDeadlineTime = Duration.Zero protected[akka] override def registerForExecution(mbox: Mailbox, hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = false - protected[akka] override def shutdownTimeout = Duration(1000L, TimeUnit.MILLISECONDS) + protected[akka] override def shutdownTimeout = 1 second override def suspend(actor: ActorCell) { getMailbox(actor) foreach (_.suspendSwitch.switchOn) From eebe068aa5979b459ef8a8c356c39b4bd7f6b509 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 11:52:54 +0100 Subject: [PATCH 29/30] Nice looking toString of config settings. See #1373 --- akka-actor/src/main/scala/akka/actor/ActorSystem.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala index ac6b23dab4..01f2d2bafb 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorSystem.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorSystem.scala @@ -113,9 +113,7 @@ object ActorSystem { throw new ConfigurationException("Akka JAR version [" + Version + "] does not match the provided config version [" + ConfigVersion + "]") - override def toString: String = { - config.toString - } + override def toString: String = config.root.render } From db075d094a27475c1c8fca67fffb115dd890b0b2 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Fri, 2 Dec 2011 12:08:28 +0100 Subject: [PATCH 30/30] Updated to latest config lib 38fb8d6 --- .../main/java/com/typesafe/config/Config.java | 9 +- .../com/typesafe/config/ConfigException.java | 40 +++++++- .../com/typesafe/config/ConfigFactory.java | 92 ++++++++++++++----- .../com/typesafe/config/ConfigMergeable.java | 9 -- .../typesafe/config/ConfigResolveOptions.java | 48 ++-------- .../config/impl/AbstractConfigObject.java | 24 ++++- .../config/impl/AbstractConfigValue.java | 6 +- .../com/typesafe/config/impl/ConfigImpl.java | 24 ++++- .../config/impl/ConfigSubstitution.java | 25 ++--- .../com/typesafe/config/impl/ConfigUtil.java | 28 ++++++ .../typesafe/config/impl/MergeableValue.java | 9 ++ .../com/typesafe/config/impl/Parseable.java | 80 ++++++++++++---- .../java/com/typesafe/config/impl/Path.java | 20 +++- .../typesafe/config/impl/SimpleConfig.java | 8 +- 14 files changed, 299 insertions(+), 123 deletions(-) create mode 100644 akka-actor/src/main/java/com/typesafe/config/impl/MergeableValue.java diff --git a/akka-actor/src/main/java/com/typesafe/config/Config.java b/akka-actor/src/main/java/com/typesafe/config/Config.java index 0d83504fe3..1c7fca50e5 100644 --- a/akka-actor/src/main/java/com/typesafe/config/Config.java +++ b/akka-actor/src/main/java/com/typesafe/config/Config.java @@ -87,9 +87,6 @@ public interface Config extends ConfigMergeable { @Override Config withFallback(ConfigMergeable other); - @Override - ConfigObject toValue(); - /** * Returns a replacement config with all substitutions (the * ${foo.bar} syntax, see * If no paths are specified in checkValid()'s parameter list, * validation is for the entire config. - * + * *

* If you specify paths that are not in the reference config, those paths * are ignored. (There's nothing to validate.) @@ -465,7 +462,7 @@ public interface Config extends ConfigMergeable { List getAnyRefList(String path); - List getMemorySizeInBytesList(String path); + List getBytesList(String path); List getMillisecondsList(String path); diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java index 3973116064..8c23d09533 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigException.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigException.java @@ -7,7 +7,7 @@ package com.typesafe.config; /** * All exceptions thrown by the library are subclasses of ConfigException. */ -public class ConfigException extends RuntimeException { +public abstract class ConfigException extends RuntimeException { private static final long serialVersionUID = 1L; final private ConfigOrigin origin; @@ -152,6 +152,11 @@ public class ConfigException extends RuntimeException { } } + /** + * Exception indicating that a path expression was invalid. Try putting + * double quotes around path elements that contain "special" characters. + * + */ public static class BadPath extends ConfigException { private static final long serialVersionUID = 1L; @@ -267,6 +272,11 @@ public class ConfigException extends RuntimeException { } } + /** + * Information about a problem that occurred in {@link Config#checkValid}. A + * {@link ConfigException.ValidationFailed} exception thrown from + * checkValid() includes a list of problems encountered. + */ public static class ValidationProblem { final private String path; @@ -279,19 +289,31 @@ public class ConfigException extends RuntimeException { this.problem = problem; } + /** Returns the config setting causing the problem. */ public String path() { return path; } + /** + * Returns where the problem occurred (origin may include info on the + * file, line number, etc.). + */ public ConfigOrigin origin() { return origin; } + /** Returns a description of the problem. */ public String problem() { return problem; } } + /** + * Exception indicating that {@link Config#checkValid} found validity + * problems. The problems are available via the {@link #problems()} method. + * The getMessage() of this exception is a potentially very + * long string listing all the problems found. + */ public static class ValidationFailed extends ConfigException { private static final long serialVersionUID = 1L; @@ -321,4 +343,20 @@ public class ConfigException extends RuntimeException { return sb.toString(); } } + + /** + * Exception that doesn't fall into any other category. + */ + public static class Generic extends ConfigException { + private static final long serialVersionUID = 1L; + + public Generic(String message, Throwable cause) { + super(message, cause); + } + + public Generic(String message) { + this(message, null); + } + } + } diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java index 36ad5c54b4..9251b3fb45 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigFactory.java @@ -5,11 +5,13 @@ package com.typesafe.config; import java.io.File; import java.io.Reader; +import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.Properties; import com.typesafe.config.impl.ConfigImpl; +import com.typesafe.config.impl.ConfigUtil; import com.typesafe.config.impl.Parseable; /** @@ -57,16 +59,6 @@ public final class ConfigFactory { /** * Like {@link #load(String)} but allows you to specify parse and resolve * options. - * - *

- * To be aware of: using - * {@link ConfigResolveOptions#setUseSystemProperties(boolean) - * setUseSystemProperties(false)} with this method has no meaningful effect, - * because the system properties are merged into the config as overrides - * anyway. setUseSystemProperties affects whether to fall back - * to system properties when they are not found in the config, but with - * load(), they will be in the config. - * * @param resourceBasename * the classpath resource name with optional extension * @param parseOptions @@ -100,15 +92,6 @@ public final class ConfigFactory { * Like {@link #load(Config)} but allows you to specify * {@link ConfigResolveOptions}. * - *

- * To be aware of: using - * {@link ConfigResolveOptions#setUseSystemProperties(boolean) - * setUseSystemProperties(false)} with this method has no meaningful effect, - * because the system properties are merged into the config as overrides - * anyway. setUseSystemProperties affects whether to fall back - * to system properties when they are not found in the config, but with - * load(), they will be in the config. - * * @param config * the application's portion of the configuration * @param resolveOptions @@ -121,20 +104,83 @@ public final class ConfigFactory { } private static class DefaultConfigHolder { - static final Config defaultConfig = load("application"); + + private static Config loadDefaultConfig() { + int specified = 0; + + // override application.conf with config.file, config.resource, + // config.url if requested. + String resource = System.getProperty("config.resource"); + if (resource != null) + specified += 1; + String file = System.getProperty("config.file"); + if (file != null) + specified += 1; + String url = System.getProperty("config.url"); + if (url != null) + specified += 1; + + if (specified == 0) { + return load("application"); + } else if (specified > 1) { + throw new ConfigException.Generic("You set more than one of config.file='" + file + + "', config.url='" + url + "', config.resource='" + resource + + "'; don't know which one to use!"); + } else { + if (resource != null) { + // this deliberately does not parseResourcesAnySyntax; if + // people want that they can use an include statement. + return load(parseResources(ConfigFactory.class, resource)); + } else if (file != null) { + return load(parseFile(new File(file))); + } else { + try { + return load(parseURL(new URL(url))); + } catch (MalformedURLException e) { + throw new ConfigException.Generic( + "Bad URL in config.url system property: '" + url + "': " + + e.getMessage(), e); + } + } + } + } + + static final Config defaultConfig = loadDefaultConfig(); } /** * Loads a default configuration, equivalent to {@link #load(String) - * load("application")}. This configuration should be used by libraries and - * frameworks unless an application provides a different one. + * load("application")} in most cases. This configuration should be used by + * libraries and frameworks unless an application provides a different one. *

* This method may return a cached singleton. + *

+ * If the system properties config.resource, + * config.file, or config.url are set, then the + * classpath resource, file, or URL specified in those properties will be + * used rather than the default + * application.{conf,json,properties} classpath resources. + * These system properties should not be set in code (after all, you can + * just parse whatever you want manually and then use {@link #load(Config) + * if you don't want to use application.conf}). The properties + * are intended for use by the person or script launching the application. + * For example someone might have a production.conf that + * include application.conf but then change a couple of values. + * When launching the app they could specify + * -Dconfig.resource=production.conf to get production mode. + *

+ * If no system properties are set to change the location of the default + * configuration, ConfigFactory.load() is equivalent to + * ConfigFactory.load("application"). * * @return configuration for an application */ public static Config load() { - return DefaultConfigHolder.defaultConfig; + try { + return DefaultConfigHolder.defaultConfig; + } catch (ExceptionInInitializerError e) { + throw ConfigUtil.extractInitializerError(e); + } } /** diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java index 356f89021b..c4280e93ea 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigMergeable.java @@ -17,15 +17,6 @@ package com.typesafe.config; * implementations will break. */ public interface ConfigMergeable { - /** - * Converts this instance to a {@link ConfigValue}. If called on a - * {@code ConfigValue} it returns {@code this}, if called on a - * {@link Config} it's equivalent to {@link Config#root()}. - * - * @return this instance as a {@code ConfigValue} - */ - ConfigValue toValue(); - /** * Returns a new value computed by merging this value with another, with * keys in this value "winning" over the other one. Only diff --git a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java index 8ed11c4bba..37c7b36d5b 100644 --- a/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java +++ b/akka-actor/src/main/java/com/typesafe/config/ConfigResolveOptions.java @@ -7,31 +7,24 @@ package com.typesafe.config; * A set of options related to resolving substitutions. Substitutions use the * ${foo.bar} syntax and are documented in the HOCON spec. - * *

* This object is immutable, so the "setters" return a new object. - * *

* Here is an example of creating a custom {@code ConfigResolveOptions}: - * *

  *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
- *         .setUseSystemProperties(false)
  *         .setUseSystemEnvironment(false)
  * 
- * *

* In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt * {@link ConfigResolveOptions#noSystem} which avoids looking at any system - * properties or environment variables. + * environment variables or other external system information. (Right now, + * environment variables are the only example.) */ public final class ConfigResolveOptions { - private final boolean useSystemProperties; private final boolean useSystemEnvironment; - private ConfigResolveOptions(boolean useSystemProperties, - boolean useSystemEnvironment) { - this.useSystemProperties = useSystemProperties; + private ConfigResolveOptions(boolean useSystemEnvironment) { this.useSystemEnvironment = useSystemEnvironment; } @@ -41,31 +34,17 @@ public final class ConfigResolveOptions { * @return the default resolve options */ public static ConfigResolveOptions defaults() { - return new ConfigResolveOptions(true, true); + return new ConfigResolveOptions(true); } /** * Returns resolve options that disable any reference to "system" data - * (system properties or environment variables). + * (currently, this means environment variables). * - * @return the resolve options with system properties and env variables - * disabled + * @return the resolve options with env variables disabled */ public static ConfigResolveOptions noSystem() { - return defaults().setUseSystemEnvironment(false).setUseSystemProperties(false); - } - - /** - * Returns options with use of Java system properties set to the given - * value. - * - * @param value - * true to resolve substitutions falling back to Java system - * properties. - * @return options with requested setting for use of system properties - */ - public ConfigResolveOptions setUseSystemProperties(boolean value) { - return new ConfigResolveOptions(value, useSystemEnvironment); + return defaults().setUseSystemEnvironment(false); } /** @@ -76,18 +55,9 @@ public final class ConfigResolveOptions { * variables. * @return options with requested setting for use of environment variables */ + @SuppressWarnings("static-method") public ConfigResolveOptions setUseSystemEnvironment(boolean value) { - return new ConfigResolveOptions(useSystemProperties, value); - } - - /** - * Returns whether the options enable use of system properties. This method - * is mostly used by the config lib internally, not by applications. - * - * @return true if system properties should be used - */ - public boolean getUseSystemProperties() { - return useSystemProperties; + return new ConfigResolveOptions(value); } /** diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 3f7cc0edf1..428a7b2b3f 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -35,6 +35,11 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements return config; } + @Override + public AbstractConfigObject toFallbackValue() { + return this; + } + /** * This looks up the key with no transformation or type conversion of any * kind, and returns null if the key is not present. @@ -135,6 +140,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements if (ignoresFallbacks()) throw new ConfigException.BugOrBroken("should not be reached"); + boolean changed = false; boolean allResolved = true; Map merged = new HashMap(); Set allKeys = new HashSet(); @@ -150,12 +156,26 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements kept = first; else kept = first.withFallback(second); + merged.put(key, kept); + + if (first != kept) + changed = true; + if (kept.resolveStatus() == ResolveStatus.UNRESOLVED) allResolved = false; } - return new SimpleConfigObject(mergeOrigins(this, fallback), merged, - ResolveStatus.fromBoolean(allResolved), fallback.ignoresFallbacks()); + + ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved); + boolean newIgnoresFallbacks = fallback.ignoresFallbacks(); + + if (changed) + return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, + newIgnoresFallbacks); + else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) + return newCopy(newResolveStatus, newIgnoresFallbacks); + else + return this; } @Override diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index f94cf22123..1bec6ec536 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -16,7 +16,7 @@ import com.typesafe.config.ConfigValue; * improperly-factored and non-modular code. Please don't add parent(). * */ -abstract class AbstractConfigValue implements ConfigValue { +abstract class AbstractConfigValue implements ConfigValue, MergeableValue { final private ConfigOrigin origin; @@ -72,7 +72,7 @@ abstract class AbstractConfigValue implements ConfigValue { } @Override - public AbstractConfigValue toValue() { + public AbstractConfigValue toFallbackValue() { return this; } @@ -110,7 +110,7 @@ abstract class AbstractConfigValue implements ConfigValue { if (ignoresFallbacks()) { return this; } else { - ConfigValue other = mergeable.toValue(); + ConfigValue other = ((MergeableValue) mergeable).toFallbackValue(); if (other instanceof Unmergeable) { return mergedWithTheUnmergeable((Unmergeable) other); diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java index ddb810494c..8c016d6f98 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -305,7 +305,11 @@ public class ConfigImpl { } static ConfigIncluder defaultIncluder() { - return DefaultIncluderHolder.defaultIncluder; + try { + return DefaultIncluderHolder.defaultIncluder; + } catch (ExceptionInInitializerError e) { + throw ConfigUtil.extractInitializerError(e); + } } private static AbstractConfigObject loadSystemProperties() { @@ -319,7 +323,11 @@ public class ConfigImpl { } static AbstractConfigObject systemPropertiesAsConfigObject() { - return SystemPropertiesHolder.systemProperties; + try { + return SystemPropertiesHolder.systemProperties; + } catch (ExceptionInInitializerError e) { + throw ConfigUtil.extractInitializerError(e); + } } /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ @@ -351,7 +359,11 @@ public class ConfigImpl { } static AbstractConfigObject envVariablesAsConfigObject() { - return EnvVariablesHolder.envVariables; + try { + return EnvVariablesHolder.envVariables; + } catch (ExceptionInInitializerError e) { + throw ConfigUtil.extractInitializerError(e); + } } /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ @@ -369,6 +381,10 @@ public class ConfigImpl { /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ public static Config defaultReference() { - return ReferenceHolder.referenceConfig; + try { + return ReferenceHolder.referenceConfig; + } catch (ExceptionInInitializerError e) { + throw ConfigUtil.extractInitializerError(e); + } } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java index 1b27408654..8f1b43571c 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigSubstitution.java @@ -131,22 +131,25 @@ final class ConfigSubstitution extends AbstractConfigValue implements private ConfigValue resolve(SubstitutionResolver resolver, SubstitutionExpression subst, int depth, ConfigResolveOptions options) { + // First we look up the full path, which means relative to the + // included file if we were not a root file ConfigValue result = findInObject(resolver.root(), resolver, subst.path(), depth, options); - // when looking up system props and env variables, - // we don't want the prefix that was added when - // we were included in another file. - Path unprefixed = subst.path().subPath(prefixLength); + if (result == null) { + // Then we want to check relative to the root file. We don't + // want the prefix we were included at to be used when looking up + // env variables either. + Path unprefixed = subst.path().subPath(prefixLength); - if (result == null && options.getUseSystemProperties()) { - result = findInObject(ConfigImpl.systemPropertiesAsConfigObject(), null, - unprefixed, depth, options); - } + if (result == null && prefixLength > 0) { + result = findInObject(resolver.root(), resolver, unprefixed, depth, options); + } - if (result == null && options.getUseSystemEnvironment()) { - result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, - unprefixed, depth, options); + if (result == null && options.getUseSystemEnvironment()) { + result = findInObject(ConfigImpl.envVariablesAsConfigObject(), null, unprefixed, + depth, options); + } } return result; diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigUtil.java b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigUtil.java index bfd8f05521..6f7b2c5aaa 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/ConfigUtil.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/ConfigUtil.java @@ -3,6 +3,12 @@ */ package com.typesafe.config.impl; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +import com.typesafe.config.ConfigException; + /** This is public just for the "config" package to use, don't touch it */ final public class ConfigUtil { @@ -118,4 +124,26 @@ final public class ConfigUtil { } return s.substring(start, end); } + + /** This is public just for the "config" package to use, don't touch it! */ + public static ConfigException extractInitializerError(ExceptionInInitializerError e) { + Throwable cause = e.getCause(); + if (cause != null && cause instanceof ConfigException) { + return (ConfigException) cause; + } else { + throw e; + } + } + + static File urlToFile(URL url) { + // this isn't really right, clearly, but not sure what to do. + try { + // this will properly handle hex escapes, etc. + return new File(url.toURI()); + } catch (URISyntaxException e) { + // this handles some stuff like file:///c:/Whatever/ + // apparently but mangles handling of hex escapes + return new File(url.getPath()); + } + } } diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/MergeableValue.java b/akka-actor/src/main/java/com/typesafe/config/impl/MergeableValue.java new file mode 100644 index 0000000000..38dba70cc2 --- /dev/null +++ b/akka-actor/src/main/java/com/typesafe/config/impl/MergeableValue.java @@ -0,0 +1,9 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.ConfigMergeable; +import com.typesafe.config.ConfigValue; + +interface MergeableValue extends ConfigMergeable { + // converts a Config to its root object and a ConfigValue to itself + ConfigValue toFallbackValue(); +} diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java b/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java index 71808188d3..e5b67540de 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Parseable.java @@ -247,6 +247,20 @@ public abstract class Parseable implements ConfigParseable { } } + static File relativeTo(File file, String filename) { + File child = new File(filename); + + if (child.isAbsolute()) + return null; + + File parent = file.getParentFile(); + + if (parent == null) + return null; + else + return new File(parent, filename); + } + private final static class ParseableReader extends Parseable { final private Reader reader; @@ -338,7 +352,13 @@ public abstract class Parseable implements ConfigParseable { } public static Parseable newURL(URL input, ConfigParseOptions options) { - return new ParseableURL(input, options); + // we want file: URLs and files to always behave the same, so switch + // to a file if it's a file: URL + if (input.getProtocol().equals("file")) { + return newFile(ConfigUtil.urlToFile(input), options); + } else { + return new ParseableURL(input, options); + } } private final static class ParseableFile extends Parseable { @@ -362,13 +382,27 @@ public abstract class Parseable implements ConfigParseable { @Override ConfigParseable relativeTo(String filename) { - try { - URL url = relativeTo(input.toURI().toURL(), filename); - if (url == null) - return null; - return newURL(url, options().setOriginDescription(null)); - } catch (MalformedURLException e) { + File sibling; + if ((new File(filename)).isAbsolute()) { + sibling = new File(filename); + } else { + // this may return null + sibling = relativeTo(input, filename); + } + if (sibling == null) return null; + if (sibling.exists()) { + return newFile(sibling, options().setOriginDescription(null)); + } else { + // fall back to classpath; we treat the "filename" as absolute + // (don't add a package name in front), + // if it starts with "/" then remove the "/", for consistency + // with ParseableResources.relativeTo + String resource = filename; + if (filename.startsWith("/")) + resource = filename.substring(1); + return newResources(this.getClass().getClassLoader(), resource, options() + .setOriginDescription(null)); } } @@ -450,6 +484,10 @@ public abstract class Parseable implements ConfigParseable { } static String parent(String resource) { + // the "resource" is not supposed to begin with a "/" + // because it's supposed to be the raw resource + // (ClassLoader#getResource), not the + // resource "syntax" (Class#getResource) int i = resource.lastIndexOf('/'); if (i < 0) { return null; @@ -460,18 +498,24 @@ public abstract class Parseable implements ConfigParseable { @Override ConfigParseable relativeTo(String sibling) { - // here we want to build a new resource name and let - // the class loader have it, rather than getting the - // url with getResource() and relativizing to that url. - // This is needed in case the class loader is going to - // search a classpath. - String parent = parent(resource); - if (parent == null) - return newResources(loader, sibling, options() - .setOriginDescription(null)); - else - return newResources(loader, parent + "/" + sibling, + if (sibling.startsWith("/")) { + // if it starts with "/" then don't make it relative to + // the including resource + return newResources(loader, sibling.substring(1), options().setOriginDescription(null)); + } else { + // here we want to build a new resource name and let + // the class loader have it, rather than getting the + // url with getResource() and relativizing to that url. + // This is needed in case the class loader is going to + // search a classpath. + String parent = parent(resource); + if (parent == null) + return newResources(loader, sibling, options().setOriginDescription(null)); + else + return newResources(loader, parent + "/" + sibling, options() + .setOriginDescription(null)); + } } @Override diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/Path.java b/akka-actor/src/main/java/com/typesafe/config/impl/Path.java index d57cc0091e..f19552c890 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/Path.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/Path.java @@ -139,11 +139,25 @@ final class Path { } // this doesn't have a very precise meaning, just to reduce - // noise from quotes in the rendered path + // noise from quotes in the rendered path for average cases static boolean hasFunkyChars(String s) { - for (int i = 0; i < s.length(); ++i) { + int length = s.length(); + + if (length == 0) + return false; + + // if the path starts with something that could be a number, + // we need to quote it because the number could be invalid, + // for example it could be a hyphen with no digit afterward + // or the exponent "e" notation could be mangled. + char first = s.charAt(0); + if (!(Character.isLetter(first))) + return true; + + for (int i = 1; i < length; ++i) { char c = s.charAt(i); - if (Character.isLetterOrDigit(c) || c == ' ') + + if (Character.isLetterOrDigit(c) || c == '-' || c == '_') continue; else return true; diff --git a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java index aba696ae65..127a98a05b 100644 --- a/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java +++ b/akka-actor/src/main/java/com/typesafe/config/impl/SimpleConfig.java @@ -27,9 +27,9 @@ import com.typesafe.config.ConfigValueType; * key-value pairs would be all the tree's leaf values, in a big flat list with * their full paths. */ -class SimpleConfig implements Config { +final class SimpleConfig implements Config, MergeableValue { - AbstractConfigObject object; + final private AbstractConfigObject object; SimpleConfig(AbstractConfigObject object) { this.object = object; @@ -327,7 +327,7 @@ class SimpleConfig implements Config { } @Override - public List getMemorySizeInBytesList(String path) { + public List getBytesList(String path) { List l = new ArrayList(); List list = getList(path); for (ConfigValue v : list) { @@ -378,7 +378,7 @@ class SimpleConfig implements Config { } @Override - public AbstractConfigObject toValue() { + public AbstractConfigObject toFallbackValue() { return object; }