2011-04-27 00:40:20 +02:00
/* *
2014-02-02 19:05:45 -06:00
* Copyright ( C ) 2009 - 2014 Typesafe Inc . < http : //www.typesafe.com>
2011-04-27 00:40:20 +02:00
*/
package akka.actor
2014-11-20 14:07:18 +01:00
import java.util.concurrent.atomic.AtomicReference
2011-12-12 23:31:15 +01:00
import akka.routing._
2012-04-27 12:17:00 +02:00
import akka.util.WildcardTree
2014-11-20 14:07:18 +01:00
import com.typesafe.config._
2012-11-07 16:35:14 +01:00
import scala.annotation.tailrec
2011-12-12 23:31:15 +01:00
2013-04-05 16:12:45 +02:00
object Deploy {
final val NoDispatcherGiven = ""
2013-04-18 13:35:36 +02:00
final val NoMailboxGiven = ""
2013-05-29 16:13:10 +02:00
val local = Deploy ( scope = LocalScope )
2013-04-05 16:12:45 +02:00
}
2012-01-31 21:19:28 +01:00
/* *
* This class represents deployment configuration for a given actor path . It is
* marked final in order to guarantee stable merge semantics ( i . e . what
* overrides what in case multiple configuration sources are available ) and is
* fully extensible via its Scope argument , and by the fact that an arbitrary
* Config section can be passed along with it ( which will be merged when merging
* two Deploys ) .
*
* The path field is used only when inserting the Deploy into a deployer and
* not needed when just doing deploy - as - you - go :
*
* { { {
2013-04-14 22:56:41 +02:00
* val remoteProps = someProps . withDeploy ( Deploy ( scope = RemoteScope ( "someOtherNodeName" ) ) )
2012-01-31 21:19:28 +01:00
* } } }
*/
2013-04-18 13:35:36 +02:00
@SerialVersionUID ( 2L )
2012-01-31 21:19:28 +01:00
final case class Deploy (
path : String = "" ,
config : Config = ConfigFactory . empty ,
routerConfig : RouterConfig = NoRouter ,
2013-04-05 16:12:45 +02:00
scope : Scope = NoScopeGiven ,
2013-04-18 13:35:36 +02:00
dispatcher : String = Deploy . NoDispatcherGiven ,
mailbox : String = Deploy . NoMailboxGiven ) {
2012-01-31 21:19:28 +01:00
2012-05-16 15:22:21 +02:00
/* *
* Java API to create a Deploy with the given RouterConfig
*/
2012-01-31 21:19:28 +01:00
def this ( routing : RouterConfig ) = this ( "" , ConfigFactory . empty , routing )
2012-05-16 15:22:21 +02:00
/* *
* Java API to create a Deploy with the given RouterConfig with Scope
*/
2012-01-31 21:19:28 +01:00
def this ( routing : RouterConfig , scope : Scope ) = this ( "" , ConfigFactory . empty , routing , scope )
2012-05-16 15:22:21 +02:00
/* *
* Java API to create a Deploy with the given Scope
*/
2012-02-02 09:40:17 +01:00
def this ( scope : Scope ) = this ( "" , ConfigFactory . empty , NoRouter , scope )
2012-01-31 21:19:28 +01:00
/* *
* Do a merge between this and the other Deploy , where values from “ this ” take
* precedence . The “ path ” of the other Deploy is not taken into account . All
* other members are merged using ` `<X>.withFallback(other.<X>)` ` .
*/
2013-04-05 16:12:45 +02:00
def withFallback ( other : Deploy ) : Deploy = {
2013-04-14 22:56:41 +02:00
Deploy (
path ,
config . withFallback ( other . config ) ,
routerConfig . withFallback ( other . routerConfig ) ,
scope . withFallback ( other . scope ) ,
2013-04-18 13:35:36 +02:00
if ( dispatcher == Deploy . NoDispatcherGiven ) other . dispatcher else dispatcher ,
if ( mailbox == Deploy . NoMailboxGiven ) other . mailbox else mailbox )
2013-04-05 16:12:45 +02:00
}
2012-01-31 21:19:28 +01:00
}
2012-02-03 09:43:23 +01:00
/* *
* The scope of a [ [ akka . actor . Deploy ] ] serves two purposes : as a marker for
* pattern matching the “ scope ” ( i . e . local / remote / cluster ) as well as for
* extending the information carried by the final Deploy class . Scopes can be
* used in conjunction with a custom [ [ akka . actor . ActorRefProvider ] ] , making
* Akka actors fully extensible .
*/
2012-01-31 21:19:28 +01:00
trait Scope {
2012-02-03 09:43:23 +01:00
/* *
* When merging [ [ akka . actor . Deploy ] ] instances using ` `withFallback()` ` on
* the left one , this is propagated to “ merging ” scopes in the same way .
* The setup is biased towards preferring the callee over the argument , i . e .
* ` `a.withFallback(b)` ` is called expecting that ` `a` ` should in general take
* precedence .
*/
2012-01-31 21:19:28 +01:00
def withFallback ( other : Scope ) : Scope
}
2012-07-30 13:26:12 +02:00
@SerialVersionUID ( 1L )
2012-04-10 12:07:14 +02:00
abstract class LocalScope extends Scope
2011-12-12 23:31:15 +01:00
2012-10-16 12:06:03 +02:00
/* *
* The Local Scope is the default one , which is assumed on all deployments
* which do not set a different scope . It is also the only scope handled by
* the LocalActorRefProvider .
*/
2013-05-29 16:13:10 +02:00
@SerialVersionUID ( 1L )
2012-05-16 15:22:21 +02:00
case object LocalScope extends LocalScope {
2012-04-10 16:49:29 +02:00
/* *
* Java API : get the singleton instance
*/
def getInstance = this
2012-04-10 12:07:14 +02:00
2012-01-31 21:19:28 +01:00
def withFallback ( other : Scope ) : Scope = this
}
2011-12-12 23:31:15 +01:00
2012-01-31 21:19:28 +01:00
/* *
* This is the default value and as such allows overrides .
*/
2012-07-30 13:26:12 +02:00
@SerialVersionUID ( 1L )
2012-04-10 12:07:14 +02:00
abstract class NoScopeGiven extends Scope
2013-05-29 16:13:10 +02:00
@SerialVersionUID ( 1L )
2012-04-10 12:07:14 +02:00
case object NoScopeGiven extends NoScopeGiven {
2012-01-31 21:19:28 +01:00
def withFallback ( other : Scope ) : Scope = other
2012-04-10 12:07:14 +02:00
2012-04-10 16:49:29 +02:00
/* *
* Java API : get the singleton instance
*/
def getInstance = this
2012-01-31 21:19:28 +01:00
}
2011-04-27 00:40:20 +02:00
/* *
2011-11-08 16:49:50 +01:00
* Deployer maps actor paths to actor deployments .
2011-04-27 00:40:20 +02:00
*/
2012-04-26 17:24:43 +02:00
private [ akka ] class Deployer ( val settings : ActorSystem . Settings , val dynamicAccess : DynamicAccess ) {
2011-05-03 21:04:45 +02:00
2011-12-09 20:19:59 +01:00
import scala.collection.JavaConverters._
2011-05-03 21:04:45 +02:00
2013-09-19 08:00:05 +02:00
private val resizerEnabled : Config = ConfigFactory . parseString ( "resizer.enabled=on" )
2012-04-27 12:17:00 +02:00
private val deployments = new AtomicReference ( WildcardTree [ Deploy ] ( ) )
2011-12-09 20:19:59 +01:00
private val config = settings . config . getConfig ( "akka.actor.deployment" )
protected val default = config . getConfig ( "default" )
2013-09-19 08:00:05 +02:00
val routerTypeMapping : Map [ String , String ] =
settings . config . getConfig ( "akka.actor.router.type-mapping" ) . root . unwrapped . asScala . collect {
case ( key , value : String ) ⇒ ( key -> value )
} . toMap
2012-04-26 17:24:43 +02:00
2011-12-09 20:19:59 +01:00
config . root . asScala flatMap {
case ( "default" , _ ) ⇒ None
case ( key , value : ConfigObject ) ⇒ parseConfig ( key , value . toConfig )
case _ ⇒ None
} foreach deploy
2011-05-03 21:04:45 +02:00
2012-04-27 12:48:22 +02:00
def lookup ( path : ActorPath ) : Option [ Deploy ] = lookup ( path . elements . drop ( 1 ) . iterator )
2012-04-26 17:24:43 +02:00
2012-04-27 12:48:22 +02:00
def lookup ( path : Iterable [ String ] ) : Option [ Deploy ] = lookup ( path . iterator )
2012-04-26 17:24:43 +02:00
2012-04-27 12:48:22 +02:00
def lookup ( path : Iterator [ String ] ) : Option [ Deploy ] = deployments . get ( ) . find ( path ) . data
2012-04-26 23:59:18 +02:00
2012-04-26 17:24:43 +02:00
def deploy ( d : Deploy ) : Unit = {
2013-05-23 18:48:02 +02:00
@tailrec def add ( path : Array [ String ] , d : Deploy , w : WildcardTree [ Deploy ] = deployments . get ) : Unit = {
for ( i ← 0 until path . length ) path ( i ) match {
case "" ⇒
throw new InvalidActorNameException ( s" actor name in deployment [ ${ d . path } ] must not be empty " )
2014-11-20 14:07:18 +01:00
case el if ActorPath . isValidPathElement ( el ) ⇒ // ok
2013-05-23 18:48:02 +02:00
case name ⇒
throw new InvalidActorNameException (
2014-11-20 14:07:18 +01:00
s" Illegal actor name [ $name ] in deployment [ ${ d . path } ]. Actor paths MUST: not start with ` $$ `, include only ASCII letters and can only contain these special characters: ${ ActorPath . ValidSymbols } . " )
2013-05-23 18:48:02 +02:00
}
2012-04-26 17:24:43 +02:00
if ( ! deployments . compareAndSet ( w , w . insert ( path . iterator , d ) ) ) add ( path , d )
2013-05-23 18:48:02 +02:00
}
2011-11-15 11:34:39 +01:00
2012-04-27 12:48:22 +02:00
add ( d . path . split ( "/" ) . drop ( 1 ) , d )
2012-04-26 17:24:43 +02:00
}
2011-05-03 21:04:45 +02:00
2012-05-24 12:40:52 +02:00
def parseConfig ( key : String , config : Config ) : Option [ Deploy ] = {
2011-12-09 20:19:59 +01:00
val deployment = config . withFallback ( default )
2012-11-08 18:49:54 +01:00
val router = createRouterConfig ( deployment . getString ( "router" ) , key , config , deployment )
2013-04-05 16:12:45 +02:00
val dispatcher = deployment . getString ( "dispatcher" )
2013-04-18 13:35:36 +02:00
val mailbox = deployment . getString ( "mailbox" )
Some ( Deploy ( key , deployment , router , NoScopeGiven , dispatcher , mailbox ) )
2012-11-08 18:49:54 +01:00
}
2011-07-26 17:12:00 +02:00
2012-11-08 18:49:54 +01:00
/* *
* Factory method for creating `RouterConfig`
* @param routerType the configured name of the router , or FQCN
* @param key the full configuration key of the deployment section
* @param config the user defined config of the deployment , without defaults
* @param deployment the deployment config , with defaults
*/
2013-09-19 08:00:05 +02:00
protected def createRouterConfig ( routerType : String , key : String , config : Config , deployment : Config ) : RouterConfig =
if ( routerType == "from-code" ) NoRouter
else {
// need this for backwards compatibility, resizer enabled when including (parts of) resizer section in the deployment
val deployment2 =
if ( config . hasPath ( "resizer" ) && ! deployment . getBoolean ( "resizer.enabled" ) )
resizerEnabled . withFallback ( deployment )
else deployment
2014-03-12 14:43:18 +01:00
val fqn = routerTypeMapping . getOrElse ( routerType , routerType )
2013-09-19 08:00:05 +02:00
def throwCannotInstantiateRouter ( args : Seq [ ( Class [ _ ] , AnyRef ) ] , cause : Throwable ) =
throw new IllegalArgumentException (
s" Cannot instantiate router [ $fqn ], defined in [ $key ], " +
s" make sure it extends [ ${ classOf [ RouterConfig ] } ] and has constructor with " +
s" [ ${ args ( 0 ) . _1 . getName } ] and optional [ ${ args ( 1 ) . _1 . getName } ] parameter " , cause )
// first try with Config param, and then with Config and DynamicAccess parameters
val args1 = List ( classOf [ Config ] -> deployment2 )
val args2 = List ( classOf [ Config ] -> deployment2 , classOf [ DynamicAccess ] -> dynamicAccess )
dynamicAccess . createInstanceFor [ RouterConfig ] ( fqn , args1 ) . recover ( {
case e @ ( _ : IllegalArgumentException | _ : ConfigException ) ⇒ throw e
case e : NoSuchMethodException ⇒
dynamicAccess . createInstanceFor [ RouterConfig ] ( fqn , args2 ) . recover ( {
case e @ ( _ : IllegalArgumentException | _ : ConfigException ) ⇒ throw e
case e2 ⇒ throwCannotInstantiateRouter ( args2 , e )
} ) . get
case e ⇒ throwCannotInstantiateRouter ( args2 , e )
} ) . get
2011-11-19 19:55:44 +01:00
}
2012-11-08 18:49:54 +01:00
2011-04-27 00:40:20 +02:00
}