|
|
You may sometimes need to initialize other objects for your
application, Spine allows you to pass additional initialization
properties to your application but you must implement an instance
of
SpinePlugin and ensure that you handle the
initialization of your objects from this implementation. Spine
will then use the
SpinePlugin instance to perform
initialization.Eg if you wish to read another XML file at
initialization, follow the steps below:
-
Create a class which reads the XML file and transposes it
into a composite object eg a Document object.
-
Create a class which implements the
SpinePlugin interface.
-
Add an entry to the
spine-init.xml to reflect your
new
SpinePlugin. This must be listed as a plugin tag in
the
spine-init.xml. Eg:
<plugins>
<plugin key="xmlReaderPlugin">
<pluginName>
foo.bar.plugin.XMLReaderPlugin
</pluginName>
<property name="fileName"
value="/home/joebloggs/myXmlFile.xml"/>
<property
name="anotherProperty"
value="PROPERTY_VALUE "/>
</plugin>
</plugins>
-
Handle your calls to your XML reader from the
SpinePlugin.process(Map):void method. The
Map sent from the
ApplicationConfigurator becomes available to
the
SpinePlugin and it contains all the
parameters defined by the user for the plugin in the
spine-init.xml file. The plugin
is initialized after the
ApplicationConfigurator will have run its
initialization processes.
-
If you wish to access your plugin after initialization or
from one of your processes, you can can access the plugin by
calling
PluginServiceLocator.getPlugin(pluginName:String):SpinePlugin.
Further information on creating plugins is available in the next
section.
|
|
|
|
|
|
In order to create a
SpinePlugin, you will need to create a java
class which either extends
AbstractSpinePlugin or implements the
SpinePlugin interface.As
AbstractSpinePlugin already defines 2 of the
3 methods you will need to implement, we recommend you extend this
object.
To demonstrate the creation and use of a
SpinePlugin, we will use the example in the
previous section i.e we need a plugin which effective reads data
from an XML file, whose properties will be used to create some
Java objects which we intend to use in certain parts of our
application. The steps enumerated below describes how we could go
about creating a
SpinePlugin to store
Groups and
Applications registered with the framework:
-
Create your plugin class and define the class and its key
in the
spine-init.xml
file. For our plugin class we define:
public class RolePlugin extends AbstractSpinePlugin
{
private Map membersMap = null;
public RolePlugin(){
membersMap = Collections.synchronizedMap(new HashMap());
}
}
For our plugin initialization configuration, we define:
<plugins>
<plugin key="rolePlugin">
<pluginName>
foo.bar.plugin.RolePlugin
</pluginName>
<property
name="fileName"
value="/path-to/spine-distro/groups.xml" />
</plugin>
</plugins>
-
Read the
previous section and follow the
instructions to see how your plugin will be initiated and
persisted in the framework.
-
Create an XML file to store your data, for this example we
will be using the default
groups.xml file
shipped with this distribution.
-
Create a class to read and store the contents of the XML
file in object format. Our example uses the class
RoleConfigHelper which reads the XML file
based on the filename sent to it and saves it as a map of
Member objects.
-
Implement the process method of the plugin you have just
created, for this example we call the plugin, RolePlugin.As we
also need a means of accessing and querying the contents of the
map of
Members, we add some other delegate methods
i.e:
-
getAllGroups():Map
|
Used to retrieve the map of Groups And Applications
|
-
getGroupById(id:String):Group
|
Used to retrieve a Group whose id is known to the system.
|
-
getGroupByName(userName:String):Group
|
Used to retrieve a Group whose userName is known to the
system.
|
These method implementations can be seen in the code snippet
shown below:
/*
*
Gets the map of saved Groups and Applications in this plugin
*
*/
public Map getAllGroups(){
return this.membersMap;
}
/*
*
Gets the Group or Application whose id is specified by id
in the map of Groups and Applications
*
*/
public Group getGroupById(String id){
return (Group) this.membersMap.get(id);
}
/*
*
Gets the Group or Application whose userName is specified
by userName in the map of Groups and Applications
*
*/
public Group getGroupByName(String userName){
Group someGroup = null;
Iterator it = this.membersMap.keySet().iterator();
while(it.hasNext()){
String key = (String) it.next();
someGroup = membersMap.get(key);
if(someGroup.getUserName().equals(userName){
break;
}
else continue;
}
}
-
In the process method of the plugin, we simply extract the
parameters we specified in the
spine-init.xml
file, pass the extracted file name to the instance of
RoleConfigHelper, and save the returned Map
as an instance variable in our plugin as shown below:
public void process(Map map){
String fileName = (String) map.get("fileName");
RoleConfigHelper rch = new RoleConfigHelper();
this.membersMap = rch.createConfig(fileName);
}
-
To retrieve the plugin at anytime during the duration of
this application, we call
PluginServiceLocator.getPlugin(key:String):SpinePlugin
method and request the plugin by the key by which it is known to
the system. i.e:
PluginServiceLocator psl
= PluginServiceLocator.getInstance(); RolePlugin
rolePlug
= psl.getPlugin("role");
As you can see from the example above, it is easy to add your
plugins to the framework and as the framework maintains
synchronized reference to all the registered plugins, we can
easily retrieve the objects we have persisted in our plugins at
any time the application is running.
|
|
|
|
|
|
The Spine framework does not provide reference DataSource
connector implementations for all defined databases. This
connector, called a
DataSourceBuilder performs the function of
instantiating a DataSource and making it available to the
framework and the user application.
It does however provide a default
DataSourceBuilder which is suitable for some
databases but not for all databases.
In order to create a DataSource connector or
DataSourceBuilder, you will need to perform
the following actions:
-
Ensure your datasource class libraries are in the
classpath of the application and framework.
-
Specify your datasource initialization and access
parameters in the
spine-init.xml
file. This will usually be in the dataSource tag e.g
<dataSources>
<dataSource key="Mysql"
default="true">
<dataSourceBuilder>
com.zphinx.spine.start.helpers.impl.MysqlDataSourceBuilder
</dataSourceBuilder>
<sourceClass>
com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
</sourceClass>
<property
name="userName"
value="testuser"/>
<property
name="password"
value="minerva"/>
<property
name="url"
value="jdbc:mysql://192.168.1.5:3306/securesite"/>
<property
name="port"
value="3306"/>
<property
name="databaseName"
value="securesite"/>
</dataSource> </dataSources>
-
Create a class which implements the interface
DataSourceBuilder of which only method
requires a concrete implementation. This method, the
DataSourceBuilder.createDataSource(m:Map):DataSource
will receive a map of all the properties specified in the
initialization file (
spine-init.xml). E.g.
public class MysqlDataSourceBuilder implements DataSourceBuilder
{ /**
* Public Constructor
*/ public
MysqlDataSourceBuilder() {
super();
}
}
-
In the
DataSourceBuilder.createDataSource(m:Map):DataSource
method of your DataSourceBuilder implementation, retrieve the
initialization properties you have set in the
spine-init.xml as
shown below:
/* *
(non-Javadoc) * * @see
com.zphinx.spine.start.helpers.DataSourceBuilder#createDataSource(java.util.Map)
*/
public
DataSource createDataSource(Map map) {
Iterator it = map.keySet().iterator();
String userName = null;
String password = null;
String databaseName = null;
String url = null;
String sourceClass = null;
String server = null;
int port = 3306;
int loginTimeout = 15;
String profileSql = null;
while (it.hasNext()){
String key = (String) it.next();
String value = (String) map.get(key);
if(key.equalsIgnoreCase(USER_NAME)){
userName = value;
}
else if(key.equalsIgnoreCase(PASSWORD)){
password = value;
}
else if(key.equalsIgnoreCase(DATABASE_NAME)){
databaseName = value;
}
else if(key.equalsIgnoreCase(URL)){
url = value;
}
else if(key.equalsIgnoreCase(PORT)){
try{
port = Integer.parseInt( value);
}
catch (NumberFormatException e){
log.debug("Unable to set port address, setting port to
default: " + port);
}
}
else if(key.equalsIgnoreCase(LOGIN_TIMEOUT)){
try{
loginTimeout = Integer.parseInt( value);
}
catch (NumberFormatException e){
log.debug("Unable to set login timeout, setting timeout to
default: " + loginTimeout);
}
}
else if(key.equalsIgnoreCase(SERVER_NAME)){
server = value;
}
else if(key.equalsIgnoreCase(PROFILE_SQL)){
profileSql = value;
}
else if(key.equalsIgnoreCase(SOURCE_CLASS)){
sourceClass = value;
}
} return
this.createMysqlDataSource(sourceClass, userName, password,
databaseName, url, port, server, profileSql, loginTimeout); }
-
After retrieving the properties from the stated Map, you
should then instantiate your DataSource class and use its setter
methods to set the defined properties which you retrieved from
the map. E.g:
private DataSource
createMysqlDataSource(String sourceClass,
String userName, String
password, String databaseName, String url,
int port, String server,
String profileSql, int loginTimeout) {
MysqlDataSource
source = null;
try {
source = (MysqlDataSource)
Class.forName(sourceClass).newInstance();
source.setDatabaseName(databaseName);
source.setPort(port);
source.setPassword(password);
source.setUrl(url);
source.setUser(userName);
source.setServerName(server);
source.setLoginTimeout(loginTimeout);
source.setProfileSql(profileSql);
}
catch (Throwable e) {
e.printStackTrace();
}
return source;
}
Return the instantiated DataSource from the implementated method
to conclude the DataSource creation process as shown above.
You can see from above that only one method need be implemented
for us to make available a DataSource connector to the Spine
framework. It is also possible to use this method to call your
JNDI resources to grant the framework an instance of a
pre-initiated DataSource.
|
|
|
|
|
Security properties in spine are available for 2 main objects, the
Member object and its sub classes, and
SpineBeans and its extensions.
SpineBeans and
Member objects are entity beans which contain
a security profile known as a
Permission. There are 2 kinds of
Permissions, namely:
SpinePermission
SpinePermission operates similarly to the Unix
access control rules system i.e resources have owners,groups and
public access. An extension of the
SpineBean which possesses a
SpinePermission can have its permission
queried by any principal which wishes to access it. This Permission
also exposes flags to denote if the accessing principal has
Read,Write and/or execute access.
The steps for creating a
SpineBean are described
above and require
that a principal's permission be used (Where the bean is not
created by the system) in the creation process. This
automatically applies the security properties of the
principal to the SpineBean which can then be querried
at a latter time.
To enforce security at any time, the client developer must invoke
one of
SpinePermission.getPermit(String):boolean
or
SpinePermission.checkGuard(Object):void.
Please note that these two methods only check accessibility to
the stated
SpineBean and not the specific ACL
in use relating to the specified principal.
To find out what access rights the principal has over the stated
SpineBean you will need to call the methods:
The value of the flags returned by the 3 methods above are
determined by the
PermissionLevel associated with the
SpinePermission and are based on the unix
octal representation of ACL. The
PermissionLevel. can be set at any time but
users are advised when using a principal to set this
PermissionLevel, you should use the
secure variant of
SpinePermission, i.e
SpinePermission.setPermission(PermissionLevel,SpinePermission):void.
MemberPermission
Both Permission types work differently, although the
MemberPermission inherits from the
SpinePermission, the
PermissionLevel object available in the
MemberPermission is not queried when
determining if a principal can access a role or manage another
principal, instead spine will use the records of the
principal's roles and principals to determine access control.
For this reason, a call to
Permission.getPermit(String):boolean where the
Permission is a
MemberPermission will not determine read,write
and execute access but will be sufficient to determine if
access is permited.
If the client developer wishes to add unix type ACL rules, then the
developer should set a
PermissionLevel into the
MemberPermission and assign values to it's
properties.
|
|
|
|
|
Creating a proxy extension requires that the client developer
extend the
AbstractDataProxy class and implement 2
methods, namely
AbstractDataProxy.open(Object,String,DAOInput):DataAccessObject
AbstractDataProxy.close(DataAccessObject):void
The open method allows the new
DataBaseProxy to instantiate/initialize all
parameters needed by the
DataBaseProxy and also provides the new
DataBaseProxy with 3 parameters namely:
-
Object: Any arbitary object which can possess
properties needed to drive the DataProxy, eg
DataBaseProxy uses a DataSource object but
FileProxy uses an array of Strings(
Path,boolean expressed as a String ).
-
String: A string representing the type of
DataAccessObject to create.This will usually
be the full className of a
DataAccessObject.You will notice that this
and the DAOInput parameter can be passed directly to the
AbstractDataProxy.createDataAccessImpl(String,DAOInput):Object
method to create the
DataAccessObject specified by the full
class name.
-
DAOInput: This is the interface which must be
implemented when passing parameters to any custom
DAO. It can be null but when we create a
DAO and we wish it to pass parameters to it,
we must wrap this parameter(s) in a
DAOInput so that Spine can recognize and
instantiate it for us.
The close method is used to close resources opened by the
AbstractDataProxy and the
DataAccessObject, any resources that you wish
to ensure are closed should be handled in this method.
DataAbstracts
DataAbstracts are implementation of
DataAccessObjects specific to a particular
DataProxy, E.G. the
DatabaseAbstract is specific to
DataAccessObjects which access a database.
Please note you do not have to use a
DataAbstract class but as several methods
within the
DataAccessObject interface will be recurrent,
creating a
DataAbstract to implement this methods is good
coding sense.
If creating a new
DataProxy, you should look for generic methods
which can be abstracted to a
DataAbstract class.You will discover that
every time you need to create a
DataAccessObject, all you need do is to extend
your
DataAbstract which can be used by your
DataProxy.
Defining the new Proxy in configuration
Everytime you create a new
DataProxy, you must define this proxy in the
spine.xml file. This is to allow the framework instantiate and
associate this
DataProxy with DAOs which require the
DataProxy for operation. You will normally use
an XML tag which has been assigned a unique index as shown below:
<dataProxy name="com.foo.bar.NewProxy"
index="11"/>
|
|
|
|
|
Spine messaging and Exception components require a registered java
properties file to be available at initialization. Several
properties files can be registered with the system as the system
will usually read through all this files until it finds the key it
requires. This provides multilingual/locale sensitive messages
which can be rendered to your view when needed.
Messaging ComponentsThere are
three main messaging components in Spine. They are :
-
DisplayMessage
|
This object represents a single message which can be displayed
to the user of the spine framework. It use a single key and an
array of parametric objects for string replacement to create a
locale sensitive message.
|
-
DisplayError
|
This
DisplayMessage extension represents a
single error which can be displayed to the user of the spine
framework. It use a severity,a single key and an array of
parametric objects for string replacement to create a locale
sensitive message.
|
-
DisplayMessages
|
Serves as a container for all the messages generated as a
result of the processing activity. It can contain several
messages and errors all of which can be identified by a set of
unique keys. EG one
DisplayMessages can contain several errors
and messages registered with key
"ERRORS" and key
"MESSAGES".There is no
limit on the number of keys which may be registered with a
single
DisplayMessages object.
|
The user should note that only the message key is available in a
DisplayMessage or a
DisplayError object. This message key is
parsed through the properties files immediately it is added to a
DisplayMessages object.
The persisted message key is returned if an entry is not found for
that key in any of the properties files defined for the framework.
Exception Components
There are 5 exceptions included in the spine
framework,namely:
-
ResourceException
|
The exception which is obtained when we fail to obtain a
resource associated with the spine framework
|
-
SpineApplicationException
|
An application specific exception which can be thrown when
processing a request using the framework
|
-
SpineException
|
A general exception thrown by the framework
|
-
SpineRuntimeException
|
A runtime exception that can be thrown by the framework
|
-
SpineMessageException
|
A locale sensitive exception useful for displaying exception
information in a locale sensitive manner. This exception
defines messages as keys which are available in the
properties files stored in the configuration. Parametric
replacement is possible because most of the methods of this
exception allow an Object array to be used as a
parameter.
|
You may use any of the above exceptions in your code. The most
useful exception is the
SpineMessageException as it allows your
application to display locale sensitive information obtained from a
properties file.
|
|
|
|
|
Client developers can send a request to spine in 2 different ways,
these are shown in the use of the
ViewProcessor default types i.e
- DefaultViewProcessor
- MultiViewProcessor
This is the default
ViewProcessor to use when your request is a simple ViewProcessor
- BusinessDelegate - DAO request .
If the request requires that the
ViewProcessor has to go back to the engine
and possibly invoke another
BusinessDelegate,you simply specify this BusinessDelegate in configuration and the ViewProcessor will
invoke the delegate and its associated DAO.
You will need to reuse the same instance of
ViewProcessor (do not create a new
instance!!) otherwise extend the
DefaultViewProcessor but ensure that a
single instance handles your calls in a sequential manner. The
order in which the
ViewProcessor instance will call the
BusinessDelegate will depend on the configuration of the
ViewProcessor in the spine.xml file.
The
MultiViewProcessor is specifically meant for
objects which need to handle various DAOS and/or ManagedObjects
within one delegate. A builder should be provided which we may
use to perform various operations on the different objects. We
recommend using extensions of the SpineBean as ManagedObjects
specified in the configuration file spine.xml.
The
MultiViewProcessor behaves just like a normal
processor, but allows us to define more than one DataProxy,DAO
instance for a BusinessDelegate.i.e
This configuration allows the delegate to use a DAO to process
different ManagedObjects each directly linked to a DAO via
configuration. It is also possible to use a more
sophisticated configuration of more than one BusinessDelegate
interfacing with several DAOs as seen by the one to many relationship in the diagram above.
Again you must reuse the same instance of
MultiViewProcessor(do not create a new
instance!!) otherwise extend the default
MultiViewProcessor but ensure that a single
instance handles your calls in a sequential manner. The order in
which the
MultiViewProcessor instance will call a
BusinessDelegate will depend on the configuration of the
MultiViewProcessor in the spine.xml file.
<< Back |
Home |
Index |
Forward >>
|
|
|
|