Chapter 7. Playing with Globals

Sometimes web applications require application-wide objects that live beyond the request-response life cycle, such as database connections, application configuration, shared objects, and cross-cutting concerns (authentication, error handling, and so on). Consider the following:

  • Ensuring that the database used by the application is defined and accessible.
  • Notify through e-mail or any other service when the application is receiving unexpected heavy traffic.
  • Logging the different requests served by the application. These logs can later be used to analyze user behavior.
  • Restricting certain facilities on the web application by time. For example, some food ordering apps take orders only between 11 a.m. to 8 p.m., while all requests to build orders at any other time will be blocked and a message about the timings will be displayed.
  • Generally, when a user sends an e-mail and the recipient's email ID is incorrect or not in use, the sender is notified about the failure in delivering the e-mail only after 12 to 24 hrs. In this duration, further attempts are made to send the e-mail.

Applications with in-app sales allow users to retry with the same or different payment options when payment has been declined for various reasons.

In a Play Framework app, by convention, all of these various concerns can be managed through GlobalSettings.

In this chapter, we will discuss the following topics:

  • GlobalSettings
  • Application life cycle
  • Request-response life cycle

GlobalSettings

Every Play application has a global object which can be used to define application-wide objects. It can also be used to customize the application's life cycle and the request-response life cycle.

The global object for an application can be defined by extending the trait GlobalSettings. By default, the name of the object is expected to be Global and it is assumed to be in the app directory. This can be changed by updating application.global in the conf/application.conf property. For example, if we wish to use a file with AppSettings in the app/com/org name:

application.global=app.com.org.AppSettings

The GlobalSettings trait has methods that can be used to interrupt both the application's life cycle and the request-response life cycle. We will see its methods as and when required in the following sections.

Now, let's see how this works.

An app developed through the Play Framework is represented by an instance of the Application trait, since its creation and the build is to be handled by the framework itself.

The Application trait is extended by DefaultApplication and FakeApplication. FakeApplication is a helper that tests Play applications and we will see more of it in Chapter 9, Testing. DefaultApplication is defined as follows:

class DefaultApplication(
  override val path: File,
  override val classloader: ClassLoader,
  override val sources: Option[SourceMapper],
  override val mode: Mode.Mode) extends Application with WithDefaultConfiguration with WithDefaultGlobal with WithDefaultPlugins

The WithDefaultConfiguration and WithDefaultPlugins traits are used to initialize the application's configuration and plugin objects, respectively. The WithDefaultGlobal trait is the one responsible for setting the correct global object for the application. It is defined as follows:

trait WithDefaultGlobal { 
  self: Application with WithDefaultConfiguration => 

  private lazy val globalClass = initialConfiguration.getString("application.global").getOrElse(initialConfiguration.getString("global").map { g => 
    Play.logger.warn("`global` key is deprecated, please change `global` key to `application.global`") 
    g 
  }.getOrElse("Global")) 

  lazy private val javaGlobal: Option[play.GlobalSettings] = try { 
    Option(self.classloader.loadClass(globalClass).newInstance().asInstanceOf[play.GlobalSettings]) 
  } catch { 
    case e: InstantiationException => None 
    case e: ClassNotFoundException => None 
  } 

  lazy private val scalaGlobal: GlobalSettings = try { 
    self.classloader.loadClass(globalClass + "$").getDeclaredField("MODULE$").get(null).asInstanceOf[GlobalSettings] 
  } catch { 
    case e: ClassNotFoundException if !initialConfiguration.getString("application.global").isDefined => DefaultGlobal 
    case e if initialConfiguration.getString("application.global").isDefined => { 
      throw initialConfiguration.reportError("application.global", s"Cannot initialize the custom Global object ($globalClass) (perhaps it's a wrong reference?)", Some(e)) 
    } 
  } 

  private lazy val globalInstance: GlobalSettings = Threads.withContextClassLoader(self.classloader) { 
    try { 
      javaGlobal.map(new j.JavaGlobalSettingsAdapter(_)).getOrElse(scalaGlobal) 
    } catch { 
      case e: PlayException => throw e 
      case e: ThreadDeath => throw e 
      case e: VirtualMachineError => throw e 
      case e: Throwable => throw new PlayException( 
        "Cannot init the Global object", 
        e.getMessage, 
        e 
      ) 
    } 
  } 

  def global: GlobalSettings = { 
    globalInstance 
  } 
}

The globalInstance object is the global object to be used for this application. It is set to javaGlobal or scalaGlobal, whichever is applicable to the application. If the application does not have custom Global object configured for the application, the application's global is set to DefaultGlobal. It is defined as:

object DefaultGlobal extends GlobalSettings
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.37.38