In addition to the Servlet-Engine
, the Persistence Container is one of the main services appserver.io provides. The name Persistence-Container
might lead to some misunderstanding, as many people think that it mostly refers to database persistence. In Java, there are EJB-Containers that provide a broad set of functionalities like Bean- or Container-Managed-Persistence, whereas appserver.io only provides a small subset of the functionality similar to platforms like Wildfly. In the following, the possibilities of the Persistence-Container
and its usage for writing enterprise-ready applications, are described in detail.
Since version 1.1 we've an integrated Persistence Manager, based on Doctrine 2.5. The Persistence Manager is responsible to handle the Entity Managers defined by the deployed applications. Each Entity Manager itself simply is a wrapped Doctrine Entity Manager instance and enables you to access all the functionality, Doctrine is delivered with.
A Datasource specifies the connection to all kind of databases. As the application server uses Doctrine to handle database connections, the configuration parameters are very close to the one, you have to specify if you're using Doctrine in a usual project with one of the frameworks out there.
As all other configuration parameters, the Datasource configuration, also has to be done in a XML configuration file. In contrast to other application specific configuration, Datasource's can come with your application or can be defined globally, e. g. in the webroot folder of the application server. In that case, the application doesn't need to have to specify a datasource. Instead, the Persistence Unit, that will be explained in the next chapter, can reference one. If your application provides it's own Datasource, what will be the common case, it has to be specified in a file within the META-INF
folder and has to end with -ds.xml
, for example appserver-ds.xml
.
In the following example, you can see the configuration for two Datasource's.
<?xml version="1.0" encoding="UTF-8"?>
<datasources xmlns="http://www.appserver.io/appserver">
<datasource name="appserver.io-example-mysql">
<database>
<driver>pdo_mysql</driver>
<user>appserver</user>
<password>appserver.i0</password>
<databaseName>appserver_ApplicationServer</databaseName>
<databaseHost>127.0.0.1</databaseHost>
</database>
</datasource>
<datasource name="appserver.io-example-sqlite">
<database>
<driver>pdo_sqlite</driver>
<user>appserver</user>
<password>appserver</password>
<path>META-INF/data/appserver_ApplicationServer.sqlite</path>
<memory>false</memory>
</database>
</datasource>
</datasources>
The first example defines the Datasource for a MySQL database, the second one for a SQLite database that will be created in the META-INF/data
folder.
Param name | Type | Description |
---|---|---|
driver |
string | The driver specifies the actual implementations of the DBAL interfaces to use. |
user |
string | Username to use when connecting to the database. |
password |
string | Password to use when connecting to the database. |
databaseHost |
string | Hostname of the database to connect to. |
databasePort |
integer | Port of the database to connect to. |
databaseName |
string | Name of the database/schema to connect to. |
path |
string | The filesystem path to the database file. Mutually exclusive with memory. path takes precedence. |
memory |
boolean | True if the SQLite database should be in-memory (non-persistent). Mutually exclusive with path. path takes precedence. |
unixSocket |
string | Name of the socket used to connect to the database. |
driverOptions |
string | Any supported additional flags. |
charset |
string | The charset used when connecting to the database. |
You'll find a more detailed description about the possible driver/parameter options in the Doctring DBAL configuration.
Please be aware, a Datasource doesn't specify a Doctrine Entity Manager, it simply specifies a connection. To specify a Doctrine Entity Manager, what usually is what you want to do, you also need a Persistence Unit.
A Persistence Unit declares the Enity Manager that should be initialized by the application server and will be injected into your application's components when requested. Persistence Unit's can be configured by a XML file named META-INF/persistence.xml
inside your application. Each Persistence Unit references a datasource that can be defined as described in section Datasource.
The following example is a Persistence Unit configuration and give you a brief introduction about what configuration options are available.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://www.appserver.io/appserver">
<persistenceUnits>
<persistenceUnit name="ExampleEntityManager"
interface="Doctrine\ORM\EntityManagerInterface"
type="Doctrine\ORM\EntityManager"
factory="AppserverIo\Appserver\PersistenceContainer\Doctrine\EntityManagerFactory">
<metadataConfiguration type="annotation">
<directories>
<directory>/common/classes/AppserverIo/Apps/Example/Entities/Impl</directory>
</directories>
<params>
<param name="isDevMode" type="boolean">true</param>
<param name="useSimpleAnnotationReader" type="boolean">false</param>
<param name="proxyDir" type="string">false</param>
</params>
</metadataConfiguration>
<datasource name="appserver.io-example-mysql"/>
<annotationRegistries>
<annotationRegistry namespace="JMS\Serializer\Annotation">
<directories>
<directory>/vendor/jms/serializer/src</directory>
</directories>
</annotationRegistry>
</annotationRegistries>
</persistenceUnit>
</persistenceUnits>
</persistence>
Each Persistence Unit needs at least the <metadataConfiguration/>
and the <datasource/>
nodes work properly. The <annotationRegistries/>
node is optional.
The <persistenceUnit/>
node has for mandatory attributes:
name
attribute, which needs a unique name that will be used to reference the Persistence Unit for DI in your componentsinterface
attribute specifies the interface of the Entity Manager, that has to be Doctrine\ORM\EntityManagerInterface
unless you wrote your own custom Entity Managertype
attribute, that specifies the Entity Manager class to use, unless your wrote your own custom this has to be Doctrine\ORM\EntityManager
factory
class that creates the Entity Manager instance, by default AppserverIo\Appserver\PersistenceContainer\Doctrine\EntityManagerFactory
unless you write your own factory classThe subnode <metadataConfiguration/>
has only one, even the type
attribute. Theses attribute can have one of the three values
xml
: For a setup based on XML filesyaml
: For a YAML based setupannotation
: For an annotation based setupDepending on the values the factory either invokes the apropriate method on the Setup
class to create the Entity Manager, e. g. if you choose annotation
, the factory will invoke Setup::createAnnotationMetadataConfiguration()
.
The last mandatory subnode is <datasource/>
. The value of the name
attribute must also be a valid Datasource name
. In that way, you can relate the Persistence Unit with the Datasource with the same name you've entered here.
Finally we've the <annotationRegistries/>
subnode, that allows you to specify additional annotation libraries, such JMS
. You can register as many annotation libraries as necessary by specifying the details in a separate <annotationRegistry/>
node. This node only supports the namespace
attribute, that has to contain the annotation libraries namespace. Additionally you've to specifiy the directories to be parsed for the classes containing the annotation specification. Therefore you can define a <directories/>
subnode that contains several <directory/>
nodes with the path to a directory that has to be parsed.
Although providing persisting data to a database is one functionality of the Persistence-Container
, it is not the most important one. The following reasons support the usage of the Persistence-Container
. Since PHP is used as a scripting language, it lacks the possibility of having objects, we call them components, persistent in memory. The Persistence-Container
enables you to do exactly this. It provides performance and many other possibilities you would not benefit from if working with the well-known LAMP stack.
One may wonder how it is possible to have a component persistent in memory using PHP, a scripting language. Usually after every request the instance will be destroyed. The simple answer is: As appserver.io provides containers that run as daemons. You can specify components that are loaded when the application server starts and are in memory until the server shuts down. For simplicity reasons, the classes are called Beans, as it is done in Java.
There are three different types of beans, Session Beans
, Message Beans
and Entity Beans
. In version 1.0.0 we do not deliver support for Entity Beans
because the responsibility is up to ORM libraries like Doctrine. We support Doctrine to handle database persistence.
These
Server-Side Component Types
can be distributed across a network, free of charge for developers. If components have been deployed on different instances, the distribution has to be activated by configuration.
A session bean is a plain PHP class. You must not instantiate it directly because the application server takes care of its complete lifecycle.
A session bean MUST provide a non-argument constructor, optionally no constructor.
Therefore, if a developer needs access to a session bean, he requests the application server for an instance. The request can either be initiated by a client or Dependency Injection. In both cases, a proxy to the session bean is delivered that allows invoking its methods. Depending on your configuration, the proxy also allows you to call this method over a network as a Remote Method Call
. This enables you to figure out if the session bean is located on the same application server instance or on another one in your network.
When writing a session bean, the developer has to specify the type of bean he wants to implement. This can either be done by adding an annotation to the classes DocBlock or by specifying it in a deployment descriptor. As it seems to be easier to add the annotation and, in most cases, it is sufficient, we recommend this for the start.
We differentiate three kinds of session beans, even Stateless
, Stateful
and Singleton
session beans.
A SLSBs
state is only available for the time you invoke a method on it. As this bean type is designed for efficiency and simplicity, the developer does not need to take care about memory consumption, concurrency or lifecycle.
SLSBs
are very similar to PHP`s default request behaviour, as they are created to handle the request and will be destroyed after the request is finished.
With each request, a new SLSB
instance will be created. The instance is destroyed by the container after the request is handled.
The following example demonstrates the implementation of a SLSB
that provides functionality to create a user from the arguments passed to the createUser()
method. The SLSB
is registered with the name AStatelessSessionBean
in the application servers Naming Directory
. Registering a bean in the Naming Directory is necessary to use it for Dependency Injection as explained in the documentation.
<?php
namespace AppserverIo\Example\SessionBeans;
/**
* @Stateless(name="AStatelessSessionBean")
*/
class AStatelessSessionBean
{
/**
* Creates and returns a new md5 hash for the passed password.
*
* @param string $password The password we want to hash
*
* @return string The md5 hash representation of the password
*/
public function hashPassword($password)
{
return md5($password);
}
/* Creates a new user, hashes the password before.
*
* @param string $username The username of the user to create
* @param string $password The password bound to the user
*
* @return void
*/
public function createUser($username, $password)
{
// hash the password
$hashedPassword = $this->hashPassword($password);
/*
* Implement functionality to create user in DB
*/
}
}
Then we can implement a servlet that invokes the method with the credentials loaded from the request. The servlet can look like this.
<?php
namespace AppserverIo\Example\Servlets;
use AppserverIo\Psr\Servlet\Http\HttpServlet;
use AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface;
use AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface;
/**
* This servlets implements functionality to store user data by
* invoking a SLSB instance.
*
* @Route(name="user", urlPattern={"/user.do", "/user.do*"})
*/
class UserServlet extends HttpServlet
{
/**
* The SLSB instance we want to have injected, used to store the user.
*
* @var \AppserverIo\Example\SessionBeans\AStatelessSessionBean
* @EnterpriseBean(name="AStatelessSessionBean")
*/
protected $aStatelessSessionBean;
/**
* Handles a HTTP POST request.
*
* This is a very simple example that shows how to start a new session to
* login a user with credentials found as request parameters.
*
* @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface
* $servletRequest The request instance
* @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface
* $servletResponse The response instance
*
* @return void
* @see \AppserverIo\Psr\Servlet\Http\HttpServlet::doGet()
*/
public function doPost(
HttpServletRequestInterface $servletRequest,
HttpServletResponseInterface $servletResponse)
{
// create the user by invoking the SLSB createUser() method
$this->aStatelessSessionBean->createUser(
$username = $servletRequest->getParameter('username'),
$servletRequest->getParameter('password')
);
// add a message to the response
$servletResponse->appendBodyStream("$username has successfully been created!");
}
}
If we now invoke a POST
request on our servlet, sending username
and password
parameters, the application server will inject the SLSB
at runtime and invoke the doPost()
method. In return, this step invokes the createUser()
method on the SLSB
and adds a success message to the response.
The SFSB
is something between the two other types. It is bound to the session with the ID pass to the client, when an instance is requested. A SFSB
is very useful, if you want to implement something like a shopping cart. If the shopping cart instance will be declared as a class member of SFSB
, it is persistent for the sessions lifetime.
In contrast to a HTTP Session, SFSBs
enables you to have session bound persistence, without the need to explicitly add data to a session object. This makes development easy and comfortable. As SFSBs
are persisted in memory and not serialized to files, the application server has to make sure that the number of instances are flushed when their lifetime has been reached. By doing so, the number of instances is minimized.
SFSBs
are created by the container when they are requested and no instance exists, based on the passed session-ID. After the request has been processed, the instance will be re-attached to the container ready to handle the next request.
If the session is removed, times out, or the application server restarts, the data of a
SFSB
will be lost. BecauseSFSBs
use the HTTP session-ID, it is necessary to start an HTTP session before you invoke methods on it.
As described above, a SFSB
has a state that is bound to a HTTP session. It is necessary to start the HTTP session once before accessing it. Imagine we have a servlet and want to a access a SFSB
used to login a user with credentials found as request parameters. After a successfull login, the user entity should be persisted in the SFSB
in order to protect the following GET
requests.
<?php
namespace AppserverIo\Example\SessionBeans;
/**
* @Stateful
*/
class AStatefulSessionBean
{
/**
* The user, logged into the system.
*
* @var \AppserverIo\Apps\Example\Entities\User
*/
protected $user;
/**
* Logs the user into the system.
*
* @param string $username The username to login
* @param string $password The password used to login
*
* @return void
*/
public function login($username, $password)
{
/*
* Implement login functionality, e. g. check user/password in DB
*/
// make user entity persistent by setting it as SFSB property
$this->user = $user;
}
/**
* Checks if a user has been logged into the system, if not an exception
* will be thrown.
*
* @return void
* @throws \Exception Is thrown if no user is logged into the system
*/
public function isLoggedIn()
{
if (isset($this->user) === false) {
throw new \Exception('Please log-in first!');
}
}
}
A
SFSB
is pretty easy to use and has to be implemented as a plain old PHP class. It is important that the user entity, once set in theSFSB
, is available at every request as long as the HTTP session is available.
The necessary servlet is also a very simple example that implements the login on a POST
request, whereas the GET
request is protected.
<?php
namespace AppserverIo\Example\Servlets;
use AppserverIo\Psr\Servlet\Http\HttpServlet;
use AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface;
use AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface;
/**
* This servlets implements login functionality using a SFSB.
*
* @Route(name="login", urlPattern={"/login.do", "/login.do*"})
*/
class LoginServlet extends HttpServlet
{
/**
* The SFSB instance we want to have injected, used for login.
*
* @var \AppserverIo\Example\SessionBeans\AStatefulSessionBean
* @EnterpriseBean(name="AStatefulSessionBean")
*/
protected $aStatefulSessionBean;
/**
* Handles a HTTP POST request.
*
* This is a very simple example that shows how to start a new session to
* login the a user with credentials found as request parameters.
*
* @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface
* $servletRequest The request instance
* @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface
* $servletResponse The response instance
*
* @return void
* @see \AppserverIo\Psr\Servlet\Http\HttpServlet::doGet()
*/
public function doPost(
HttpServletRequestInterface $servletRequest,
HttpServletResponseInterface $servletResponse)
{
// create a new session, if not available
$session = $servletRequest->getSession(true);
// start the session and add the cookie to the response
$session->start();
// login by invoking the SFSB login() method
$this->aStatefulSessionBean->login(
$servletRequest->getParameter('username'),
$servletRequest->getParameter('password')
);
// add a message to the response
$servletResponse->appendBodyStream("You've successfully been logged in!");
}
/**
* Handles a HTTP GET request.
*
* @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface
* $servletRequest The request instance
* @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface
* $servletResponse The response instance
*
* @return void
* @see \AppserverIo\Psr\Servlet\Http\HttpServlet::doGet()
*/
public function doGet(
HttpServletRequestInterface $servletRequest,
HttpServletResponseInterface $servletResponse)
{
try {
// check for a user logged in
$this->aStatefulSessionBean->isLoggedIn();
/*
* do some other, almost protected, stuff here
*/
} catch(\Exception $e) {
$servletResponse->setStatusCode(500);
$servletResponse->appendBodyStream($e->getMessage());
}
}
}
You do not have to restart the session in the
GET
request because theServlet-Engine
is aware of the session-ID passed as request header and uses it when theSFSB
is injected on runtime.
A SSB
is created by the container only once for each application. Thus, whenever an instance is requested, it will be the same. If a variable is set as a SSB
member, it is available until it is overwritten, or the application server is restarted.
Concurrency is, in case of a SSB
, a more complex issue. In contrast to SLSBs
and SFSBs
, the data is shared across requests. The container has to make sure that only one request has access to the data of a SFSB
. Therefore, requests are serialized and blocked until the instance is available again.
To enable a
SSB
for sharing its data across requests, it has to extend the\Stackable
class. This class comes with the PECL pthreads extension that brings multithreading to PHP. appserver.io uses a fork of the 1.x branch, due to some restrictions introduced with 2.x branch.
In contrast to a SLSB
, the lifecycle of a SSB
is different. Once the instance is created, it is shared among all requests. Instead of destroying the instance after each request the instance persists in memory until the application is shut down or restarted.
A
SSB
gives developers great power because all data added to a member stays in memory until someone unsets it. Thus, aSSB
is an excellent option for sharing data across requests. However, great power comes with great responsibility for the developer. This is why he always has to keep an eye on aSSB
's memory consumption.
In combination with having data persistent in memory, a SSB
can be pre-loaded on application startup. This can be done by adding the @Startup
annotation to the classes DocBlock. Using explicit startup functionality and loading data from a configuration file or a DB persistent in memory, leads to massive performance improvements.
To demonstrate the usage of a SSB
the previous example of the SFSB
is extended by a counter tracking the number of successful logins.
<?php
namespace AppserverIo\Example\SessionBeans;
/**
* @Singleton
*/
class ASingletonSessionBean extends \Stackable
{
/**
* The number of successful logins since the last restart.
*
* @var integer
*/
protected $counter;
/**
* Raises the login counter.
*
* @return integer The new number of successful logins
*/
public function raise()
{
return $this->counter++;
}
}
To use the SSB
in a SFSB
, the SSB
can be injected with the @EnterpriseBeans
annotation. Additionally, the login()
method has to be customized to raise and return the number of successful logins by invoking the raise()
method of the SSB
.
<?php
namespace AppserverIo\Example\SessionBeans;
/**
* @Stateful
*/
class AStatefulSessionBean
{
/**
* The SSB instance that counts succesful logins.
*
* @var \AppserverIo\Example\SessionBeans\ASingletonSessionBean
* @EnterpriseBean(name="ASingletonSessionBean")
*/
protected $aSingletonSessionBean;
/**
* The user, logged into the system.
*
* @var \AppserverIo\Apps\Example\Entities\User
*/
protected $user;
/**
* Logs the user into the system.
*
* @param string $username The username to login
* @param string $password The password used to login
*
* @return integer The number of successful logins since the last restart
*/
public function login($username, $password)
{
/*
* Implement login functionality, e. g. check user/password in DB
*/
// make user entity persistent by setting it as SFSB property
$this->user = $user;
// raise and return the successfull login counter
return $this->aSingletonSessionBean->raise();
}
/**
* Checks if a user has been logged into the system, if not, an exception
* will be thrown.
*
* @return void
* @throws \Exception Is thrown if no user is logged into the system
*/
public function isLoggedIn()
{
if (isset($this->user) === false) {
throw new \Exception('Please log-in first!');
}
}
}
Finally, the servlet receives the number of successul logins since the application server's last restart and adds it to the response.
<?php
namespace AppserverIo\Example\Servlets;
use AppserverIo\Psr\Servlet\Http\HttpServlet;
use AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface;
use AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface;
/**
* This servlets implements login functionality using a SFSB.
*
* @Route(name="login", urlPattern={"/login.do", "/login.do*"})
*/
class LoginServlet extends HttpServlet
{
/**
* The SFSB instance we want to have injected, used for login.
*
* @var \AppserverIo\Example\SessionBeans\AStatefulSessionBean
* @EnterpriseBean(name="AStatefulSessionBean")
*/
protected $aStatefulSessionBean;
/**
* Handles a HTTP POST request.
*
* This is a very simple example that shows how to start a new session to
* login the user with credentials found as request parameters.
*
* @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface
* $servletRequest The request instance
* @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface
* $servletResponse The response instance
*
* @return void
* @see \AppserverIo\Psr\Servlet\Http\HttpServlet::doGet()
*/
public function doPost(
HttpServletRequestInterface $servletRequest,
HttpServletResponseInterface $servletResponse)
{
// create a new session, if not available
$session = $servletRequest->getSession(true);
// start the session and add the cookie to the response
$session->start();
// login by invoking the SFSB login() method + receive number
// of successful logins since last application server restart
$successfulLogins = $this->aStatefulSessionBean->login(
$servletRequest->getParameter('username'),
$servletRequest->getParameter('password')
);
// add the number of successful login attempts to the response
$servletResponse->appendBodyStream(
"$successfulLogins login attempts since last restart!"
);
}
/**
* Handles a HTTP GET request.
*
* @param \AppserverIo\Psr\Servlet\Http\HttpServletRequestInterface
* $servletRequest The request instance
* @param \AppserverIo\Psr\Servlet\Http\HttpServletResponseInterface
* $servletResponse The response instance
*
* @return void
* @see \AppserverIo\Psr\Servlet\Http\HttpServlet::doGet()
*/
public function doGet(
HttpServletRequestInterface $servletRequest,
HttpServletResponseInterface $servletResponse)
{
try {
// check for a user logged in
$this->aStatefulSessionBean->isLoggedIn();
/*
* do some other, almost protected, stuff here
*/
} catch(\Exception $e) {
$servletResponse->setStatusCode(500);
$servletResponse->appendBodyStream($e->getMessage());
}
}
}
Other than session beans, MDBs
are NOT invoked by a proxy, but are sent to a Message Broker
as receiver of the messages. The Message Broker
adds them to a queue until they are collected and proccessed in a separate thread.
Using
MDBs
enables you to execute long running processesasynchronously
because waiting for an answer after having set a message to theMessage Broker
is no longer neccessary. Unlike session beans,MDBs
have to implement theAppserverIo\Psr\Pms\MessageListenerInterface
interface. Like session beans,MDBs
MUST provide a non-argument constructor, optionally no constructor.
As MDBs
are mostly used in context of a Message-Queue, this section does not describe the functionality in detail.
Lifecycle Callbacks
enable a developer to declare callback methods depending on the bean's lifecycle. We support post-construct
and pre-destroy
callbacks. Lifecycle Callbacks
can be configured either by annotations or the deployment descriptor. Declaring Lifecycle Callbacks
by annotations is more intuitive, as you easily add the annotation to the methods DocBlock. Therefore, we go with the annotations here.
Keep in mind that
Lifecycle Callbacks
are optional, MUST bepublic
, MUST NOT have any arguments and CAN NOT deliver checked exceptions. Exceptions are handled by the container and result in acritical
log message.
As the bean's lifecycle is controlled by the container and Dependency Injection
works either by property or method injection, a Post-Construct
callback enables a developer to implement a method that is invoked by the container after the bean has been created and all instances have been injected.
This callback can be very helpful for implementing functionalities like cache systems that need to load data from a datasource once and update it frequently.
The second callback is the Pre-Destroy
callback. This is fired before the container destroys the instance of the bean.
As a simple example we add a Post-Construct
and a Pre-Destroy
callback to our SSB
example from the last section.
<?php
namespace AppserverIo\Example\SessionBeans;
/**
* @Singleton
*/
class ASingletonSessionBean
{
/**
* The number of successful logins since the last restart.
*
* @var integer
*/
protected $counter;
/**
* Lifecycle Callback that will be invoked by the container on
* application startup.
*
* @return void
* @PostConstruct
*/
public function startup()
{
// try to load the counter from a simple textfile
if ($counter = file_get_contents('/tmp/counter.txt')) {
$this->counter = (integer) $counter;
} else {
$this->counter = 0;
}
}
/**
* Lifecycle Callback that will be invoked by the container before the
* bean will be destroyed.
*
* @return void
* @PreDestroy
*/
public function shutdown()
{
// write the counter back to a simple textfile
file_put_contents('/tmp/counter.txt', $this->counter);
}
/**
* Raises the login counter.
*
* @return integer The new number of successful logins
*/
public function raise()
{
return $this->counter++;
}
}
This extends the SSB
with some kind of real persistence by loading the counter from a simple textfile on application startup or writing it back before the SSB
is destroyed.
Interceptors
enable a developer to weave cross-cutting concerns into his application, without adding code to business methods. An Interceptor
is an advice. The functionality behind the secenes is AOP.
To add a very basic ACL authorization functionality that use an Interceptor
, we have to implement a simple aspect first. The aspect looks like this
<?php
namespace AppserverIo\Example\Aspects;
use AppserverIo\Doppelgaenger\Interfaces\MethodInvocationInterface;
/**
* @Aspect
*/
class AuthorizationInterceptor
{
/**
* Advice used to check user authorization on method call.
*
* @param \AppserverIo\Doppelgaenger\Interfaces\MethodInvocationInterface $methodInvocation
* Initially invoked method
*
* @return void
* @throws \AppserverIo\Example\Exceptions\AuthorizationException
* Is thrown if access is denied for the user logged into the system
*
* @Before
*/
public function authorize(MethodInvocationInterface $methodInvocation)
{
// load class and method name
$className = $methodInvocation->getStructureName();
$methodName = $methodInvocation->getName();
// load context, a instance of AStatefulSessionBean
$context = $methodInvocation->getContext();
// load the application context
$application = $context->getApplication();
// load user logged into the system
$user = $context->getUser();
// load the SLSB handling the ACLs
$aclSessionBean = $application->search('AclSessionBean');
/*
* Query whether the user is allowed to invoke the method and will throw
* an exception that could be catched/handled in the servlet for example
*/
$aclSessionBean->allowed($methodInvocation, $user);
// log the method invocation
$methodInvocation->getContext()
->getApplication()
->getInitialContext()
->getSystemLogger()
->info(
sprintf('The method %s::%s is about to be called', className, methodName)
);
}
}
Keep in mind that the
$methodInvocation->getContext()
method allows access to the component the advice has been declared in, in our example this is theSSB
instance below.
If we want to authorize the user logged into the system for the method call to a session bean method, we simply have to declare it by adding an annotation like the following.
<?php
namespace AppserverIo\Example\SessionBeans;
use AppserverIo\Example\Interceptors\AuthorizationInterceptor;
/**
* @Stateful
*/
class AStatefulSessionBean
{
/**
* The SSB instance that counts succesful logins.
*
* @var \AppserverIo\Example\SessionBeans\ASingletonSessionBean
* @EnterpriseBean(name="ASingletonSessionBean")
*/
protected $aSingletonSessionBean;
/**
* The user, logged into the system.
*
* @var \AppserverIo\Apps\Example\Entities\User
*/
protected $user;
/**
* Logs the user into the system.
*
* @param string $username The username to login
* @param string $password The password used to login
*
* @return void
*/
public function login($username, $password)
{
/*
* Implement login functionality, e. g. check user/password in DB
*/
// make user entity persistent by setting it as SFSB property
$this->user = $user;
// raise and return the successfull login counter
return $this->aSingletonSessionBean->raise();
}
/**
* Checks if a user has been logged into the system, if not an exception
* will be thrown.
*
* @return void
* @throws \Exception Is thrown if no user is logged into the system
*/
public function isLoggedIn()
{
if (isset($this->user) === false) {
throw new \Exception('Please log-in first!');
}
}
/**
* Returns the user logged into the system.
*
* @return \AppserverIo\Apps\Example\Entities\User
* The user logged into the system
*/
public function getUser()
{
return $this->user;
}
/**
* A business method protected by a before advice that will query authorization
* for the users method call by invoking the authorize() method of our
* interceptor.
*
* @return void
* @Before("advise(AuthorizationInterceptor->authorize())")
*/
public function protectedMethod()
{
// do something protected here
}
}
The AclSessionBean
is NOT implemented in this example because this description only gives a rough indication on how to implement such a functionality and how an Interceptor
can be used.
Please note that by clicking "Load Comments", your browser will establish a connection with servers from Disqus.
For more information, please visit our privacy statement.