GateIn 3.0 is the merge of two mature Java projects; JBoss Portal and eXo Portal. This new community project takes the best of both offerings and incorporates them into a single j2ee deployment archive. The aim is to provide an intuitive user-friendly portal and a framework to address the needs of today's Web 2.0 applications.
This book provides deep-dive information about the installation and configuration of the services provide by GateIn.
GateIn homepage: www.gatein.org
GateIn videos: www.jboss.org/gatein/videos.html
GateIn documentation: www.jboss.org/gatein/documentation.html
GateIn downloads: www.jboss.org/gatein/downloads.html
GateIn 3.0 has two different database dependencies. One is the identity service configuration, which depends on the Hibernate. The other database dependency is Java content repository (JCR) service, which depends on the native JDBC API and it can integrate with any existing datasource implementation.
When you change the database configuration for the first time, GateIn will automatically generate the proper schema (assuming that the database user has the appropriate permissions).
GateIn 3.0 assumes the default encoding for your database is
latin1
. You will need to change this parameter for
your database in order to work properly.
To configure the database used by JCR you will need to edit the file:
$JBOSS_HOME/server/default/conf/gatein/configuration.properties
For Tomcat, the file is located at
$TOMCAT_HOME/gatein/conf/configuration.properties
And edit the values of driver, url, username and password with the values for your JDBC connection (Please refer to your database JDBC driver documentation).
gatein.jcr.datasource.driver=org.hsqldb.jdbcDriver
gatein.jcr.datasource.url=jdbc:hsqldb:file:${gatein.db.data.dir}/data/jdbcjcr_${name}
gatein.jcr.datasource.username=sa
gatein.jcr.datasource.password=
In that case, the name of the database is "jdbcjcr_${name}", ${name} should be part of the database name, as it is dynamically replaced by the name of the portal container extension (for instance gatein-sample-portal.ear defines "sample-portal" as container name and the default portal defines "portal" as container name).
In the case of HSQL the databases are created automatically, for any other database you will need to create a database named jdbcjcr_portal (and "jdbcjcr_sample-portal" if you kept gatein-sample-portal.ear in $JBOSS_HOME/server/default/deploy. Note that some database wont accept '-' in a database name and you will have to delete $JBOSS_HOME/server/default/deploy/gatein-sample-portal.ear)
Make sure the user has rights to create tables on jdbcjcr_portal and to update them as during the first startup they will be automatically created.
Also add the JDBC driver into the classpath, for instance in $JBOSS_HOME/server/default/lib (or $TOMCAT_HOME/lib if you are running on Tomcat)
MySQL example:
Let's configure our JCR to store data in MySQL, let's pretend we have a user named "gateinuser" with a password "gateinpassword". We would create a database "mygateindb_portal" (Remember that _portal is required) and assign him the rights to create tables.
Then we need to add the MySQL JDBC connector in the classpath and finally edit gatein.ear/02portal.war/WEB-INF/conf/jcr/jcr-configuration with:
gatein.jcr.datasource.driver=com.mysql.jdbc.Driver gatein.jcr.datasource.url=jdbc:mysql://localhost:3306/mygateindb${container.name.suffix} gatein.jcr.datasource.username=gateinuser gatein.jcr.datasource.password=gateinpassword
By default users are stored in database. To change the database to store users, you will need to edit the file:
$JBOSS_HOME/server/default/conf/gatein/configuration.properties
For Tomcat, the file is located at
$TOMCAT_HOME/gatein/conf/configuration.properties
You will find the same configuration as in jcr-configuration.xml:
gatein.idm.datasource.driver=org.hsqldb.jdbcDriver gatein.idm.datasource.url=jdbc:hsqldb:file:${gatein.db.data.dir}/data/jdbcidm_${name} gatein.idm.datasource.username=sa gatein.idm.datasource.password
GateIn 3.0 has a service to send e-mails that requires to be configured before it can work properly. This service is used to send e-mails to users who forgot their username or password for instance.
The e-mail service can use any SMTP account that needs to be configured in $JBOSS_HOME/server/default/conf/gatein/configuration.properties (Or $TOMCAT_HOME/gatein/conf/configuration.properties if you are using Tomcat).
The relevant section looks like:
# EMail gatein.email.smtp.username= gatein.email.smtp.password= gatein.email.smtp.host=smtp.gmail.com gatein.email.smtp.port=465 gatein.email.smtp.starttls.enable=true gatein.email.smtp.auth=true gatein.email.smtp.socketFactory.port=465 gatein.email.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
It is preconfigured for GMail so that any GMail account can be easily used (Just use a full gmail address as username and fill-in the password.
The default portal will be accessed if the user doesn't specify another portal. For example: http://hostname:port/portal/
. The default portal also be used at startup to determine if the database is empty.
The example configuration file below is found at: "02portal.war:/WEB-INF/conf/portal/portal-configuration.xml
".
<component>
<key>org.exoplatform.portal.config.UserPortalConfigService</key>
<type>org.exoplatform.portal.config.UserPortalConfigService</type>
<component-plugins>
<component-plugin>
<name>new.portal.config.user.listener</name>
<set-method>initListener</set-method>
<type>org.exoplatform.portal.config.NewPortalConfigListener</type>
<description>this listener init the portal configuration</description>
<init-params>
<value-param>
<name>default.portal</name>
<description>The default portal for checking db is empty or not</description>
<value>classic</value>
</value-param>
..........
</init-params>
</component-plugin>
</component-plugins>
</component>
In this example the classic portal has been set as the default. Note that the definition should be as a initial parameter of the NewPortalConfigListener component-plugin.
GateIn 3.0 uses the PicketLink IDM component to retain necessary identity information (user, groups, memberships, etc.). While legacy interfaces are still used (org.exoplatform.services.organization) for identity management, the wrapper implementation delegates to the PicketLink IDM framework.
This section doesn't provide information about PicketLink IDM and its configuration. Please refer to the appropriate project documentation (http://jboss.org/picketlink/IDM.html) for further information.
It is important to fully understand the concepts behind this framework design before changing the default configuration.
The identity model represented in 'org.exoplatform.services.organization' interfaces and the one used in PicketLink IDM have some major differences.
For example: the PicketLink IDM provides greater abstraction. It is possible for groups in the IDM framework to form memberships with many parents (which requires recursive ID translation) while the GateIn model allows only pure tree like membership structures.
Additionally the GateIn membership concept needs to be translated into the IDM Role concept. Therefore PicketLink IDM model is used in a limited way. All these translations are applied by the integration layer.
The main configuration file is idm-configuration:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd http://www.exoplaform.org/xml/ns/kernel_1_0.xsd" xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"> <component><key>org.exoplatform.services.organization.idm.PicketLinkIDMService</key> <type>org.exoplatform.services.organization.idm.PicketLinkIDMServiceImpl</type> <init-params> <value-param> <name>config</name> <value>war:/conf/organization/idm-config.xml</value> </value-param> <value-param> <name>portalRealm</name> <value>realm${container.name.suffix}</value> </value-param> </init-params> </component> <component> <key>org
.exoplatform.services.organization.OrganizationService</key> <type>org.exoplatform.services.organization.idm.PicketLinkIDMOrganizationServiceImpl</type> <init-params> <object-param> <name>configuration</name> <object type="org.exoplatform.services.organization.idm.Config"> <field name="useParentIdAsGroupType"> <boolean>true</boolean> </field> <field name="forceMembershipOfMappedTypes"> <boolean>true</boolean> </field> <field name="pathSeparator"> <string>.</string> </field> <field name="rootGroupName"> <string>GTN_ROOT_GROUP</string> </field> <field name="groupTypeMappings"> <map type="java.util.HashMap"> <entry> <key><string>/</string></key> <value><string>root_type</string></value> </entry> <!-- Sample mapping --> <!-- <entry> <key><string>/platform/*</string></key> <value><string>platform_type</string></value> </entry> <entry> <key><string>/organization/*</string></key> <value><string>organization_type</string></value> </entry> --> </map> </field> <field name="associationMembershipType"> <string>member</string> </field> <field name="ignoreMappedMembershipType"> <boolean>false</boolean> </field> </object> </object-param> </init-params> </component> </configuration>
![]() | The org.exoplatform.services.organization.idm.PicketLinkIDMServiceImpl service has following options:
|
![]() | The org.exoplatform.services.organization.idm.PicketLinkIDMOrganizationServiceImpl key is a main entrypoint implementing org.exoplatform.services.organization.OrganizationService and is dependant on org.exoplatform.services.organization.idm.PicketLinkIDMService org.exoplatform.services.organization.idm.PicketLinkIDMOrganizationServiceImpl service has following options defined as fields of object-param type org.exoplatform.services.organization.idm.Config:
Additionally JBossIDMOrganizationServiceImpl uses those defaults to perform identity management operations
|
A sample PicketLink IDM configuration file is shown below. To understand all the options present in it please refer to the PicketLink IDM Reference Guide
<jboss-identity xmlns="urn:jboss:identity:idm:config:v1_0_beta" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:identity:idm:config:v1_0_alpha identity-config.xsd"> <realms> <realm> <id>PortalRealm</id> <repository-id-ref>PortalRepository</repository-id-ref> <identity-type-mappings> <user-mapping>USER</user-mapping> </identity-type-mappings> </realm> </realms> <repositories> <repository> <id>PortalRepository</id> <class>org.jboss.identity.idm.impl.repository.WrapperIdentityStoreRepository</class> <external-config/> <default-identity-store-id>HibernateStore</default-identity-store-id> <default-attribute-store-id>HibernateStore</default-attribute-store-id> </repository> </repositories> <stores> <attribute-stores/> <identity-stores> <identity-store> <id>HibernateStore</id> <class>org.jboss.identity.idm.impl.store.hibernate.HibernateIdentityStoreImpl</class> <external-config/> <supported-relationship-types> <relationship-type>JBOSS_IDENTITY_MEMBERSHIP</relationship-type> <relationship-type>JBOSS_IDENTITY_ROLE</relationship-type> </supported-relationship-types> <supported-identity-object-types> <identity-object-type> <name>USER</name> <relationships/> <credentials> <credential-type>PASSWORD</credential-type> </credentials> <attributes/> <options/> </identity-object-type> </supported-identity-object-types> <options> <option> <name>hibernateSessionFactoryRegistryName</name> <value>hibernateSessionFactory</value> </option> <option> <name>allowNotDefinedIdentityObjectTypes</name> <value>true</value> </option> <option> <name>populateRelationshipTypes</name> <value>true</value> </option> <option> <name>populateIdentityObjectTypes</name> <value>true</value> </option> <option> <name>allowNotDefinedAttributes</name> <value>true</value> </option> <option> <name>isRealmAware</name> <value>true</value> </option> </options> </identity-store> </identity-stores> </stores> </jboss-identity>
Three types of navigation are available to portal users:
These navigations are configured with standard XML syntax in the file; "02portal.war:/WEB-INF/conf/portal/portal-configuration.xml
".
<component>
<key>org.exoplatform.portal.config.UserPortalConfigService</key>
<type>org.exoplatform.portal.config.UserPortalConfigService</type>
<component-plugins>
<component-plugin>
<name>new.portal.config.user.listener</name>
<set-method>initListener</set-method>
<type>org.exoplatform.portal.config.NewPortalConfigListener</type>
<description>this listener init the portal configuration</description>
<init-params>
<value-param>
<name>default.portal</name>
<description>The default portal for checking db is empty or not</description>
<value>classic</value>
</value-param>
<object-param>
<name>portal.configuration</name>
<description>description</description>
<object type="org.exoplatform.portal.config.NewPortalConfig">
<field name="predefinedOwner">
<collection type="java.util.HashSet">
<value><string>classic</string></value>
<value><string>webos</string></value>
</collection>
</field>
<field name="ownerType"><string>portal</string></field>
<field name="templateLocation"><string>war:/conf/portal</string></field>
</object>
</object-param>
<object-param>
<name>group.configuration</name>
<description>description</description>
<object type="org.exoplatform.portal.config.NewPortalConfig">
<field name="predefinedOwner">
<collection type="java.util.HashSet">
<value><string>platform/administrators</string></value>
<value><string>platform/users</string></value>
<value><string>platform/guests</string></value>
<value><string>organization/management/executive-board</string></value>
</collection>
</field>
<field name="ownerType"><string>group</string></field>
<field name="templateLocation"><string>war:/conf/portal</string></field>
</object>
</object-param>
<object-param>
<name>user.configuration</name>
<description>description</description>
<object type="org.exoplatform.portal.config.NewPortalConfig">
<field name="predefinedOwner">
<collection type="java.util.HashSet">
<value><string>root</string></value>
<value><string>john</string></value>
<value><string>mary</string></value>
<value><string>demo</string></value>
</collection>
</field>
<field name="ownerType"><string>user</string></field>
<field name="templateLocation"><string>war:/conf/portal</string></field>
</object>
</object-param>
</init-params>
</component-plugin>
</component-plugins>
This XML file defines, for the three navigations, which sets of portals, groups or users will have XML files inside the apprpriate war
. Those files will be used to create an initial navigation the first time the portal is launched. That information will then be stored in the JCR and is then modifiable from the portal UI.
The portal navigation incorporates the pages that can be accessed when the user is not logged in (if the applicable permissions allow public access). For example; several portal navigations are used when a company has multiple trademarks and each trade has its own website.
The classic portal is configured by four XML files in the 02portal.war:/WEB-INF/conf/portal/portal/classic
directory:
This file describes the layout and portlets that will be shown on all pages. Usually the layout contains the banner, footer, menu and breadcrumbs portlets. GateIn 3.0 is extremely configurable as every area (even the banner and footer) is a portlet.
<?xml version="1.0" encoding="ISO-8859-1"?>
<portal-config>
<portal-name>classic</portal-name>
<locale>en</locale>
<factory-id>office</factory-id>
<access-permissions>Everyone</access-permissions>
<edit-permission>*:/platform/administrators</edit-permission>
<creator>root</creator>
<portal-layout>
<application>
<instance-id>portal#classic:/web/BannerPortlet/banner</instance-id>
<show-info-bar>false</show-info-bar>
</application>
<application>
<instance-id>portal#classic:/web/NavigationPortlet/toolbar</instance-id>
<show-info-bar>false</show-info-bar>
</application>
<application>
<instance-id>portal#classic:/web/BreadcumbsPortlet/breadcumbs</instance-id>
<show-info-bar>false</show-info-bar>
</application>
<page-body> </page-body>
<application>
<instance-id>portal#classic:/web/FooterPortlet/footer</instance-id>
<show-info-bar>false</show-info-bar>
</application>
</portal-layout>
</portal-config>
It is also possible to apply a nested container that can also contain portlets. Row, column or tab containers are then responsible of the layout of their child portlets.
Each application references a portlet using the id portal#{portalName}:/{portletWarName}/{portletName}/{uniqueId}
Use the page-body
tag to define where GateIn 3.0 should render the current page.
The defined classic portal is accessible to "Everyone" (at /portal/public/classic
) but only members of the group /platform/administrators
can edit it.
This file defines all the navigation nodes the portal will have. The syntax is simple, using nested node tags. Each node references a page that is defined in the next XML file.
If the label #{}
node label is used, the internationalization mechanism is activated and the actual label to render is taken from an associated properties file for the current locale.
<?xml version="1.0" encoding="UTF-8"?>
<node-navigation>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<priority>1</priority>
<page-nodes>
<node>
<uri>home</uri>
<name>home</name>
<label>#{portal.classic.home}</label>
<page-reference>portal::classic::homepage</page-reference>
</node>
<node>
<uri>webexplorer</uri>
<name>webexplorer</name>
<label>#{portal.classic.webexplorer}</label>
<page-reference>portal::classic::webexplorer</page-reference>
</node>
</page-nodes>
</node-navigation>
This navigation tree can have multiple views inside portlets (such as the breadcrumbs portlet) that render the current view node, the site map or the menu portlets.
For top nodes, the uri and the name of your navigation nodes must have the same value. For the other nodes the uri is composed like contentmanagement/fileexplorer
where 'contentmanagement
' is the name of the parent node and 'fileexplorer
' is the name of the node ( <name>fileexplorer</name> ).
This XML file structure is very similar to portal.xml
and it can also contain container tags. Each application can decide whether to render the portlet border, the window state icons or the mode.
<?xml version="1.0" encoding="ISO-8859-1"?>
<page-set>
<page>
<page-id>portal::classic::homepage</page-id>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<name>homepage</name>
<title>Home Page</title>
<access-permissions>Everyone</access-permissions>
<edit-permission>*:/platform/administrators</edit-permission>
<application>
<instance-id>portal#classic:/web/HomePagePortlet/homepageportlet</instance-id>
<title>Home Page portlet</title>
<show-info-bar>false</show-info-bar>
<show-application-state>false</show-application-state>
<show-application-mode>false</show-application-mode>
</application>
</page>
<page>
<page-id>portal::classic::webexplorer</page-id>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<name>webexplorer</name>
<title>Web Explorer</title>
<access-permissions>*:/platform/users</access-permissions>
<edit-permission>*:/platform/administrators</edit-permission>
<application>
<instance-id>group#platform/users:/web/BrowserPortlet/WebExplorer</instance-id>
<title>Web Explorer</title>
<show-info-bar>false</show-info-bar>
</application>
</page>
</page-set>
Porlet instances can be associated with portlet-preferences
that override the one defined in portlet.xml
file of the portlet application war
.
<?xml version="1.0" encoding="ISO-8859-1"?>
<portlet-preferences-set>
<portlet-preferences>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<window-id>portal#classic:/web/BannerPortlet/banner</window-id>
<preference>
<name>template</name>
<value>par:/groovy/groovy/webui/component/UIBannerPortlet.gtmpl</value>
<read-only>false</read-only>
</preference>
</portlet-preferences>
<portlet-preferences>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<window-id>portal#classic:/web/NavigationPortlet/toolbar</window-id>
<preference>
<name>useAJAX</name>
<value>true</value>
<read-only>false</read-only>
</preference>
</portlet-preferences>
<portlet-preferences>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<window-id>portal#classic:/web/FooterPortlet/footer</window-id>
<preference>
<name>template</name>
<value>par:/groovy/groovy/webui/component/UIFooterPortlet.gtmpl</value>
<read-only>false</read-only>
</preference>
</portlet-preferences>
<portlet-preferences>
<owner-type>portal</owner-type>
<owner-id>classic</owner-id>
<window-id>portal#classic:/web/GroovyPortlet/groovyportlet</window-id>
<preference>
<name>template</name>
<value>par:/groovy/groovy/webui/component/UIGroovyPortlet.gtmpl</value>
<read-only>false</read-only>
</preference>
</portlet-preferences>
</portlet-preferences-set>
Group navigations are dynamically added to the user navigation at login. This allows users to see all the pages assigned to any groups they belong to in the menu.
The group navigation menu is configured by three XML files (navigation.xml
, pages.xml
and portlet-preferences.xml
). The syntax used in these files is the same as those covered in Section 2.5.2, “Portal Navigation”.
They are located in the portal/WEB-INF/conf/portal/group
directory (For example; /group-name-path/
portal/WEB-INF/conf/portal/group/platform/administrators/
).
The user navigation is the set of nodes and pages that are owned by a user. They are part of the user dashboard.
Three files configure the user navigation (navigation.xml
, pages.xml
and portlet-preferences.xml
). They are located in the directory "portal/WEB-INF/conf/portal/users/{userName}
".
This directory also contains a gadgets.xml
file (which was formerly called widgets.xml
). This file defines the gadgets located in the user workspace.
The user workspace is located at the left hand side of the page and access is restricted to some privileged users, see Section 2.7, “Predefined User Configuration”
<?xml version="1.0" encoding="ISO-8859-1"?>
<widgets>
<owner-type>user</owner-type>
<owner-id>root</owner-id>
<container id="Information">
<name>Information</name>
<description>Information's Description</description>
<application>
<instance-id>user#root:/GateInWidgetWeb/WelcomeWidget/WelcomeWidget1</instance-id>
<application-type>GateInWidget</application-type>
</application>
<application>
<instance-id>user#root:/GateInWidgetWeb/StickerWidget/StickerWidget</instance-id>
<application-type>GateInWidget</application-type>
</application>
<application>
<instance-id>user#root:/GateInWidgetWeb/InfoWidget/InfoWidget1</instance-id>
<application-type>GateInWidget</application-type>
</application>
</container>
<container id="Calendar">
<name>Calendar</name>
<description>Calendar's Description</description>
<application>
<instance-id>user#root:/GateInWidgetWeb/CalendarWidget/CalendarWidget</instance-id>
<application-type>GateInWidget</application-type>
</application>
</container>
</widgets>
If you wish to add a link to a URL outside the portal you you first have to define a "page" which will not be used. Then add the URL to the navigation.
<page> <owner-type>portal</owner-type> <owner-id>website</owner-id> <name>documentation</name> <title>Documentation</title> <access-permissions>Everyone</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> </page>
<node> <uri>http://wiki.exoplatform.com/xwiki/bin/view/Main/WebHome</uri> <name>documentation</name> <label>#{portal.classic.documentation}</label> <page-reference>portal::website::documentation</page-reference> </node>
Currently you cannot modify the URL using the portal interface, you must change it in the configuration files or modify the underlying database table.
Token service is used in authentication.
The token system prevents user account information being sent in clear text mode for inbound requests. This increases authentication security.
Token service allows adminitrators to create, delete, retrieve and clean tokens as required. The service also defines the validity period of any given token. The token becomes invalid once this period has expired, . The life-time definition must be configured.
All token services used in GateIn 3.0's authentication must be a subclass of an abstract class AbstractTokenService. The following example shows how the token-service manipulates its tokens.
public Token getToken(String id) throws PathNotFoundException, RepositoryException;
public Token deleteToken(String id) throws PathNotFoundException, RepositoryException;
public String[] getAllTokens();
public long getNumberTokens() throws Exception;
public String createToken(Credentials credentials) throws IllegalArgumentException,NullPointerException;
public Credentials validateToken(String tokenKey, boolean remove) throws NullPointerException;
Token services configuration includes specifying the validity period time of token in the configuration file. The token service is configured as a portal component.
In the example below, CookieTokenService is a subclass of AbstractTokenService so it has a property which specifies the validity period of the token.
The token service will initiate this validity property by looking for an init-param
named "service.configuration".
This property must have three values.
<component> <key>org.exoplatform.web.security.security.CookieTokenService</key> <type>org.exoplatform.web.security.security.CookieTokenService</type> <init-params> <values-param> <name>tokenTimeout</name> <value>jcr-token</value> <value>7
</value> <value>D
AY</value> </values-param> </init-params> </component>
![]() | Service name |
![]() | Amount of time |
![]() | Unit of time |
In this case, the service's name is "jcr-token" and the token's expiration time is a week.
GateIn 3.0 supports four timing units:
SECOND
MINUTE
HOUR
DAY
To specify the initial Organization configuration, the content of 02portal.war:/WEB-INF/conf/organization/organization-configuration.xml
should be edited. This file uses the portal XML configuration schema. It lists several configuration plugins.
The plugin of type org.exoplatform.services.organization.OrganizationDatabaseInitializer
specifies the list of users, groups and membership types to be created.
The initialization parameter "checkDatabaseAlgorithm
" determines how the creation is triggered.
The value "entry
" means that each user, group and membership listed in the configuration file is checked each time GateIn 3.0 is started. If the entry doesn't exist, it is created. The value "empty
" sets the configuration file to be processed only if the database is empty.
The predefined membership types are specified in the "membershipType
" field of the "OrganizationConfig
" plugin parameter.
<field name="membershipType">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
<field name="type">
<string>member</string>
</field>
<field name="description">
<string>member membership type</string>
</field>
</object>
</value>
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
<field name="type">
<string>owner</string>
</field>
<field name="description">
<string>owner membership type</string>
</field>
</object>
</value>
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$MembershipType">
<field name="type">
<string>validator</string>
</field>
<field name="description">
<string>validator membership type</string>
</field>
</object>
</value>
</collection>
</field>
The predefined groups are specified in the "group" field of the "OrganizationConfig
" plugin parameter.
<field name="group">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$Group">
<field name="name">
<string>portal</string>
</field>
<field name="parentId">
<string></string>
</field>
<field name="type">
<string>hierachy</string>
</field>
<field name="description">
<string>the /portal group</string>
</field>
</object>
</value>
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$Group">
<field name="name">
<string>community</string>
</field>
<field name="parentId">
<string>/portal</string>
</field>
<field name="type">
<string>hierachy</string>
</field>
<field name="description">
<string>the /portal/community group</string>
</field>
</object>
</value>
...
</collection>
</field>
The predefined users are specified in the "membershipType
" field of the "OrganizationConfig
" plugin parameter.
<field name="user">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$User">
<field name="userName"><string>root</string></field>
<field name="password"><string>exo</string></field>
<field name="firstName"><string>root</string></field>
<field name="lastName"><string>root</string></field>
<field name="email"><string>exoadmin@localhost</string></field>
<field name="groups"><string>member:/admin,member:/user,owner:/portal/admin</string></field>
</object>
</value>
<value>
<object type="org.exoplatform.services.organization.OrganizationConfig$User">
<field name="userName"><string>exo</string></field>
<field name="password"><string>exo</string></field>
<field name="firstName"><string>site</string></field>
<field name="lastName"><string>site</string></field>
<field name="email"><string>exo@localhost</string></field>
<field name="groups"><string>member:/user</string></field>
</object>
</value>
...
</collection>
</field>
The plugin of type org.exoplatform.services.organization.impl.NewUserEventListener
specifies which groups should join all newly created users. It notably specifies the groups and memberships to be used. It also specifies a list of users that should be excepted.
<component-plugin>
<name>new.user.event.listener</name>
<set-method>addListenerPlugin</set-method>
<type>org.exoplatform.services.organization.impl.NewUserEventListener</type>
<description>this listener assign group and membership to a new created user</description>
<init-params>
<object-param>
<name>configuration</name>
<description>description</description>
<object type="org.exoplatform.services.organization.impl.NewUserConfig">
<field name="group">
<collection type="java.util.ArrayList">
<value>
<object type="org.exoplatform.services.organization.impl.NewUserConfig$JoinGroup">
<field name="groupId"><string>/user</string></field>
<field name="membership"><string>member</string></field>
</object>
</value>
</collection>
</field>
<field name="ignoredUser">
<collection type="java.util.HashSet">
<value><string>exo</string></value>
<value><string>root</string></value>
<value><string>company</string></value>
<value><string>community</string></value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
The permission configuration for the portal is defined in the file 02portal.war:/WEB-INF/conf/portal/portal-configuration.xml
. The component UserACL is described there along with other portal component configurations.
It defines 5 permissions types:
The super user has all the rights on the platform, this user is referred to as root.
This list defines all groups that will be able to manage the different portals. Members of this group also have the permission to create new portals. The format is "membership:/group/subgroup
".
Defines the membership type of the group managers. The group managers have the permission to create and edit group pages and they can modify the group navigation.
Any anonymous user automatically becomes a member of this group whent they enter the public pages.
Defines the users that have access to the control workspace. In the demo version the control workspace is accessible only to 'root' and 'john'. They can expand/collapse the workspace at the left hand side. The format is "membership:/group/subgroup", An asterisk '' gives permission to all memberships.
<component>
<key>org.exoplatform.portal.config.UserACL</key>
<type>org.exoplatform.portal.config.UserACL</type>
<init-params>
<value-param>
<name>super.user</name>
<description>administrator</description>
<value>root</value>
</value-param>
<value-param>
<name>portal.creator.groups</name>
<description>groups with membership type have permission to manage portal</description>
<value>*:/platform/administrators,*:/organization/management/executive-board</value>
</value-param>
<value-param>
<name>navigation.creator.membership.type</name>
<description>specific membership type have full permission with group navigation</description>
<value>manager</value>
</value-param>
<value-param>
<name>guests.group</name>
<description>guests group</description>
<value>/platform/guests</value>
</value-param>
<value-param>
<name>access.control.workspace</name>
<description>groups with memberships that have the right to access the User Control Workspace</description>
<value>*:/platform/administrators,*:/organization/management/executive-board</value>
</value-param>
</init-params>
</component>
<external-component-plugins>
<target-component>org.exoplatform.portal.config.UserACL</target-component>
<component-plugin>
<name>addPortalACLPlugin</name>
<set-method>addPortalACLPlugin</set-method>
<type>org.exoplatform.portal.config.PortalACLPlugin</type>
<description>setting some permission for portal</description>
<init-params>
<values-param>
<name>access.control.workspace.roles</name>
<value>*:/platform/administrators</value>
<value>*:/organization/management/executive-board</value>
</values-param>
<values-param>
<name>portal.creation.roles</name>
<value>*:/platform/administrators</value>
<value>*:/organization/management/executive-board</value>
</values-param>
</init-params>
</component-plugin>
</external-component-plugins>
Data-injector is a utility to initialize enterprise data for GateIn 3.0. It is packed as a .jar.
OrganizationInitializer is the service that allows creating a large organization with many groups and users. It also creates portal navigation and page(s) for each group and each user.
<configuration> <component> <key>org.exoplatform.portal.initializer.organization.OrganizationInitializer</key> <type>org.exoplatform.portal.initializer.organization.OrganizationInitializer</type> <init-params> <value-param> <name>auto.create.group.page.navigation</name> <description>true or false</description> <value>true</value> </value-param> <value-param> <name>auto.create.user.page.navigation</name> <description>number of pages per user</description> <value>10</value> </value-param> <object-param> <name>organization</name> <description>description</description> <object type="org.exoplatform.portal.initializer.organization.OrganizationConfig"> <field name="groups"> <collection type="java.util.ArrayList"> <value> <object type="org.exoplatform.portal.initializer.organization.OrganizationConfig$GroupsConfig"> <field name="group"> <object type="org.exoplatform.services.organization.OrganizationConfig$Group"> <field name="name"><string>province</string></field> <field name="parentId"><string>/africa/tanzania</string></field> <field name="description"><string>Tanzania's province</string></field> <field name="label"><string>Province</string></field> </object> </field> <field name="from"><string>1</string></field> <field name="to"><string>10</string></field> </object> </value> </collection> </field> <field name="users"> <collection type="java.util.ArrayList"> <value> <object type="org.exoplatform.portal.initializer.organization.OrganizationConfig$UsersConfig"> <field name="user"> <object type="org.exoplatform.services.organization.OrganizationConfig$User"> <field name="userName"><string>user</string></field> <field name="password"><string>GateInPlatform</string></field> <field name="firstName"><string>First-Name</string></field> <field name="lastName"><string>Last-Name</string></field> <field name="email"><string>exo@localhost</string></field> <field name="groups"><string>member:/africa</string></field> </object> </field> <field name="from"><string>0</string></field> <field name="to"><string>9</string></field> </object> </value> </collection> </field> </object> </object-param> </init-params> </component> </configuration>
The name of the group.
The ID of the parent group. If the parent ID is null the group is at the first level. If parent groups do not exist, this ID will be created automatically.
A description of the group.
A label for the group.
This group can be cloned to may copies and each copy is marked with a number.
The user's ID.
The user's password.
last name.
The user's email address.
A list of the groups that user belongs to.
Users can be cloned and each copy is assigned a number. This allows the creation of a range of users that can be put into various groups based on the range value.
The " auto.create.group.page.navigation
" parameter value is true
or false
. If this value is set to true
it automatically creates portal navigations and pages for each group. If false
it does not.
The "auto.create.user.page.navigation
" parameter value is the number of pages that are automatically created for each user.
GateIn 3.0 provides support for skinning the entire portal User Interface (UI) including your own portlets. Skins are designed to help you pack and reuse common graphic resources.
Skins can be switched dynamically at runtime.
When you switch, the whole portal will be repainted and the new styles will be applied to the UI.
The complete skinning of a page can be decomposed into four parts:
CSS styles for html tags (ex div,th,td...) and the portal UI (including the toobar).
CSS styles for each portlet. Used when a portlet want to define it's own CSS.
CSS styles for the surrounding of windows (Which embed either gadgets or portlets).
GateIn 3.0 WebUI components styles are reused among most of the shipped-in portlets.
The main portal skin stylesheet
(/DefaultSkin/skin/Stylesheet.css
)is shown below as
an example:
@import url(/eXoResources/skin/PortletThemes/Stylesheet.css) ; @import url(DefaultSkin/portal/webui/component/UIPortalApplicationSkin.css) ; @import url(DefaultSkin/webui/component/Stylesheet.css) ;
A GateIn 3.0 skin contains css styles for the portal's components but
also shares components that may be reused in portlets. When GateIn 3.0
generates a portal page markup, it inserts stylesheet links in the page's
head
tag.
In the snipped code below you can see two types of links:
<head> ... <link id="CoreSkin" rel="stylesheet" type="text/css" href="/eXoResources/skin/Stylesheet.css" /> <link id="web_
FooterPortlet" rel="stylesheet" type="text/css" href= "/web/skin/portal/webui/component/UIFooterPortlet/DefaultStylesheet.css" /> <link id="web_NavigationPortlet" rel="stylesheet" type="text/css" href= "/web/skin/portal/webui/component/UINavigationPortlet/DefaultStylesheet.css" /> <link id="web_HomePagePortlet" rel="stylesheet" type="text/css" href= "/portal/templates/skin/webui/component/UIHomePagePortlet/DefaultStylesheet.css" /> <link id="web_BannerPortlet" rel="stylesheet" type="text/css" href= "/web/skin/portal/webui/component/UIBannerPortlet/DefaultStylesheet.css" /> ... </head>
![]() | Portal skin stylesheet
( |
![]() | Portlets skins stylesheets (all others). Each portlet within the page may contribute its own style(s) |
Window styles are included with the portal skin
The default skin of portal is called Default. To change this value
add a skin tag in the portal.xml
that defines your
portal:
<portal-config>
<portal-name>classic</portal-name>
<locale>en</locale>
<access-permissions>Everyone</access-permissions>
<edit-permission>*:/platform/administrators</edit-permission>
<skin>MySkin</skin>
<creator>root</creator>
...
The SkinService is an GateIn 3.0 service that manages portal skin, portlet styles and portlet themes (windows borders).
GateIn 3.0 automatically discovers web archives that contains a file descriptor for skins (WEB-INF/gatein-resources.xml).
Because of the Right-To-Left support all CSS files need to be retrieved through a Servlet filter and the web application need to be configured to activate this filter. This is already done for the 01eXoResources.war web application.
<filter>
<filter-name>ResourceRequestFilter</filter-name>
<filter-class>org.exoplatform.portal.application.ResourceRequestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ResourceRequestFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
GateIn 3.0 automatically discovers web archives that contains a file descriptor for skins (WEB-INF/gatein-resources.xml).The full schema can be found in 02portal.war:/WEB-INF/gatein_resources_1-0.xsd
Here is an example:
<gatein-resources> <portal-skin> <skin-name>MySkin</skin-name> <css-path>/skin/myskin.css</css-path> <overwrite>false</overwrite> </portal-skin> </gatein-resources>
Here we defined a skin (MySkin) and the CSS location within the same web archive.
Default portal skin and window styles are defined in 01eXoResources.war:/WEB-INF/gatein-resources.xml and the part dedicated to skinning is defined like:
<gatein-resources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_resources_1_0" xmlns="http://www.ga tein.org/xml/ns/gatein_resources_1_0.xsd"> <portal-skin> <skin-name>Default</skin-name> <css-path>/skin/Stylesheet.css</css-path> </portal-skin> <!-- Simple window style --> <window-style> <style-name>Simple</style-name> <style-theme> <theme-name>SimpleBlue</theme-name> </style-theme> <style-theme> <theme-name>SimpleViolet</theme-name> </style-theme> <style-theme> <theme-name>SimpleOrange</theme-name> </style-theme> <style-theme> <theme-name>SimplePink</theme-name> </style-theme> <style-theme> <theme-name>SimpleGreen</theme-name> </style-theme> </window-style>
When selecting a skin, an overview of the skin is possible. It has to be defined in the current skin used to display the form to change skins.
Put the screeshot of the skin in:
01eXoResources.war:/skin/DefaultSkin/portal/webui/component/customization/UIChangeSkinForm/background
and add the following in
01eXoResources.war:/skin/DefaultSkin/portal/webui/component/customization/UIChangeSkinForm/Stylesheet.css:
.UIChangeSkinForm .UIItemSelector .TemplateContainer .MySkinImage { margin: auto; width: 329px; height:204px; background: url('background/MySkin.jpg') no-repeat top; cursor: pointer ; }
Window styles are the CSS applied to window decoration. When an administrator choose a new application to add on a page he can decide which style of decoration would go around the window if any.
The code below illustrates the inclusion of all CSS resoucres for a
new theme from the example file
GateInResourcesCp060508/skin/MyPortalSkin/PortletThemes/Stylesheet.css
.
In 01eXoResources:/WEB-INF/gatein-resources.xml add the following snippet:
<window-style> <style-name>MyTheme</style-name> <style-theme> <theme-name>MyThemeBlue</theme-name> </style-theme> <style-theme> <theme-name>MyThemeRed</theme-name> </style-theme> </window-style>
Here we created a group of themes called 'MyTheme' and two themes that reference CSS classnames.
Create the CSS file:
/*---- MyTheme ----*/ .MyTheme .WindowBarCenter .WindowPortletInfo { margin-right: 80px; /* orientation=lt */ margin-left: 80px; /* orientation=rt */ } .MyTheme .WindowBarCenter .ControlIcon { float: right;/* orientation=lt */ float: left;/* orientation=rt */ width: 24px; height: 17px; cursor: pointer; background-image: url('background/MyTheme.png'); } .MyTheme .ArrowDownIcon { background-position: center 20px; } .MyTheme .OverArrowDownIcon { background-position: center 116px; } .MyTheme .MinimizedIcon { background-position: center 44px; } .MyTheme .OverMinimizedIcon { background-position: center 140px; } .MyTheme .MaximizedIcon { background-position: center 68px; } .MyTheme .OverMaximizedIcon { background-position: center 164px; } .MyTheme .RestoreIcon { background-position: center 92px; } .MyTheme .OverRestoreIcon { background-position: center 188px; } .MyTheme .NormalIcon { background-position: center 92px; } .MyTheme .OverNormalIcon { background-position: center 188px; } .UIPageDesktop .MyTheme .ResizeArea { float: right;/* orientation=lt */ float: left;/* orientation=rt */ width: 18px; height: 18px; cursor: nw-resize; background: url('background/ResizeArea18x18.gif') no-repeat left top; /* orientation=lt */ background: url('background/ResizeArea18x18-rt.gif') no-repeat right top; /* orientation=rt */ } .MyTheme .Information { height: 18px; line-height: 18px; vertical-align: middle; font-size: 10px; padding-left: 5px;/* orientation=lt */ padding-right: 5px;/* orientation=rt */ margin-right: 18px;/* orientation=lt */ margin-left: 18px;/* orientation=rt */ } .MyTheme .WindowBarCenter .WindowPortletIcon { background-position: left top; /* orientation=lt */ background-position: right top; /* orientation=rt */ padding-left: 20px; /* orientation=lt */ padding-right: 20px; /* orientation=rt */ height: 16px; line-height: 16px; } .MyTheme .WindowBarCenter .PortletName { font-weight: bold; color: #333333; overflow: hidden; white-space: nowrap; width: 100%; } .MyTheme .WindowBarLeft { padding-left: 12px; background-image: url('background/MyTheme.png'); background-repeat: no-repeat; background-position: left -148px; } .MyTheme .WindowBarRight { padding-right: 11px; background-image: url('background/MyTheme.png'); background-repeat: no-repeat; background-position: right -119px; } .MyTheme .WindowBarCenter { background-image: url('background/MyTheme.png'); background-repeat: repeat-x; background-position: left -90px; } .MyTheme .WindowBarCenter .FixHeight { height: 21px; padding-top: 8px; } .MyTheme .MiddleDecoratorLeft { padding-left: 12px; background: url('background/MyTheme.png') repeat-y left; } .MyTheme .MiddleDecoratorRight { padding-right: 11px; background: url('background/MyTheme.png') repeat-y right; } .MyTheme .MiddleDecoratorCenter { background: #ffffff; } .MyTheme .BottomDecoratorLeft { MyTheme: 12px; background-image: url('background/MyTheme.png'); background-repeat: no-repeat; background-position: left -60px; } .MyTheme .BottomDecoratorRight { padding-right: 11px; background-image: url('background/MyTheme.png'); background-repeat: no-repeat; background-position: right -30px; } .MyTheme .BottomDecoratorCenter { background-image: url('background/MyTheme.png'); background-repeat: repeat-x; background-position: left top; } .MyTheme .BottomDecoratorCenter .FixHeight { height: 30px; }
Portlets often require additionnal styles that may not be defined by
the portal skin. GateIn 3.0 allows portlets to define additional
stylesheets for each portlet and will append the corresponding
link
tags to the head
.
The link ID will be of the form
{portletAppName}{PortletName}
. For example:
ContentPortlet
in content.war
, will
give
id="content
ContentPortlet"
To define a new CSS file to include whenever a portlet is added, the following snippet need to be added in gatein-resources.xml
<portlet-skin> <application-name>portletAppName</application-name> <portlet-name>PortletName</portlet-name> <skin-name>Default</skin-name> <css-path>/skin/DefaultStylesheet.css</css-path> </portlet-skin> <portlet-skin> <application-name>portletAppName</application-name> <portlet-name>PortletName</portlet-name> <skin-name>OtherSkin</skin-name> <css-path>/skin/OtherSkinStylesheet.css</css-path> </portlet-skin>
The portal framework will include the CSS file corresponding to the skin in used.
Each portlet is represented by an icon that you can see in the portlet registry. This icon can be changed by adding an image to the directory:
skin/DefaultSkin/portletIcons
.
To be used correctly the icon must be named after the
portlet.icon_name.png
For example,the icon for an account portlet named AccountPortlet would be located at:
skin/DefaultSkin/portletIcons/AccountPortlet.png
By default, CSS files are cached and their imports are merged into a single CSS file at the server side. This reduces the number of HTTP requests from the browser to the server.
The optimization code is quite simple as all the CSS files are
parsed at the server startup time and all the @import and url(...)
references are rewritten to support a single flat file. The result is
stored in a cache directly used from the
ResourceRequestFilter
.
Although the optimization is useful for a production environments,
it may be easier to deactivate this optimization while debugging
stylesheets. To do so, set the java system property
exo.product.developing
to
true
.
For example, the property can passed as a JVM parameter with the
-D
option when running GateIn.
sh $JBOSS_HOME/bin/run.sh -Dexo.product.developing=true
warning("This is option may cause display bugs with certain browsers like Internet Explorer")
It is recommended that users have some experience with CSS before studying GateIn 3.0 CSS.
GateIn 3.0 relies heavily on CSS to create the layout and effects for the UI. Some common techniques for customizing GateIn 3.0's CSS are explained below.
The decorator is a pattern to create a contour or a curve around an area. In order to achieve this effect you need to create 9 cells. The BODY is the central area that you want to decorate. The other 8 cells are distributed around the BODY cell. You can use the width, height and background image properties to achieve any decoration effect that you want.
----------------------------------------------------------------------- | | | | | TopLeft | TopCenter | TopRight | | | | | ----------------------------------------------------------------------- | | | | | CenterLeft | BODY | CenterRight | | | | | ----------------------------------------------------------------------- | | | | | BottomLeft | BottomCenter | BottomRight | | | | | ----------------------------------------------------------------------- <div class="Parent"> <div class="TopLeft"> <div class="TopRight"> <div class="TopCenter"><span></span></div> </div> </div> <div class="CenterLeft"> <div class="CenterRight"> <div class="CenterCenter">BODY</div> </div> </div> <div class="BottomLeft"> <div class="BottomRight"> <div class="BottomCenter"><span></span></div> </div> <div> </div>
Left margin left pattern is a technique to create 2 blocks side by side. The left block will have a fixed size and the right block will take the rest of the available space. When the user resizes the browser the added or removed space will be taken from the right block.
| | | | | | | |<--- fixed width --->| | will expand to right ----> | | | | | | | | | | ---- <div class="Parent"> <div style="float: left; width: 100px"> </div> <div style="margin-left: 105px;"> <div> <div style="clear: left"><span></span></div> </div>
Managing Javascript scripts in an application like GateIn 3.0 is a critical part of the configuration work. Configuring the scripts correctly will result in a faster response time from the portal.
Every portlet can have its own javscript code but in many cases it is more convenient to reuse some existing shared libraries. For that reason, GateIn 3.0 has a mechanism to easily register the libraries that will be loaded when the first page will be rendered.
To do so, every WAR deployed in GateIn 3.0 can register the js
files with the groovy script "WEB-INF/conf/script/groovy/JavascriptScript.groovy
".
The example file below is found in the 01eXoResources.war
JavascriptService.addJavascript("eXo", "/javascript/eXo.js", ServletContext); /* Animation Javascripts */ JavascriptService.addJavascript("eXo.animation.ImplodeExplode", "/javascript/eXo/animation/ImplodeExplode.js", ServletContext); /* Application descriptor */ JavascriptService.addJavascript("eXo.application.ApplicationDescriptor", "/javascript/eXo/application/ApplicationDescriptor.js", ServletContext); /* CORE Javascripts */ JavascriptService.addJavascript("eXo.core.Utils", "/javascript/eXo/core/Util.js", ServletContext); JavascriptService.addJavascript("eXo.core.DOMUtil", "/javascript/eXo/core/DOMUtil.js", ServletContext); JavascriptService.addJavascript("eXo.core.Browser", "/javascript/eXo/core/Browser.js", ServletContext); JavascriptService.addJavascript("eXo.core.MouseEventManager", "/javascript/eXo/core/MouseEventManager.js", ServletContext); JavascriptService.addJavascript("eXo.core.UIMaskLayer", "/javascript/eXo/core/UIMaskLayer.js", ServletContext); JavascriptService.addJavascript("eXo.core.Skin", "/javascript/eXo/core/Skin.js", ServletContext); JavascriptService.addJavascript("eXo.core.DragDrop", "/javascript/eXo/core/DragDrop.js", ServletContext); JavascriptService.addJavascript("eXo.core.TemplateEngine", "/javascript/eXo/core/TemplateEngine.js", ServletContext); /* Widget Javascripts */ JavascriptService.addJavascript("eXo.widget.UIWidget", "/javascript/eXo/widget/UIWidget.js", ServletContext); JavascriptService.addJavascript("eXo.widget.UIAddWidget", "/javascript/eXo/widget/UIAddWidget.js", ServletContext); JavascriptService.addJavascript("eXo.widget.UIExoWidget", "/javascript/eXo/widget/UIExoWidget.js", ServletContext); /* Desktop Javascripts */ JavascriptService.addJavascript("eXo.desktop.UIDockbar", "/javascript/eXo/desktop/UIDockbar.js", ServletContext); JavascriptService.addJavascript("eXo.desktop.UIDesktop", "/javascript/eXo/desktop/UIDesktop.js", ServletContext); /* WebUI Javascripts */ JavascriptService.addJavascript("eXo.webui.UIItemSelector", "/javascript/eXo/webui/UIItemSelector.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIForm", "/javascript/eXo/webui/UIForm.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIPopup", "/javascript/eXo/webui/UIPopup.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIPopupSelectCategory", "/javascript/eXo/webui/UIPopupSelectCategory.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIPopupWindow", "/javascript/eXo/webui/UIPopupWindow.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIVerticalScroller", "/javascript/eXo/webui/UIVerticalScroller.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIHorizontalTabs", "/javascript/eXo/webui/UIHorizontalTabs.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIPopupMenu", "/javascript/eXo/webui/UIPopupMenu.js", ServletContext); JavascriptService.addJavascript("eXo.webui.UIDropDownControl", "/javascript/eXo/webui/UIDropDownControl.js", ServletContext); /* Portal Javascripts */ JavascriptService.addJavascript("eXo.portal.PortalHttpRequest", "/javascript/eXo/portal/PortalHttpRequest.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIPortal", "/javascript/eXo/portal/UIPortal.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIWorkspace", "/javascript/eXo/portal/UIWorkspace.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIPortalControl", "/javascript/eXo/portal/UIPortalControl.js", ServletContext); JavascriptService.addJavascript("eXo.portal.PortalDragDrop", "/javascript/eXo/portal/PortalDragDrop.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIPortalNavigation", "/javascript/eXo/portal/UIPortalNavigation.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIMaskWorkspace", "/javascript/eXo/portal/UIMaskWorkspace.js", ServletContext); JavascriptService.addJavascript("eXo.portal.UIExoStartMenu", "/javascript/eXo/portal/UIExoStartMenu.js", ServletContext); /* Desktop Javascripts 2 */ JavascriptService.addJavascript("eXo.desktop.UIWindow", "/javascript/eXo/desktop/UIWindow.js", ServletContext);
Note that even registered dedicated javascripts will be merged into a single merged.js
file when the server loads. This reduces the number of HTTP calls as seen in the home page source code:
<script type="text/javascript" src="/portal/javascript/merged.js"></script>
Although this optimization is useful for a production environment, it may be easier to deactivate this optimization while debugging javascript problems.
To do this, set the java system property exo.product.developing
to true
.
To see or use the merged file set this property to false
.
The property can be passed as a JVM parameter with the -D
option in your GateIn.sh
or GateIn.bat
startup script.
Every javascript file is referenced with a module name of "eXo.core.DragDrop
" which acts as a namespace. Inside the associated files, global javascript functions are used following the same namespace convention:
eXo.core.DragDrop = new DragDrop() ;
It is also possible to use the eXo.require() javascript method to lazy load and evaluate some javascript code. This is quite useful from the portlet or widget applications that will use this javascript only once. Otherwise, if the library is reusable in several places it is better to reference it in the groovy file.
The following parameters are available controll the Dashboard configuration (when in edit mode):
if empty, everyone share the same dashboard and can edit it
if set to CURRENTUSER , every user has his own dashboard
if set to a username, everyone will see the dashboard of this person
if set to 1, only the owner of the dashboard can edit it
if set to 0, everyone can edit it
GateIn 3.0, provides some form of Single Sign On (SSO
) as an integration and aggregation platform.
When logging into the portal users gain access to many systems through portlets using a single identity. In many cases, however, the portal infrastructure must be integrated with other SSO enabled systems. There are many different Identity Management solutions available. In most cases each SSO framework provides a unique way to plug into a Java EE application.
In this tutorial, the SSO server is installed in a Tomcat installation. Tomcat can be obtained from http://tomcat.apache.org.
All the packages required for setup can be found in a zip file located at: http://repository.jboss.org/maven2/org/gatein/sso/sso-packaging
. In this document we will call $SSO_HOME the directory where the file is extracted.
Users are advised to not run any portal extensions that could override the data when manipulating the gatein.ear
file directly.
Remove $JBOSS_HOME/server/default/deploy/gatein-sample-extension.ear
and $JBOSS_HOME/server/default/deploy/gatein-sample-portal.ear
which are packaged by default with GateIn 3.0.
This Single Sign On plugin enables seamless integration between GateIn 3.0 and the CAS Single Sign On Framework. Details about CAS can be found here.
The integration consists of two parts; the first part consists of installing or configuring a CAS server, the second part consists of setting up the portal to use the CAS server.
First, set up the server to authenticate against the portal login module. In this example the CAS server will be installed on Tomcat.
CAS can be downloaded from http://www.jasig.org/cas/download.
Extract the downloaded file into a suitable location. This location will be referred to as $CAS_HOME
in the following example.
To configure the web archive as desired, it is simpler to directly modify the sources.
To change the authentication handler to use the portal authentication handler:
The CAS Server Plugin makes secure authentication callbacks to a RESTful service installed on the remote GateIn server in order to authenticate a user.
In order for the plugin to function correctly, it needs to be properly configured to connect to this service. This configuration is done via the cas.war/WEB-INF/deployerConfigContext.xml
file.
Open CAS_HOME/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml
Replace:
<!-- | Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, | AuthenticationHandlers actually authenticate credentials. Here e declare the AuthenticationHandlers that | authenticate the Principals that the CredentialsToPrincipalResolvers identified. CAS will try these handlers in turn | until it finds one that both supports the Credentials presented and succeeds in authenticating. +--> <property name="authenticationHandlers"> <list> <!-- | This is the authentication handler that authenticates services by means of callback via SSL, thereby validating | a server side SSL certificate. +--> <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" /> <!-- | This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS | into production. The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials | where the username equals the password. You will need to replace this with an AuthenticationHandler that implements your | local authentication strategy. You might accomplish this by coding a new such handler and declaring | edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules. +--> <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> </list> </property>
With the following (Make sure to set the host, port and context with the values corresponding to your portal). Also available in GATEIN_SSO/cas/plugin/WEB-INF/deployerConfigContext.xml
.
<!-- | Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, | AuthenticationHandlers actually authenticate credentials. Here we declare the AuthenticationHandlers that | authenticate the Principals that the CredentialsToPrincipalResolvers identified. CAS will try these handlers in turn | until it finds one that both supports the Credentials presented and succeeds in authenticating. +--> <property name="authenticationHandlers"> <list> <!-- | This is the authentication handler that authenticates services by means of callback via SSL, thereby validating | a server side SSL certificate. +--> <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" /> <!-- | This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS | into production. The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials | where the username equals the password. You will need to replace this with an AuthenticationHandler that implements your | local authentication strategy. You might accomplish this by coding a new such handler and declaring | edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules. +--> <!-- Integrates with the Gatein Authentication Service to perform authentication --> <!-- | Note: Modify the Plugin Configuration based on the actual information of a GateIn instance. | The instance can be anywhere on the internet...Not necessarily on localhost where CAS is running +--> <bean class="org.gatein.sso.cas.plugin.AuthenticationPlugin"> <property name="gateInHost"><value>localhost</value></property> <property name="gateInPort"><value>8080</value></property> <property name="gateInContext"><value>portal</value></property> </bean> </list> </property>
Copy GATEIN_SSO/cas/plugin/WEB-INF/lib/sso-cas-plugin-<VERSION>.jar
and GATEIN_SSO/cas/plugin/WEB-INF/lib/commons-httpclient-<VERSION>.jar
into the CAS_HOME/cas-server-webapp/src/main/webapp/WEB-INF/lib
created directory.
Get an installation of Tomcat and extract it into a suitable location (which will be called TOMCAT_HOME
for these instructions).
Change the default port to avoid a conflict with the default GateIn 3.0 (for testing purposes). Edit TOMCAT_HOME/conf/server.xml
and replace the 8080 port to 8888.
If GateIn 3.0 is running with Tomcat on the same machine the port 8005 should be changed to something else to avoid port conflicts.
Go to CAS_HOME/cas-server-webapp
and execute the command:
mvn install
Copy CAS_HOME/cas-server-webapp/target/cas.war
into TOMCAT_HOME/webapps
.
Tomcat should start and be accessible at http://localhost:8888/cas. Note that at this stage login won't be available.
Copy all libraries from GATEIN_SSO/cas/gatein.ear/lib
into JBOSS_HOME/server/default/deploy/gatein.ear/lib
(Or in Tomcat, into $GATEIN_HOME/lib
)
In JBoss AS, edit gatein.ear/META-INF/gatein-jboss-beans.xml
and uncomment this section:
<authentication> <login-module code="org.gatein.sso.agent.login.SSOLoginModule" flag="required"> </login-module> <login-module code="org.exoplatform.services.security.j2ee.JbossLoginModule" flag="required"> <module-option name="portalContainerName">portal</module-option> <module-option name="realmName">gatein-domain</module-option> </login-module> </authentication>
In Tomcat, edit GATEIN_HOME/conf/jaas.conf
and uncomment this section:
org.gatein.sso.agent.login.SSOLoginModule required org.exoplatform.services.security.j2ee.JbossLoginModule required portalContainerName=portal realmName=gatein-domain
The installation can be tested at this point:
Access GateIn 3.0 (if the CAS server using Tomcat is still running) by going to http://localhost:8888/cas.
Login with the username root
and the password gtn
(or any account created through the portal).
To utilize the Central Authentication Service, GateIn 3.0 needs to redirect all user authentication to the CAS server.
Information about where the CAS is hosted must be properly configured within the GateIn 3.0 instance. The required configuration is done by modifying three files:
In the gatein.ear/02portal.war/groovy/portal/webui/UILoginForm.gtmpl
file replace the javascript code at the end with:
<script> <%=uicomponent.event("Close");%> window.location = 'http://localhost:8888/cas/login?service=http://localhost:8080/portal/private/classic'; </script>
Replace the contents of the gatein.ear/02portal.war/login/jsp/login.jsp
file with:
<html> <head> <script type="text/javascript"> window.location = 'http://localhost:8888/cas/login?service=http://localhost:8080/portal/private/classic'; </script> </head> <body> </body> </html>
Replace the InitiateLoginServlet
declaration in gatein.ear/02portal.war/WEB-INF/web.xml
with:
<servlet> <servlet-name>InitiateLoginServlet</servlet-name> <servlet-class>org.gatein.sso.agent.GenericSSOAgent</servlet-class> <init-param> <param-name>ssoServerUrl</param-name> <param-value>http://localhost:8888/cas</param-value> </init-param> </servlet>
Once these changes have been made, all links to the user authentication pages will redirect to the CAS centralized authentication form.
This Single Sign On plugin enables seamless integration between GateIn 3.0 and the JOSSO Single Sign On Framework. Details about OpenSSO can be found here.
Setting up this integration happens in two distinct actions. The first part is installing or configuring a JOSSO server and the second involves setting up the portal to use the JOSSO server.
This section details setting up the JOSSO server to authenticate against the GateIn 3.0 login module.
In this example the JOSSO server will be installed on Tomcat.
JOSSO can be downloaded from http://sourceforge.net/projects/josso/files/. Use the package that embeds Apache Tomcat.
Once downloaded, extract the package into what will be called JOSSO_HOME
in this example.
Copy the files from GATEIN_SSO/josso/plugin
into the Tomcat directory (JOSSO_HOME
).
This action should replace or add the following files to the JOSSO_HOME/webapps/josso/WEB-INF/lib
directory:
JOSSO_HOME/lib/josso-gateway-config.xml
JOSSO_HOME/lib/josso-gateway-gatein-stores.xml
and
JOSSO_HOME/webapps/josso/WEB-INF/classes/gatein.properties
Edit TOMCAT_HOME/conf/server.xml
and replace the 8080 port to 8888 to change the default Tomcat port and avoid a conflict with the default GateIn 3.0 port (for testing purposes).
If GateIn 3.0 is being on a machine with Tomcat, other ports will need to be changed to avoid conflicts.
Tomcat should now start and allow access to http://localhost:8888/josso/signon/login.do but at this stage login will not be available.
Copy the library files from GATEIN_SS)/josso/gatein.ear/lib
into gatein.ear/lib
(Or into GATEIN_HOME/lib
if GateIn 3.0 is running in Tomcat)
In JBoss AS, edit gatein.ear/META-INF/gatein-jboss-beans.xml
and uncomment this section:
<authentication> <login-module code="org.gatein.sso.agent.login.SSOLoginModule" flag="required"> </login-module> <login-module code="org.exoplatform.services.security.j2ee.JbossLoginModule" flag="required"> <module-option name="portalContainerName">portal</module-option> <module-option name="realmName">gatein-domain</module-option> </login-module> </authentication>
In Tomcat, edit GATEIN_HOME/conf/jaas.conf
and uncomment this section:
org.gatein.sso.agent.login.SSOLoginModule required org.exoplatform.services.security.j2ee.JbossLoginModule requiredtm portalContainerName=portal realmName=gatein-domain
The installation can be tested at this point.
Start GateIn 3.0 (assuming that the JOSSO server using Tomcat is running) by going to http://localhost:8888/josso/signon/login.do.
Login with the username root
and the password gtn
or any account created through the portal.
The next part of the process is to redirect all user authentication to the JOSSO server.
Information about where the JOSSO server is hosted must be properly configured within the GateIn 3.0 instance. The required configuration is done by modifying four files:
Replace the javascript at the bottom gatein.ear/02portal.war/groovy/portal/webui/UILoginForm.gtmpl
with:
<script> <%=uicomponent.event("Close");%> window.location = 'http://localhost:8888/josso/signon/login.do?josso_back_to=http://localhost:8080/portal/private/classic'; </script>
Replace the entire contents of gatein.ear/02portal.war/login/jsp/login.jsp
with:
<html> <head> <script type="text/javascript"> window.location = 'http://localhost:8888/josso/signon/login.do?josso_back_to=http://localhost:8080/portal/private/classic'; </script> </head> <body> </body> </html>
Replace the InitiateLoginServlet
declaration in gatein.ear/02portal.war/WEB-INF/web.xml
with:
<servlet> <servlet-name>InitiateLoginServlet</servlet-name> <servlet-class>org.gatein.sso.agent.GenericSSOAgent</servlet-class> <init-param> <param-name>ssoServerUrl</param-name> <param-value>http://localhost:8888/cas</param-value> </init-param> </servlet>
Remove the PortalLoginController
servlet declaration and mapping in gatein.ear/02portal.war/WEB-INF/web.xml
From now on, all links redirecting to the user authentication pages will redirect to the JOSSO centralized authentication form.
This Single Sign On plugin enables seamless integration between GateIn 3.0 and the OpenSSO Single Sign On Framework. Details about OpenSSO can be found here.
Setting up this integration happens in two distinct actions. The first part is installing or configuring an OpenSSO server and the second involves setting up the portal to use the OpenSSO server.
This section details setting up the OpenSSO server to authenticate against the Enterprise Portal Platform login module.
In this example the JOSSO server will be installed on Tomcat.
OpenSSO can be downloaded from https://opensso.dev.java.net/public/use/index.html.
Once downloaded, extract the package into a suitable location. This location will be referred to as OPENSSO_HOME
in this example.
To configure the web server as desired, it is simpler to directly modify the sources.
The first step is to add the GateIn 3.0 Authentication Plugin:
The plugin makes secure authentication callbacks to a RESTful service installed on the remote GateIn 3.0 server in order to authenticate a user.
In order for the plugin to function correctly, it needs to be properly configured to connect to this service. This configuration is done via the opensso.war/config/auth/default/AuthenticationPlugin.xml
file.
Obtain a copy of Tomcat and extract it into a suitable location (this location will be referred to as TOMCAT_HOME
in this example).
Change the default port to avoid a conflict with the default GateIn 3.0 port (for testing purposes). Do this by editing TOMCAT_HOME/conf/server.xml
and replacing the 8080 port to 8888.
If GateIn 3.0 is running on the same machine as Tomcat, the port 8005 will also need to be changed to avoid port conflicts.
Ensure the TOMCAT_HOME/webapps/opensso/config/auth/default/AuthenticationPlugin.xml
file looks like this:
<?xml version='1.0' encoding="UTF-8"?> <!DOCTYPE ModuleProperties PUBLIC "=//iPlanet//Authentication Module Properties XML Interface 1.0 DTD//EN" "jar://com/sun/identity/authentication/Auth_Module_Properties.dtd"> <ModuleProperties moduleName="AuthenticationPlugin" version="1.0" > <Callbacks length="2" order="1" timeout="60" header="GateIn OpenSSO Login" > <NameCallback> <Prompt> Username </Prompt> </NameCallback> <PasswordCallback echoPassword="false" > <Prompt> Password </Prompt> </PasswordCallback> </Callbacks> </ModuleProperties>
Copy GATEIN_SSO/opensso/plugin/WEB-INF/lib/sso-opensso-plugin-<VERSION>.jar
, GATEIN_SSO/opensso/plugin/WEB-INF/lib/commons-httpclient-<VERSION>.jar
, and GATEIN_SSO/opensso/plugin/WEB-INF/lib/commons-logging-<VERSION>.jar
into the Tomcat directory at TOMCAT_HOME/webapps/opensso/WEB-INF/lib
.
Copy GATEIN_SSO/opensso/plugin/WEB-INF/classes/gatein.properties
into TOMCAT_HOME/webapps/opensso/WEB-INF/classes
Tomcat should start and be able to access http://localhost:8888/opensso/UI/Login?realm=gatein. Login will not be available at this point.
Copy all libraries from GATEIN_SSO/opensso/gatein.ear/lib
into JBOSS_HOME/server/default/deploy/gatein.ear/lib
(Or, in Tomcat, into GATEIN_HOME/lib
)
In JBoss AS, edit gatein.ear/META-INF/gatein-jboss-beans.xml and uncomment this section
<authentication> <login-module code="org.gatein.sso.agent.login.SSOLoginModule" flag="required"> </login-module> <login-module code="org.exoplatform.services.security.j2ee.JbossLoginModule" flag="required"> <module-option name="portalContainerName">portal</module-option> <module-option name="realmName">gatein-domain</module-option> </login-module> </authentication>
If you are running GateIn in Tomcat, edit $GATEIN_HOME/conf/jaas.conf and uncomment this section
org.gatein.sso.agent.login.SSOLoginModule required org.exoplatform.services.security.j2ee.JbossLoginModule required portalContainerName=portal realmName=gatein-domain
At this point the installation can be tested:
Access GateIn 3.0 by going to http://localhost:8888/opensso/UI/Login?realm=gatein (assuming that the OpenSSO server using Tomcat is still running).
Login with the username root
and the password gtn
or any account created through the portal.
The next part of the process is to redirect all user authentication to the OpenSSO server.
Information about where the OpenSSO server is hosted must be properly configured within the Enterprise Portal Platform instance. The required configuration is done by modifying three files:
Replace the javascript at the bottom of gatein.ear/02portal.war/groovy/portal/webui/UILoginForm.gtmpl
with:
<script> <%=uicomponent.event("Close");%> window.location = 'http://localhost:8888/opensso/UI/Login?realm=gatein&goto=http://localhost:8080/portal/private/classic'; </script>
Replace the contents of gatein.ear/02portal.war/login/jsp/login.jsp
with:
<html> <head> <script type="text/javascript"> window.location = 'http://localhost:8888/opensso/UI/Login?realm=gatein&goto=http://localhost:8080/portal/private/classic'; </script> </head> <body> </body> </html>
Replace the InitiateLoginServlet
declaration in gatein.ear/02portal.war/WEB-INF/web.xml
with:
<servlet> <servlet-name>InitiateLoginServlet</servlet-name> <servlet-class>org.gatein.sso.agent.GenericSSOAgent</servlet-class> <init-param> <param-name>ssoServerUrl</param-name> <param-value>http://localhost:8888/opensso</param-value> </init-param> <init-param> <param-name>ssoCookieName</param-name> <param-value>iPlanetDirectoryPro</param-value> </init-param> </servlet>
From now on, all links redirecting to the user authentication pages will redirect to the OpenSSO centralized authentication form.
GateIn 3.0 is built on top of a kernel, and a set of services that exist in two scopes. First scope is represented by RootContainer - it contains services that exist independently of any portal, and can be accessed by all portals.
Second scope is portal-private in the form of PortalContainer. For each configured portal, an instance of PortalContainer is created. This scope contains services that have portal specific configuration, and services which should not be shared by multiple portals.
RootContainer and PortalContainer classes are part of the same class hierarchy - they both inherit from ExoContainer, and they also inherit methods for looking up registered services.
Whenever a specific service is looked up through PortalContainer, and service is not available, the lookup is delegated further up to RootContainer. We can therefore have default instance of a certain component in RootContainer, and portal specific instances in some or all PortalContainers, that override the default instance.
Whenever your portal application has to be integrated more closely with GateIn services, the way to do it is by looking up these services through PortalContainer. Be careful though - only officially documented services should be accessed this way, and used according to documentation, as most of the services are an implementation detail of GateIn, and subject to change without notice.
GateIn Kernel uses dependency injection to create services based on configuration.xml configuration files. The location of the configuration files determines if services are placed into RootContainer scope, or into PortalContainer scope. All configuration.xml files located at conf/configuration.xml in the classpath (any directory, or any jar in the classpath) will have their services configured at RootContainer scope. All configuration.xml files located at conf/portal/configuration.xml in the classpath will have their services configured at PortalContainer scope. Additionally, portal extensions can contain configuration in WEB-INF/conf/configuration.xml, and will also have their services configured at PortalContainer scope.
A service component is defined in configuration.xml by using <component> element.
There is only one required information when defining a service - the service implementation class, specified using <type>
Every component has a <key> that identifies it. If not explicitly set, a key defaults to the value of <type>. If key can be loaded as a class, a Class object is used as a key, otherwise a String is used.
The usual approach is to specify an interface as a key.
<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd
http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
<component>
<key>org.exoplatform.services.database.HibernateService</key>
<type>org.exoplatform.services.database.impl.HibernateServiceImpl</type>
...
</component>
</configuration>
GateIn Kernel supports non-component objects that can be configured, instantiated, and injected into registered components, using method calls. The mechanism is called 'plugins', and allows portal extensions to add additional configurations to core services.
External plugin is defined by using <external-component-plugins> wrapper element which contains one or more <component-plugin> definitions. <external-component-plugins> uses <target-component> to specify a target service component that will receive injected objects.
Every <component-plugin> defines an implementation type, and a method on target component to use for injection (<set-method>).
A plugin implementation class has to implement org.exoplatform.container.component. ComponentPlugin interface.
In the following example PortalContainerDefinitionPlugin implements ComponentPlugin:
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd
http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
<external-component-plugins>
<target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component>
<component-plugin>
<!-- The name of the plugin -->
<name>Add PortalContainer Definitions</name>
<!-- The name of the method to call on the PortalContainerConfig
in order to register the PortalContainerDefinitions -->
<set-method>registerPlugin</set-method>
<!-- The fully qualified name of the PortalContainerDefinitionPlugin -->
<type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type>
...
</component-plugin>
</external-component-plugins>
</configuration>
It is possible to break configuration.xml file into many smaller files, that are then included into a 'master' configuration file. The included files are complete configuration xml documents by themselves - they are not fragments of text.
An example configuration.xml that 'outsources' its content into several files:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd
http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
<import>war:/conf/sample-ext/jcr/jcr-configuration.xml</import>
<import>war:/conf/sample-ext/portal/portal-configuration.xml</import>
</configuration>
We see a special URL being used to reference another configuration file. URL schema 'war:' means, that the path that follows is resolved relative to current PortalContainer's servlet context resource path, starting at WEB-INF as a root.
Also, thanks to extension mechanism, the servlet context used for resource loading is a unified servlet context (as explaned in a later section).
To have include path resolved relative to current classpath (context classloader), use 'jar:' URL schema.
Configuration files may contain a special variable reference ${container.name.suffix}. This variable resolves to the name of the current portal container, prefixed by underscore (_). This facilitates reuse of configuration files in situations where portal specific unique names need to be assigned to some resources (i.e. JNDI names, Database / DataSource names, JCR repository names, etc ...).
This variable is only defined when there is a current PortalContainer available - only for PortalContainer scoped services.
A good example for this is HibernateService:
<?xml version="1.0" encoding="ISO-8859-1"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd
http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
<component>
<key>org.exoplatform.services.database.HibernateService</key>
<jmx-name>database:type=HibernateService</jmx-name>
<type>org.exoplatform.services.database.impl.HibernateServiceImpl</type>
<init-params>
<properties-param>
<name>hibernate.properties</name>
<description>Default Hibernate Service</description>
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.cglib.use_reflection_optimizer" value="true" />
<property name="hibernate.connection.url"
value="jdbc:hsqldb:file:../temp/data/exodb${container.name.suffix}" />
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />
<property name="hibernate.connection.autocommit" value="true" />
<property name="hibernate.connection.username" value="sa" />
<property name="hibernate.connection.password" value="" />
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="1800" />
<property name="hibernate.c3p0.max_statements" value="50" />
</properties-param>
</init-params>
</component>
</configuration>
A portal is defined by several attributes.
First, there is a portal name, which is always equal to URL context to which the current portal is bound.
Second, there is a REST context name, which is used for REST access to portal application - every portal has exactly one (unique) REST context name.
Then, there is a realm name which is the name of security realm used for authentication when users log into the portal.
Finally, there is a list of Dependencies - other web applications, whose resources are visible to current portal (via extension mechanism described later), and are searched in the specified order.
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd
http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.xsd">
<external-component-plugins>
<!-- The full qualified name of the PortalContainerConfig -->
<target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component>
<component-plugin>
<!-- The name of the plugin -->
<name>Add PortalContainer Definitions</name>
<!-- The name of the method to call on the PortalContainerConfig
in order to register the PortalContainerDefinitions -->
<set-method>registerPlugin</set-method>
<!-- The full qualified name of the PortalContainerDefinitionPlugin -->
<type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type>
<init-params>
<object-param>
<name>portal</name>
<object type="org.exoplatform.container.definition.PortalContainerDefinition">
<!-- The name of the portal container -->
<field name="name"><string>portal</string></field>
<!-- The name of the context name of the rest web application -->
<field name="restContextName"><string>rest</string></field>
<!-- The name of the realm -->
<field name="realmName"><string>exo-domain</string></field>
<!-- All the dependencies of the portal container ordered by loading priority -->
<field name="dependencies">
<collection type="java.util.ArrayList">
<value>
<string>eXoResources</string>
</value>
<value>
<string>portal</string>
</value>
<value>
<string>dashboard</string>
</value>
<value>
<string>exoadmin</string>
</value>
<value>
<string>eXoGadgets</string>
</value>
<value>
<string>eXoGadgetServer</string>
</value>
<value>
<string>rest</string>
</value>
<value>
<string>web</string>
</value>
<value>
<string>wsrp-producer</string>
</value>
<!-- The sample-ext has been added at the end of the dependency list
in order to have the highest priority -->
<value>
<string>sample-ext</string>
</value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component-plugin>
</external-component-plugins>
</configuration>
Every portal is represented by PortalContainer instance, which contains:
associated ExoContainerContext, which contains information about the portal
unified servlet context, for web-archive-relative resource loading
unified classloader, for classpath based resource loading
methods for retrieving services
Unified servlet context, and unified classloader are part of the extension mechanism (explained in next section), and provide standard API (ServletContext, ClassLoader) with specific resource loading behavior - visibility into associated web application archives, configured with Dependencies property of PortalContainerDefinition. Resources from other web applications are queried in the order specified by Dependencies. The later entries in the list override the previous ones.
Extension mechanism is a functionality that makes it possible to override portal resources in an almost plug-and-play fashion - just drop in a .war archive with the resources, and configure its position on the portal's classpath. This way any customizations of the portal don't have to involve unpacking and repacking the original portal .war archives. Instead, you create your own .war archive with changed resources, that override the resources in the original archive.
A web archive packaged in a way to be used through extension mechanism is called portal extension.
There are two steps necessary to create a portal extension.
First, declare PortalConfigOwner servlet context listener in web.xml of your web application.
An example of a portal extension called sample-ext:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE web-app PUBLIC -//Sun Microsystems, Inc.//DTD Web Application 2.3//EN
http://java.sun.com/dtd/web-app_2_3.dtd>
<web-app>
<display-name>sample-ext</display-name>
<listener>
<listener-class>org.exoplatform.container.web.PortalContainerConfigOwner</listener-class>
</listener>
...
</web-app>
Then, add the servlet context name of this web application in proper place in the list of Dependencies of the PortalContainerDefinition of all the portal containers that you want to have access to its resources.
After this step your web archive will be on portal's unified classpath, and unified servlet context resource path. The later in the Dependencies list your application is, the higher priority it has when resources are loaded by portal.
It is possible to run several independent portal containers - each bound to a different URL context - within the same JVM instance. This kind of setup is very efficient from administration and resource consumption aspect. The most elegant way to reuse configuration for different coexisting portals is by way of extension mechanism - by inheriting resources and configuration from existing web archives, and just adding extra resources to it, and overriding those that need to be changed by including modified copies.
In order for a portal application to correctly function when deployed in multiple portals, the application may have to dynamically query the information about the current portal container. The application should not make any assumptions about the name, and other information of the current portal, as there are now multiple different portals in play.
At any point during request processing, or lifecycle event processing, your application can retrieve this information through org.exoplatform.container. ExoContainerContext. Sometimes your application needs to make sure that the proper PortalContainer - the source of ExoContainerContext - is associated with the current call.
If you ship servlets or servlet filters as part of your portal application, and if you need to access portal specific resources at any time during the processing of the servlet or filter request, then you need to make sure the servlet/filter is associated with the current container.
The proper way to do that is to make your servlet extend org.exoplatform.container.web. AbstractHttpServlet class. This will not only properly initialize current PortalContainer for you, but will also set the current thread's context classloader to one that looks for resources in associated web applications in the order specified by Dependencies configuration (as explained in Extension mechanism section).
Similarly for filters, make sure your filter class extends org.exoplatform.container.web. AbstractFilter. Both AbstractHttpServlet, and AbstractFilter have a method getContainer(), which returns the current PortalContainer. If your servlet handles the requests by implementing a service() method, you need to rename that method to match the following signature:
/**
* Use this method instead of Servlet.service()
*/
protected void onService(ExoContainer container, HttpServletRequest req,
HttpServletResponse res) throws ServletException, IOException;
You may also need to access portal information within your HttpSessionListener. Again, make sure to extend the provided abstract class - org.exoplatform.container.web. AbstractHttpSessionListener. Also, modify your method signitures as follows:
/**
* Use this method instead of HttpSessionListener.sessionCreated()
*/
protected void onSessionCreated(ExoContainer container, HttpSessionEvent event);
/**
* Use this method instead of HttpSessionListener.sessionDestroyed()
*/
protected void onSessionDestroyed(ExoContainer container, HttpSessionEvent event);
There is another method you have to implement in this case:
/**
* Method should return true if unified servlet context,
* and unified classloader should be made available
*/
protected boolean requirePortalEnvironment();
If this method returns true, current thread's context classloader is set up according to Dependencies configuration, and availability of the associated web applications. If it returns false, the standard application separation rules are used for resource loading (effectively turning off the extension mechanism). This method exists on AbstractHttpServlet and AbstractFilter as well, where there is a default implementation that automatically returns true, when it detects there is a current PortalContainer present, otherwise it returns false.
We still have to explain how to properly perform ServletContextListener based initialization, when you need access to current PortalContainer.
GateIn has no direct control over the deployment of application archives (.war, .ear files) - it is the application server that performs the deployment. For extension mechanism to work properly, the applications, associated with the portal via Dependencies configuration, have to be deployed before the portal, that depends on them, is initialized. On the other hand, these applications may require an already initialized PortalContainer to properly initialize themselves - we have a recursive dependency problem. To resolve this problem, a mechanism of initialization tasks, and task queues, was put in place. Web applications that depend on current PortalContainer for their initialization have to avoid performing their initialization directly in some ServletContextListener executed during their deployment (before any PortalContainer was initialized). Instead, a web application should package its initialization logic into an init task of appropriate type, and only use ServletContextListener to insert the init task instance into the proper init tasks queue.
An example of this is Gadgets application which registers Google gadgets with the current PortalContainer:
public class GadgetRegister implements ServletContextListener
{
public void contextInitialized(ServletContextEvent event)
{
// Create a new post-init task
final PortalContainerPostInitTask task = new PortalContainerPostInitTask() {
public void execute(ServletContext context, PortalContainer portalContainer)
{
try
{
SourceStorage sourceStorage =
(SourceStorage) pcontainer.getComponentInstanceOfType(SourceStorage.class);
...
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Initialization failed: ", e);
}
}
};
// Add post-init task for execution on all the portal containers
// that depend on the given ServletContext according to
// PortalContainerDefinitions (via Dependencies configuration)
PortalContainer.addInitTask(event.getServletContext(), task);
}
}
The above example uses PortalContainerPostInitTask, which gets executed after the portal container has been initialized. In some situations you may want to execute initialization after portal container was instantiated, but before it was initialized - use PortalContainerPreInitTask in that case. Or, you may want to execute initialization after all the post-init tasks have been executed - use PortalContainerPostCreateTask in that case.
One more area that may need your attention are LoginModules. If you use custom LoginModules, that require current ExoContainer, make sure they extend org.exoplatform.services.security.jaas.AbstractLoginModule for proper initialization. AbstractLoginModule also takes care of the basic configuration - it recognizes two initialization options - portalContainerName, and realmName whose values you can access via protected fields of the same name.
This chapter describes the portal lifecycle from the application server start to its stop as well as how requests are handled.
A portal instance is simply a web application deployed as a WAR in an application server. Portlets are also part of an enhanced WAR called a portlet application.
GateIn 3.0 doesn't require any particular setup for your portlet in most common scenarios and the web.xml
file can remain without any GateIn 3.0 specific configuration.
During deployment, GateIn 3.0 will automatically and transparently inject a servlet into the portlet application to be able to interact with it. This feature is dependent on the underlying servlet container but will work out of the box on the proposed bundles.
The servlet is the main entry point for incoming requests, it also includes some init code when the portal is launched. This servlet (org.gatein.wci.command.CommandServlet
) is automatically added during deployment and mapped to /tomcatgateinservlet
.
This is equivalent to adding the following into web.xml
.
As the servlet is already configured this example is for information only.
<servlet>
<servlet-name>TomcatGateInServlet</servlet-name>
<servlet-class>org.gatein.wci.command.CommandServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TomcatGateInServlet</servlet-name>
<url-pattern>/tomcatgateinservlet</url-pattern>
</servlet-mapping>
It is possible to filter on the CommandServlet by filtering the URL pattern used by the Servlet mapping.
The example below would create a servlet filter that calculates the time of execution of a portlet request.
The filter class:
package org.example;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class MyFilter implements javax.servlet.Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException
{
long beforeTime = System.currentTimeMillis();
chain.doFilter(request, response);
long afterTime = System.currentTimeMillis();
System.out.println("Time to execute the portlet request (in ms): " + (afterTime - beforeTime));
}
public void init(FilterConfig config) throws ServletException
{
}
public void destroy()
{
}
}
The Java EE web application configuration file (web.xml
) of the portlet on which we want to know the time to serve a portlet request. As mentioned above nothing specific to GateIn 3.0 needs to be included, only the URL pattern to set has to be known.
<?xml version="1.0"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.5">
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>org.example.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/tomcatgateinservlet</url-pattern>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
</web-app>
It is important to set INCLUDE
as dispatcher as the portal will always hit the CommandServlet through a request dispatcher. Without this, the filter will not be triggered, unless direct access to a resource (such as an image).
The text orientation depends on the current locale setting. The orientation is a Java 5 enum that provides a set of functionalities:
LT, // Western Europe RT, // Middle East (Arabic, Hebrew) TL, // Japanese, Chinese, Korean TR; // Mongolian public boolean isLT() { ... } public boolean isRT() { ... } public boolean isTL() { ... } public boolean isTR() { ... }
The object defining the Orientation for the current request is the UIPortalApplication
. However it should be accessed at runtime using the RequestContext
that delegates to the UIPortalApplication
.
In the case of a PortalRequestContext
it is a direct delegate as the PortalRequestContext
has a reference to the current UIPortalApplication
.
In the case of a different context such as the PortletRequestContext
, it delegates to the parent context given the fact that the root RequestContext
is always a PortalRequestContext
.
Orientation is defined by implicit variables in the groovy binding context:
The current orientation as an Orientation
The value of orientation.isLT()
The value of orientation.isRT()
The string 'ltr' if the orientation is LT or the string 'rtl' if the orientation is RT.
The skin service handles stylesheet rewriting to accommodate the orientation. It works by appending -lt or -rt to the stylesheet name.
For instance: /web/skin/portal/webui/component/UIFooterPortlet/DefaultStylesheet-rt.css
will return the same stylesheet as /web/skin/portal/webui/component/UIFooterPortlet/DefaultStylesheet.css
but processed for the RT orientation. The -lt
suffix is optional.
Stylesheet authors can annotate their stylesheet to create content that depends on the orientation.
In the example we need to use the orientation to modify the float attribute that will make the horizontal tabs either float on left or on right:
float: left; /* orientation=lt */ float: right; /* orientation=rt */ font-weight: bold; text-align: center; white-space: nowrap;
The LT produced output will be:
float: left; /* orientation=lt */ font-weight: bold; text-align: center; white-space: nowrap;
The RT produced output will be:
float: right; /* orientation=rt */ font-weight: bold; text-align: center; white-space: nowrap;
In this example we need to modify the padding according to the orientation:
color: white; line-height: 24px; padding: 0px 5px 0px 0px; /* orientation=lt */ padding: 0px 0px 0px 5px; /* >orientation=rt */
The LT produced output will be:
color: white; line-height: 24px; padding: 0px 5px 0px 0px; /* orientation=lt */
The RT produced output will be:
color: white; line-height: 24px; padding: 0px 0px 0px 5px; /* orientation=rt */
Sometimes it is necessary to create an RT version of an image that will be used from a template or from a stylesheet. However symmetric images can be automatically generated avoiding the necessity to create a mirrored version of an image and furthermore avoiding maintenance cost.
The web resource filter uses the same naming pattern as the skin service. When an image ends with the -rt suffix the portal will attempt to locate the original image and create a mirror of it.
For instance: requesting the image /GateInResources/skin/DefaultSkin/webui/component/UITabSystem/UITabs/background/NormalTabStyle-rt.gif
returns a mirror of the image /GateInResources/skin/DefaultSkin/webui/component/UITabSystem/UITabs/background/NormalTabStyle.gif
.
It is important to consider whether the image to be mirrored is symmetrical as this will impact it's final appearance.
Here is an example combining stylesheet and images:
line-height: 24px; background: url('background/NavigationTab.gif') no-repeat right top; /* orientation=lt */ background: url('background/NavigationTab-rt.gif') no-repeat left top; /* orientation=rt */ padding-right: 2px; /* orientation=lt */ padding-left: 2px; /* orientation=rt */
GateIn 3.0 is fully configurable for internationalization, however users should have a general knowledge of Internationalization in Java products before attempting these configurations.
Sun Java hosts a comprehensive guide to internationalizing java products at http://java.sun.com/docs/books/tutorial/i18n/TOC.html.
All GateIn 3.0 applications contain property files for various languages. They are packaged with the portlets applications in a WEB-INF/classes/locale/
directory.
These files are located in the classes
folder of the WEB-INF directory, so as to be loaded by the ClassLoader.
All resource files are in a subfolder named locale
.
For instance; the translations for the NavigationPortlet are located in web.war/WEB-INF/classes/locale/portlet/portal
NavigationPortlet_de.properties NavigationPortlet_en.properties NavigationPortlet_es.properties NavigationPortlet_fr.properties NavigationPortlet_nl.properties NavigationPortlet_ru.properties NavigationPortlet_uk.properties NavigationPortlet_ar.xml
Inside those file are typical key=value
Java EE properties. For example the French one:
javax.portlet.title=Portlet Navigation
There are also properties files in the portal itself. They form the portal resource bundle.
From a portlet you can then access translations from the portlet itself or shared at the portal level, both are aggregated when you need them.
It is also possible to use a proprietary XML format to define translations. This is a more convenient way to translate a document for some languages such as Japanese, Arabic or Russian. Property files have te be ASCII encoded, while the XML file can define its encoding. As a result it's easier for a human being to read (and fix) a translation in XML instead of having to decode and encode the property file.
For more information refer to: Section 5.4, “XML Resources Bundles”
Various languages are available in the portal package. The configuration below will define which languages are shown in the "Change Language" section and made available to users.
The 02portal.war:/WEB-INF/conf/common/common-configuration.xml
file of your installation contains the following section:
<component>
<key>org.exoplatform.services.resources.LocaleConfigService</key>
<type>org.exoplatform.services.resources.impl.LocaleConfigServiceImpl</type>
<init-params>
<value-param>
<name>locale.config.file</name>
<value>war:/conf/common/locales-config.xml</value>
</value-param>
</init-params>
</component>
This configuration points to the locale configuration file.
The locale configuration file (02portal.war:/WEB-INF/conf/common/locales-config.xml
) contains the following code:
<?xml version="1.0" encoding="UTF-8"?> <locales-config> <locale-config> <locale>en</locale> <output-en
coding>UTF-8</output-encoding> <input-enc
oding>UTF-8</input-encoding> <descripti
on>Default configuration for english locale</description> </locale-config> <locale-config> <locale>fr</locale> <output-encoding>UTF-8</output-encoding> <input-encoding>UTF-8</input-encoding> <description>Default configuration for the french locale</description> </locale-config> <locale-config> <locale>ar</locale> <output-encoding>UTF-8</output-encoding> <input-encoding>UTF-8</input-encoding> <description>Default configuration for the arabic locale</description> <orientati
on>rt</orientation> </locale-config> </locales-config>
![]() | locale The locale has to be defined such as defined here http://ftp.ics.uci.edu-pub-ietf-http-related-iso639.txt. In this example "ar" is Arabic. |
![]() | output-encoding deals with character encoding. It is recommended that UTF-8 be used. |
![]() | input-encoding In the java implementation, the encoding parameters will be used for the request response stream. The input-encoding parameter will be used for request setCharacterEncoding(..). |
![]() | description Description for the language |
![]() | description The default orientation of text and images is Left-To-Right. GateIn 3.0 supports Right-To-Left orientation. Modifying text orientation is explained in Section 5.2, “RTL (Right To Left) Framework”. |
The resource bundle service is configured in: 02portal.war:/WEB-INF/conf/common/common-configuration.xml
:
<component> <key>org.exoplatform.services.resources.ResourceBundleService</key> <type>org.exoplatform.services.resources.impl.SimpleResourceBundleService</type> <init-params> <values-param> <name>classpath.resources</name> <description>The resources that start with the following package name should be load from file system</description> <value>locale.portlet</value> </values-param> <values-param> <name>in
it.resources</name> <description>Initiate the following resources during the first launch</description> <value>locale.portal.expression</value> <value>locale.portal.services</value> <value>locale.portal.webui</value> <value>locale.portal.custom</value> <value>locale.navigation.portal.classic</value> <value>locale.navigation.group.platform.administrators</value> <value>locale.navigation.group.platform.users</value> <value>locale.navigation.group.platform.guests</value> <value>locale.navigation.group.organization.management.executive-board</value> </values-param> <values-param> <name>po
rtal.resource.names</name> <description>The properties files of the portal , those file will be merged into one ResoruceBundle properties </description> <value>locale.portal.expression</value> <value>locale.portal.services</value> <value>locale.portal.webui</value> <value>locale.portal.custom</value> </values-param> </init-params> </component>
![]() | classpath.resources are discussed in a later section. |
![]() | init.resources TODO |
![]() | portal.resource.names Defines all resources that belong to the Portal Resource Bundle. These resources are merged to a single resource bundle which is accessible from anywhere in GateIn 3.0. All these keys are located in the same bundle, which is separated from the navigation resource bundles. |
There is a resource bundle for each navigation. A navigation can exist for user, groups, and portal.
The previous example shows bundle definitions for the navigation of the classic portal and of four different groups. Each of these resource bundles occupies a different sphere, they are independent of each other and they are not included in the portal.resource.names
parameter.
The properties for a group must be in the WEB-INF/classes/locale/navigation/group/
folder. /WEB-INF/classes/locale/navigation/group/organization/management/executive-board_en.properties
, for example.
The folder and file names must correspond to the group hierarchy. The group name "executive-board
" is followed by the iso 639 code.
For each language defined in LocalesConfig
must have a resource file defined. If the name of a group is changed the name of the folder and/or files of the correspondent navigation resource bundles must also be changed.
Content of executive-board_en.properties
:
organization.title=Organization organization.newstaff=New Staff organization.management=Management
This resource bundle is only accessible for the navigation of the organization.management.executive-board
group.
Portlets are independent applications and deliver their own resource files.
All shipped portlet resources are located in the locale/portlet subfolder. The ResourceBundleService parameter classpath.resources defines this subfolder.
Procedure 5.1. Example
To add a Spanish translation to the GadgetPortlet
.
Create the file GadgetPortlet_es.properties
in: WEB-INF/classes/locale/portlet/gadget/GadgetPortlet
.
In portlet.xml
, add Spanish
as a supported-locale ('es' is the 2 letters code for Spanish), the resource-bundle is already declared and is the same for all languages :
<supported-locale>en</supported-locale>
<supported-locale>es</supported-locale>
<resource-bundle>locale.portlet.gadget.GadgetPortlet</resource-bundle>
See the portlet specification for more details about portlet internationalization.
The portlet specifications defines three standard keys: Title, Short Title and Keywords. Keywords is formatted as a comma-separated list of tags.
javax.portlet.title=Breadcrumbs Portlet javax.portlet.short-title=Breadcrumbs javax.portlet.keywords=Breadcrumbs, Breadcrumb
These keys are used to display a property in the user language.
The following access method enables translation to the preferred language for the connected user:
Groovy Template
TODO
Java
WebuiRequestContext context = WebuiRequestContext.getCurrentInstance() ; ResourceBundle res = context.getApplicationResourceBundle() ; String translatedString = res.getString(key);
When translating an application it can sometimes be difficult to find the right key for a given property.
Execute the portal in debug mode and select, from the available languages, select the special language; Magic locale.
This feature translates a key to the same key value.
For example, the translated value for the key "organization.title
" is simply the value "organization.title
". Selecting that language allows use of the portal and its applications with all the keys visible. This makes it easier to find out the correct key for a given label in the portal page.
Resource bundles are usually stored in property files. However, as property files are plain files, issues with the encoding of the file may arise. The XML resource bundle format has been developed to provide an alternative to property files.
The XML format declares the encoding of the file. This avoids use of the native2ascii program which can interfere with encoding.
Property files generally use ISO 8859-1 character encoding which does not cover the full unicode charset. As a result, languages such as Arabic would not be natively supported.
Tooling for XML files is better supported than the tooling for Java property files and thus the XML editor copes well with the file encoding.
The XML format is very simple and has been developed based on the DRY (Don't Repeat Yourself) principle. Usually resource bundle keys are hierarchically defined and we can leverage the hierarchic nature of the XML for that purpose. Here is an example of turning a property file into an XML resource bundle file:
UIAccountForm.tab.label.AccountInputSet = ... UIAccountForm.tab.label.UIUserProfileInputSet = ... UIAccountForm.label.Profile = ... UIAccountForm.label.HomeInfo= ... UIAccountForm.label.BusinessInfo= ... UIAccountForm.label.password= ... UIAccountForm.label.Confirmpassword= ... UIAccountForm.label.email= ... UIAccountForm.action.Reset= ...
<?xml version="1.0" encoding="UTF-8"?>
<bundle>
<UIAccountForm>
<tab>
<label>
<AccountInputSet>...</AccountInputSet>
<UIUserProfileInputSet>...</UIUserProfileInputSet>
</label>
</tab>
<label>
<Profile>...</Profile>
<HomeInfo>...</HomeInfo>
<BusinessInfo>...</BusinessInfo>
<password>...</password>
<Confirmpassword>...</Confirmpassword>
<email>...</email>
</label>
<action>
<Reset>...</Reset>
</action>
</UIAccountForm>
</bundle>
In order to be loaded by the portal at runtime (actually the resource bundle service), the name of the file must be the same as a property file and it must use the .xml suffix.
For example; for the Account Portlet to be displayed in Arabic, the resource bundle would be AccountPortlet_ar.xml rather than AccountPortlet_ar.properties.
The traditional way of rendering a portal page is static, using a templat (usually a jsp page) for each layout (2 columns, 3 columns and so on). This method depends on the integrator or developers as each new layout will need a custom development.
GateIn 3.0 has a dynamic rendering method which creates a tree of nested UI containers that contain portlets (as shown in the image below).
Each container is responsible for rendering its children. In the below example the main container renders its children in several rows while the nested container displays them as columns.
By manipulating the tree using a WYSIWYG editor new containers can be added, their child rendering can be defined and new portlets can be nested.
As most portals use the static layout mechanism, portlets can only be dragged from one static location, such as a column, to another static location.
In GateIn 3.0, it is possible to drag UI containers (and their portlets) and drop them in containers that are higher or lower in the Portal component tree. This feature is unique to GateIn 3.0
JavaScript Inter Application Communication is designed to allow applications within a page to exchange data. This library is made for broadcasting messages on topic.
It is based on 3 functions:
Subscribe.
Publish.
Unsubscribe.
A subscription to a topic will receive any subtopic messages. For example; An application subscribed to "/eXo/application
" will receive messages sent on the "/eXo/application/map
" topic. A message sent on "/eXo
", however, would not be received.
The Inter Application Communication library is found in 01eXoResources.war:/javascript/eXo/core/Topic.js
/** * publish is used to publish an event to the other subscribers to the given channels * @param {Object} senderId is a string that identify the sender * @param {String} topic is the topic that the message will be published * @param {Object} message is the message that's going to be delivered to the subscribers to the topic */ Topic.prototype.publish = function(/*Object*/ senderId, /*String*/ topicName, /*Object*/ message ) { ... } /** * isSubscribed is used to check if a function receive the events from a topic * @param {String} topic The topic. * @param {Function} func is the name of the function of obj to call when a message is received on the topic */ Topic.prototype.isSubscribed = function(/*String*/ topic, /*Function*/ func) { ... } /** * subscribe is used to subscribe a callback to a topic * @param {String} topic is the topic that will be listened * @param {Function} func is the name of the function of obj to call when a message is received on the topic * * func is a function that take a Object in parameter. the event received have this format: * {senderId:senderId, message:message, topic: topic} * */ Topic.prototype.subscribe = function(/*String*/ topic, /*Function*/ func) { ... } /** * unsubscribe is used to unsubscribe a callback to a topic * @param {String} topic is the topic * @param {Object} id is the id of the listener we want to unsubscribe */ Topic.prototype.unsubscribe = function(/*String*/ topic, /*Object*/ id) { ... } Topic.prototype.initCometdBridge = function() { ... }
The three messaging functions require particular objects and definitions in their syntax:
The subscribe
function is used to subscribe a callback to a topic. It uses the following parameters:
The topic that will be listened for.
The name of the object function to call when a message is received on the topic. It has to be a function that takes an Object parameter. The event received will have this format:
{ senderId:senderId, message:message, topic: topic }
The publish
function is used to publish an event to the other subscribered applications through the given channels. Its parameters are:
This is a string that identifies the sender.
The topic that the message will be published.
This is the message body to be delivered to the subscribers to the topic.
The unsubscribe
function is used to unsubscribe a callback to a topic. The required parameters are:
The topic that will is to be unsubscribed from.
This is the context object.
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %> <portlet:defineObjects/> <div> <p> Received messages: <div id="received_<portlet:namespace/>"> </div> </p> <p> Send message: <input type="text" id="msg_<portlet:namespace/>"/> <a href="#" onclick="send_<portlet:namespace/>();">send</a> </p> </div> <script type="text/javascript"> Function.prototype.bind = function(object) { var method = this; return function() { method.apply(object, arguments); } } function send_<portlet:namespace/>() { var msg = document.getElementById("msg_<portlet:namespace/>").value; eXo.core.Topic.publish("<portlet:namespace/>", "/demo", msg); } function Listener_<portlet:namespace/>(){ } Listener_<portlet:namespace/>.prototype.receiveMsg = function(event) { document.getElementById("received_<portlet:namespace/>").innerHTML = document.getElementById("received_<portlet:namespace/>").innerHTML + "<br />* " + event.senderId + ": " + event.message; } function init_<portlet:namespace/>() { var listener_<portlet:namespace/> = new Listener_<portlet:namespace/>(); eXo.core.Topic.subscribe("/demo", listener_<portlet:namespace/>.receiveMsg.bind(listener_<portlet:namespace/>)); } init_<portlet:namespace/>(); </script>
The service is defined by the class: org.exoplatform.upload.UploadService
;
This can be configured with the following xml code:
<component>
<type>org.exoplatform.upload.UploadService</type>
<init-params>
<value-param>
<name>upload.limit.size</name>
<description>Maximum size of the file to upload in MB</description>
<value>10</value>
</value-param>
</init-params>
</component>
This code allows for a default upload size limit for the service to be configured. The value unit is in MegaBytes.
This limit will be used by default by all applications if no application-specific limit is set. Setting a different limit for applications is discussed in a later section.
If the value is set at 0
the upload size is unlimited.
Procedure 5.2. How to use the upload component
Create an object type org.exoplatform.webui.form.UIFormUploadInput
.
Two constructors are available for this:
public UIFormUploadInput(String name, String bindingExpression)
or:
public UIFormUploadInput(String name, String bindingExpression, int limit)
This is an example using the second form :
PortletRequestContext pcontext = (PortletRequestContext)WebuiRequestContext.getCurrentInstance();
PortletPreferences portletPref = pcontext.getRequest().getPreferences();
int limitMB = Integer.parseInt(portletPref.getValue("uploadFileSizeLimitMB", "").trim());
UIFormUploadInput uiInput = new UIFormUploadInput("upload", "upload", limitMB);
To obtain the limit from the xml
configuration, this piece of code can be added to the either portlet.xml
or portlet-preferences.xml
:
<preference>
<name>uploadFileSizeLimitMB</name>
<value>30</value>
<read-only>false</read-only>
</preference>
Again, a 0
value means an unlimited upload size, and the value unit is set in MegaBytes.
Use the getUploadDataAsStream()
method to get the uploaded data:
UIFormUploadInput input = (UIFormUploadInput)uiForm.getUIInput("upload");
InputStream inputStream = input.getUploadDataAsStream();
...
jcrData.setValue(inputStream);
The upload service stores a temporary file on the filesystem during the upload process. When the upload is finished, the service must be cleaned in order to:
Delete the temporary file.
Delete the classes used for the upload.
Use theremoveUpload()
method defined in the upload service to purge the file:
UploadService uploadService = uiForm.getApplicationComponent(UploadService.class) ;
UIFormUploadInput uiChild = uiForm.getChild(UIFormUploadInput.class) ;
uploadService.removeUpload(uiChild.getUploadId()) ;
Ensure the file is saved before the service is cleaned.
The loading mask layer is deployed after an ajax-call. Its purpose is to block the GUI in order to prevent further user actions until the the ajax-request has been completed.
However, the mask layer may need to be deactivated in instances where the portal requires user instructions before previous instructions have been carried out.
Procedure 5.3. How to deactivate the ajax-loading mask
Generate a script to make an asynchronous ajax-call. Use the uicomponent.doAsync()
method rather than the uicomponent.event()
method.
For example:
<a href="<%=uicomponent.doAsync(action, beanId, params)%>" alt="">Asynchronous</a>
The doAsync()
method automatically adds the following new parameter into the parameters list; asyncparam = new Parameter(AJAX ASYNC,"true"); (AJAX ASYNC == "ajax async")
This request is asynchronous and the ajax-loading mask will not deployed.
An asynchronous request can still be made using the uicomponent.event()
. When using this method, however, the asyncparam must be added manually.
The GUI will be blocked to ensure a user can only request one action at a time and while the request seems to be synchronous, all ajax requests are, in fact always asynchronous. For further information refer to Section 5.8.2, “Synchronous issue”.
Most web browsers support ajax requests in two modes: Synchronous and Asynchronous. This mode is specified with a boolean bAsync
parameter.
var bAsync = false; // Synchronous request.open(instance.method, instance.url, bAsync);
However, in order to work with browsers that do not support Synchronous requests, bAsync
is set to always be true (Ajax request will always be asynchronous).
// Asynchronous request request.open(instance.method, instance.url, true);
The following code retrieves the details for a logged-in user:
// Alternative context: WebuiRequestContext context = WebuiRequestContext.getCurrentInstance() ;
PortalRequestContext context = PortalRequestContext.getCurrentInstance() ;
// Get the id of the user logged
String userId = context.getRemoteUser();
// Request the information from OrganizationService:
OrganizationService orgService = getApplicationComponent(OrganizationService.class) ;
if (userId != null)
{
User user = orgService.getUserHandler().findUserByName(userId) ;
if (user != null)
{
String firstName = user.getFirstName();
String lastName = user.getLastName();
String email = user.getEmail();
}
}
Below are two alternatives for retrieving the Organization Service:
OrganizationService service = (OrganizationService)
ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(OrganizationService.class);
OrganizationService service = (OrganizationService)
PortalContainer.getInstance().getComponentInstanceOfType(OrganizationService.class);
Ajax calls are easily managed in GateIn 3.0's framework. Lines can be added to the relevant template file and java class. A simple Ajax update of a component does not require any JavaScript code to be written.
GateIn 3.0 portlets can use specific ActionListener
s to receive and process Ajax calls. To set a portlet up to recieve Ajax calls, follow this procedure:
Create an inner static class named with the following convention: action name
followed by ActionListener
.
Example : ParentClass
is the class being wrtitten in.
static public class SaveActionListener extends EventListener<ParentClass>
Declare this listener in the configuration of the portlet:
listeners = ParentClass.SaveActionListener.class
Add the correct annotation ComponentConfig
, EventConfig
, etc.
For example; the configuration below is for UIAccountForm
:
...
@ComponentConfig(
lifecycle = UIFormLifecycle.class,
template = "system:/groovy/webui/form/UIFormTabPane.gtmpl",
initParams = {
@ParamConfig(
name = "AccountTemplateConfigOption",
value = "app:/WEB-INF/conf/uiconf/account/webui/component/model/AccountTemplateConfigOption.groovy"
),
@ParamConfig(
name = "help.UIAccountFormQuickHelp",
value = "app:/WEB-INF/conf/uiconf/account/webui/component/model/UIAccountFormQuickHelp.xhtml"
)
},
events = {
@EventConfig(listeners = UIAccountForm.SaveActionListener.class ),
@EventConfig(listeners = UIAccountForm.ResetActionListener.class, phase = Phase.DECODE),
@EventConfig(listeners = UIAccountForm.SearchUserActionListener.class, phase = Phase.DECODE)
}
)
...
Create an execute
method inside this class:
public void execute(Event<ParentClass> event) throws Exception
This method is called every time the listener gets an event from the client. Hence you can process this event in it, using the even attribute. Use it to get parameters from a form in your client, to modify the status of your portlet, etc.,
Possible ways to use the event attribute :
String value = event.getRequestContext().getRequestParameter("name"); // to get a value from a form
ParentClass parent = event.getSource(); // to get the parent object (the portlet that threw and caugth the event)
UIMyComponent portal = parent.getAncestorOfType(UIMyComponent.class); // to get any node in the hierarchy of UIComponents
If your action has to update an element on your client's interface, you must call addUIComponentToUpdateByAjax() at the end of the execute
method:
event.getRequestContext().addUIComponentToUpdateByAjax(uicomponent);
The target component must be provided as parameter (the component that will be updated). We will come back on this later.
You must create one inner action listener class for each Ajax call you want to handle on this portlet. All these classes must be declared in the configuration annotations of the main class, otherwise you will get an error.
Done. Your portlet is ready to accept Ajax calls from your client.
Once the server is configured to receive Ajax calls, you must configure the client interface to make these calls.
Add the following code to the groovy template file associated with the appropriate portlet class ( ParentClass
here):
uicomponent.event("YourOperation");
YourOperation
is the same as in the ActionListener
class (save in our example above).
The event function will create an url starting with javascript:
. Make sure this code can be executed in the environment where the portal is running.
If your operation must update the content of a component, make sure that the target component is well rendered. The following is recommended:
uicomponent.renderChild(UITargetComponent.class) ;
The uicomponent
must be of the UITargetComponent
type as the component class;
event.getRequestContext().addUIComponentToUpdateByAjax(uicomponent) ;
will be updated when UITargetComponent
is called.
If this component is not rendered by default, set its rendered
attribute (in the constructor of the portlet) to false when the portlet loads:
mycomponent.setRendered(false);
All javascript in GateIn 3.0 is managed by the file 02eXoresources:javascript/eXo/portal/PortalHttpRequest.js
.
In this class, there are four functions/classes:
and 6 functions:
This is the main entry method for every Ajax calls to the portal It is simply a dispatcher method that completes some init
fields before calling the doRequest()
method.
This method is called instead of an HTTP POST
when, in an AJAX case, some manipulations are needed. Once the content of the form is placed into a string object, the call is delegated to the doRequest()
method
The doRequest()
method takes incoming request from ajaxGet
and ajaxPost
calls. The second argument is the URL to target on the server The third argument is the query string object which is created out of a form element, this value is null except when there is a POST request.
An AjaxRequest
object is instantiated. It holds the reference to the XHR method.
An HttpResponseHandler
object is instantiated and its methods (ajaxResponse
, ajaxLoading
, ajaxTimeout
) are associated with the one from the AjaxRequest
and will be called by the XHR during the process method.
Cancels the current request.
Allows to create and execute a sync or async GET request.
A simple javascript redirection with window.location.href
that are the entry points of these classes. These functions need not be called explicitly, as the template file and the portlet class manage everything.
This class doesn't contain any methods. On creation, it retrieves the response elements from the xml returned by Ajax, and stores them in the corresponding attributes :
The component ID.
The component title.
The mode the component runs in. The options are: View, Edit, Help or Config.
The processing state of the component. The options are: Decode, Render.
The updated data to put in the component.
The javascript code to update the component.
An array containing the containers to update with this script.
Attributes can be accessed by calling them from the PortletResponse
instance.
This is an array of PortletResponse
s (portletResponses
) and two other attributes:
Data to update.
Javascript code to update.
By far the most important class of this file. It makes the XMLHttpRequest
object easier to use by wrapping it with functions and attributes. You can find further documentation on this class here.
This class provides methods to handle the Ajax response.
Used to execute javascript code.
Updates html
components.
This function is called when the timeout of the ajax call exceeds its set limit.
This function creates a PortalResponse
object from the data in the Ajax request.
This function shows the loading pop-up and mask layer.
{PortalResponse} | |--->{PortletResponse} | |--->{PortletResponse} | |-->{portletId} | |-->{portletTitle} | |-->{portletMode} | |-->{portletState} | | | |-->{Data} | | | | | |--->{BlockToUpdate} | | | |-->{blockId} | | | |-->{data} | | | | | |--->{BlockToUpdate} | |--->{Script} | |--->{Data} | | | |--->{BlockToUpdate} | | |-->{blockId} | | |-->{data} | | | |--->{BlockToUpdate} |--->{Script}
If there are several actions that need to appear in a pop-up, the following technique can be used to manage the different pop-up windows:
Create a UIPopupAction
in the main portlet class:
addChild(UIPopupAction.class, null, null);
Render this action in your template file:
uicomponent.renderChild(UIPopupAction.class) ;
(This creates an empty container (pop-up) that will receive the new content by Ajax)
Put this component in your action listener class and update its content:
UIPopupAction uiPopupAction = uiMainPortlet.getChild(UIPopupAction.class) ; uiPopupAction.activate(UIReferencesList.class, 600) ;
UIReferenceList
is the component that will appear in the pop-up. It does not have to declared in the main portlet class.
The activate method creates the component and its rendering in the pop-up window.
Allow this component to be updated by Ajax:
event.getRequestContext().addUIComponentToUpdateByAjax(uiPopupAction) ;
Add an action listener class (and repeat the steps above with the appropriate component type) for each component required to appear in a pop-up window .
In addition to the content in this chapter, refer also to Section 6.1, “AJAX Framework” in order to better understand the communication between the Groovy Template and the portlet.
The structure of a template is quite simple. It consists of only two elements:
The HTML code and;
zero or more groovy language code blocks, enclosed by <% ... %> enclosures.
The HTML code in the template doesn't have to contain the html
, or body
tags. This allows groovy templates for one component to also be rendered in another component.
For example:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <% import org.exoplatform.webui.core.UIComponent; def currentPage = uicomponent.getCurrentPage(); ... %> ... <div class="$uicomponent.skin" id="UIPortalApplication"> <%uicomponent.renderChildren();%> </div>
Groovy is a scripting language for Java. Some usage examples are included in this section, however further information is available at http://groovy.codehaus.org/Documentation .
Some examples of Groovy
int min = 1; def totalPage = uicomponent.getAvailablePage(); String name = "uiPortlet"; categories = uicomponent.getItemCategories(); String columns = uicomponent.getColumns();
for(category in categories) { ... } for(i in min..max) { ... } // min and max are int variables println "</div>" ; println """ <div class="Item"> <div class="OverflowContainer"> """; <%=uicomponent.getToolbarStyle();%> // <%= to avoid a call of println method import org.exoplatform.portal.config.model.PageNode;
The configuration of a portlet is partly made with ComponentConfig
annotations (others are ComponentConfigs
, EventConfig
, etc).
One of the parameters of this annotation is template
. This is where the path to the template file associated with this portlet is defined.
To specify this parameter, add this statement to the configuration annotation.
For example, in src:/portlet/exoadmin/src/main/java/org/exoplatform/applicationregistry/webui/component/ is the UIApplicationForm.java file:
@ComponentConfig( lifecycle = UIFormLifecycle.class, template = "system:/groovy/webui/form/UIFormWithTitle.gtmpl", events = { @EventConfig(listeners = UIApplicationForm.SaveActionListener.class), @EventConfig(phase = Phase.DECODE, listeners = UIApplicationForm.CancelActionListener.class) } )
The path is in the "system
" namespace. This is a reference to the portal webapp.
This webapp contains reusable groovy templates in the folder; src:/web/portal/src/main/webapp/groovy/webui/form/ .
Use the following steps to create a new template;
Create a groovy file in the appropriate webbapp and refer to it.
Use the namespace "app" for refering to the same webapp as the component.
GateIn 3.0 stores the component templates in a folder that follows this placement convention: "/webapp/groovy/
".
your_portlet_name
/webui/component
template = "app:/groovy/your_portlet_name/webui/component/your_component.gtmpl"
Edit the template file according to the information in the Section 6.2.3.2, “The template file”.
Component template files are composed in HTML code and groovy code blocks. There are a few things more that you need to know to fully link your portlet with your template.
To successfully link a portlet with a template, please ensure the following:
If the template defines the UI of a component, use the java object variable uicomponent
to access this component instance.
This should be the case in most instances, but it is recommended that the java class inherits from UIComponent
are checked before this variable is used.
Using the uicomponent
variable, you can access all the attributes and functions of a component to use them in your template.
Take, for example UIPageIterator.gtmpl
:
<% def currentPage = uicomponent.getCurrentPage(); %> ... <a href="<%=uicomponent.event("ShowPage","$currentPage")%>" class="Icon LastTopPageIcon"> <span></span> </a>
The following example shows how uicomponent
can be used to make Ajax calls using the event
method. See Section 6.1, “AJAX Framework” for more details.
Another variable available is ctx
. This variable gives access to the context in which the template is processed.
Use this variable to retrieve elements such as; the request, the Javscript manager or the resource resolver (ctx.appRes
).
Some usage examples are:
<% def rcontext = ctx.getRequestContext() ; context.getJavascriptManager().importJavascript('GateIn.webui.UIPopupWindow'); ctx.appRes(popupId + ".title."+ title); %>
If the template defines the user interface of a component that includes a form use an instance of UIForm
in a variable named uiform
.
The UIForm
class provides the methods begin()
and end()
which write the HTML tags of the form.
The form class must inherit from UIForm
. In this class add the input elements (fields, checkboxes, lists) which are required in the form.
Render the input elements in the groovy template using uiform.renderField(field)
.
This chapter does not to refer to the Portlet API specification lifecycle but focuses on the GateIn 3.0 UI framework.
This web framework has been developed specifically for GateIn 3.0 and, while it is not necessary to use the native web framework to build portlets, all portlets packaged with GateIn 3.0 are developed using this framework.
This consistency allows portlets to use several UI components that can be used in different abstracted contexts (such as the portal itself or some portlets).
This chapter is intended for advanced developers. It covers code implementation and logic. It is not a tutorial on how to write portlets.
Refer to Section 5.1, “Portal Lifecycle” for information on concepts that are similar and top hierarchy classes that are shared.
The main entry point for configuring a portlet is in the portlet.xml file located in the portlet application WAR.
Each portlet built using the GateIn 3.0 web framework must reference the PortletApplicationController .
The portlet configuration (such as the root component) is defined in configuration.xml
. The path to this file is defined in the init-param "webui.configuration" of portlet.xml
.
<portlet> <description xml:lang="EN">Content Portlet</description> <portlet-name>ContentPortlet</portlet-name> <display-name xml:lang="EN">Content Portlet</display-name> <portlet-class>org.exoplatform.webui.application.portlet.PortletApplicationController</portlet-class> <init-param> <name>webui.configuration</name> <value>/WEB-INF/conf/portlet/content/ContentPortlet/webui/configuration.xml</value> </init-param> </portlet>
The structure of the configuration.xml
file is exactly the same as the webui-configuration.xml
which was introduced in Section 5.1, “Portal Lifecycle”.
In the case of the content portlet it is formatted as:
<webui-configuration> <application> <ui-component-root>org.exoplatform.content.webui.component.UIContentPortlet</ui-component-root> <state-manager>org.exoplatform.webui.application.portlet.ParentAppStateManager</state-manager> </application> </webui-configuration>
The PortletApplicationController
class extends the GenericPortlet
class defined in the Portlet API specification.
All methods (like processAction()
or render()
) are delegated to the PortletApplication
. The creation and caching inside the WebController
object is shown in the example below:
/** * try to obtain the PortletApplication from the WebAppController. * * If it does not exist a new PortletApplication object is created, init and cached in the * controller */ private PortletApplication getPortletApplication() throws Exception { PortalContainer container = PortalContainer.getInstance() ; WebAppController controller = (WebAppController)container.getComponentInstanceOfType(WebAppController.class) ; PortletApplication application = controller.getApplication(applicationId_) ; if(application == null) { application = new PortletApplication(getPortletConfig()) ; application.onInit() ; controller.addApplication(application) ; } return application ; }
When a portlet using the native web framework is deployed in GateIn 3.0, all methods calls go through the PortletApplication
object which extends the WebuiApplication
.
The code of the method in PortletApplication
is described below. The business logic is shown in the javadoc:
/** * The processAction() method is the one modelled according to the Portlet API specification * * The process is quite simple and here are te different steps done in the method: * * 1) The current instance of the WebuiRequestContext (stored in a ThreadLocal in the class) is referenced * 2) A new request context of type PortletRequestContext (which extends the class WebuiRequestContext) is * created as a child of the current context instance * 3) The new context is place inside the ThreadLocal and hence overides its parent one there, * only for the portlet request lifeciclye * 4) The method onStartRequest() is called in all the ApplicationLifecycle objects referenced in the webui * configuration XML file * 5) The StateManager object (in case of portlet it is an object of type ParentAppStateManager) is used to get the RootComponent * also referenced in the XML configuration file * 6) The methods processDecode(UIApplication, WebuiRequestContext) and processAction(UIApplication, WebuiRequestContext) * are then called * 7) Finally, a flag, to tell that the processAction phase was done, in the context is set to true and the parent * context is restored in the Threadlocal */ public void processAction(ActionRequest req, ActionResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance() ; PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext) ; WebuiRequestContext.setCurrentInstance(context) ; try { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context) ; } UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ; context.setUIApplication(uiApp) ; processDecode(uiApp, context) ; if(!images/context.isResponseComplete() &&!images/ context.getProcessRender()) { processAction(uiApp, context) ; } } finally { context.setProcessAction(true) ; WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ; } }
The PortletRequestContext
extends the WebuiRequestContext
class and acts as a wrapper on all the portlet request information:
/** * In this method we try to get the PortletRequestContext object from the attribute map of the parent * WebuiRequestContext. * * If it is not cached then we create a new instance, if it is cached then we init it with the correct * writer, request and response objects * * We finally cache it in the parent attribute map * */ private PortletRequestContext createRequestContext(PortletRequest req, PortletResponse res, WebuiRequestContext parentAppRequestContext) throws IOException { String attributeName = getApplicationId() + "$PortletRequest" ; PortletRequestContext context = (PortletRequestContext) parentAppRequestContext.getAttribute(attributeName) ; Writer w = null ; if(res instanceof RenderResponse){ RenderResponse renderRes = (RenderResponse)res; renderRes.setContentType("text/html; charset=UTF-8"); w = renderRes.getWriter() ; } if(context!images/= null) { context.init(w, req, res) ; } else { context = new PortletRequestContext(this, w, req, res) ; parentAppRequestContext.setAttribute(attributeName, context) ; } context.setParentAppRequestContext(parentAppRequestContext) ; return context; }
In the PortletApplication
, the line;
UIApplication uiApp = getStateManager().restoreUIRootComponent(context);
asks the StateManager
defined for the portlet to get the UI root component. In the case of a portlet the root component must extend UIPortletApplication
.
public class ParentAppStateManager extends StateManager { /** * This method simply delegate the call to the same method of the parent WebuiRequestContext */ @SuppressWarnings("unchecked") public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception { WebuiRequestContext pcontext = (WebuiRequestContext) context.getParentAppRequestContext() ; return pcontext.getStateManager().restoreUIRootComponent(context) ; }
Hence this is the PortalStateManager that will also handle the extraction of the root component.
public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception { context.setStateManager(this) ; WebuiApplication app = (WebuiApplication)context.getApplication() ; /* * If the request context is of type PortletRequestContext, we extract the parent context which will * allow to get access to the PortalApplicationState object thanks to the session id used as the key for the * syncronised Map uiApplications */ if(context instanceof PortletRequestContext) { WebuiRequestContext preqContext = (WebuiRequestContext) context.getParentAppRequestContext() ; PortalApplicationState state = uiApplications.get(preqContext.getSessionId()) ; PortletRequestContext pcontext = (PortletRequestContext) context ; String key = pcontext.getApplication().getApplicationId() ; UIApplication uiApplication = state.get(key) ; if(uiApplication!images/= null) return uiApplication; synchronized(uiApplications) { ConfigurationManager cmanager = app.getConfigurationManager() ; String uirootClass = cmanager.getApplication().getUIRootComponent() ; Class type = Thread.currentThread().getContextClassLoader().loadClass(uirootClass) ; uiApplication = (UIApplication)app.createUIComponent(type, null, null, context) ; state.put(key, uiApplication) ; } return uiApplication ; } }
The render method business logic is quite similar to processAction()
.
/** * The render method business logic is quite similar to the processAction() one. * * 1) A PortletRequestContext object is created (or extracted from the cache if it already exists) * and initialized * 2) The PortletRequestContext replaces the parent one in the WebuiRequestContext ThreadLocal object * 3) If the portal has already called the portlet processAction() then the call to all onStartRequest of * the ApplicationLifecycle has already been made, otherwise we call them * 4) The ParentStateManager is also used to get the UIApplication, as we have seen it delegates the call * to the PortalStateManager which caches the UI component root associated with the current application * 5) the processRender() method of the UIPortletApplucaton is called * 6) Finally, the method onEndRequest() is called on every ApplicationLifecycle referenced in the portlet * configuration XML file and the parent WebuiRequestContext is restored * */ public void render(RenderRequest req, RenderResponse res) throws Exception { WebuiRequestContext parentAppRequestContext = WebuiRequestContext.getCurrentInstance() ; PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext) ; WebuiRequestContext.setCurrentInstance(context) ; try { if(!context.hasProcessAction()) { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onStartRequest(this, context) ; } } UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ; context.setUIApplication(uiApp) ; if(!context.isResponseComplete()) { UIPortletApplication uiPortletApp = (UIPortletApplication)uiApp; uiPortletApp.processRender(this, context) ; } uiApp.setLastAccessApplication(System.currentTimeMillis()) ; } finally { try { for(ApplicationLifecycle lifecycle : getApplicationLifecycle()) { lifecycle.onEndRequest(this, context) ; } } catch (Exception exception){ log.error("Error while trying to call onEndRequest of the portlet ApplicationLifecycle", exception); } WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ; } }
The following is the processRender()
call made on the UIPortletApplication
:
/** * The default processRender for an UIPortletApplication handles two cases: * * A. Ajax is used * ---- * If Ajax is used and that the entire portal should not be re rendered, then an AJAX fragment is * generated with information such as the portlet id, the portlet title, the portlet modes, the window * states as well as the HTML for the block to render * * B. A full render is made * ---- * a simple call to the method super.processRender(context) which will delegate the call to all the * Lifecycle components * */ public void processRender(WebuiApplication app, WebuiRequestContext context) throws Exception { WebuiRequestContext pContext = (WebuiRequestContext)context.getParentAppRequestContext(); if(context.useAjax() &&!images/pContext.getFullRender()) { Writer w = context.getWriter() ; Set<UIComponent> list = context.getUIComponentToUpdateByAjax() ; if(list!images/= null) { if(getUIPopupMessages().hasMessage()) context.addUIComponentToUpdateByAjax(getUIPopupMessages()) ; for(UIComponent uicomponent : list) { renderBlockToUpdate(uicomponent, context, w) ; } return ; } } super.processRender(context) ; }
The Java Community Process (JCP
) uses Java Specification Requests (JSR
s) to define proposed specifications and technologies designed for the Java platform.
The Portlet Specifications aim at defining portlets that can be used by any JSR-168 (Portlet 1.0) or JSR-286 (Portlet 2.0) portlet container.
Most Java EE (Enterprise Edition) portals include at least one compliant portlet container, and GateIn 3.0 is no exception. In fact, GateIn 3.0 includes a container that supports both versions.
This chapter gives a brief overview of the Portlet Specifications but portlet developers are strongly encouraged to read the JSR-286 Portlet Specification .
GateIn 3.0 is fully JSR-286 compliant. Any JSR-168 or JSR-286 portlet operates as it is mandated by the respective specifications inside the portal.
A portal can be considered as a series of web pages with different areas within them. Those areas contain different windows and each window contains portlet:
The diagram below visually represents this nesting:
A portlet can have different view modes. Three modes are defined by the JSR-286 specification:
Generates markup reflecting the current state of the portlet.
Allows a user to customize the behavior of the portlet.
Provides information to the user as to how to use the portlet.
Window states are an indicator of how much page space a portlet consumes on any given page. The three states defined by the JSR-168 specification are:
A portlet shares this page with other portlets.
A portlet may show very little information, or none at all.
A portlet may be the only portlet displayed on this page.
The tutorials contained in this chapter are targeted toward portlet developers. It is also recommend that developers read and understand the JSR-286 Portlet Specification .
This example is using Maven to compile and build the web archive. Maven versions can be downloaded from maven.apache.org
This section describes how to deploy a portlet in GateIn 3.0. A sample portlet called SimplestHelloWorld
is located in the examples
directory at the root of your GateIn 3.0 binary package. This sample is used in the following examples.
To compile and package the application:
Navigate to the SimplestHelloWorld
directory and execute:
mvn package
If the compile is successfully packaged the result will be available in: SimplestHelloWorld/target/SimplestHelloWorld-0.0.1.war
.
Copy the package file into JBOSS_HOME/server/default/deploy
.
Start JBoss Application Server (if it is not already running).
Create a new portal page and add the portlet to it.
Like other Java EE applications, GateIn 3.0 portlets are packaged in WAR files. A typical portlet WAR file can include servlets, resource bundles, images, HTML, JavaServer Pages (JSP), and other static or dynamic files.
The following is an example of the directory structure of the SimplestHelloWorld
portlet:
|-- SimplestHelloWorld-0.0.1.war | `-- WEB-INF | |-- classes | | `-- org | | `-- gatein | | `-- portal | | `-- examples | | `-- portlets | |`-- SimplestHelloWorldPortlet.class | |-- po
rtlet.xml | `-- we
b.xml
![]() | The compiled Java class implementing javax.portlet.Portlet (through javax.portlet.GenericPortlet ) |
![]() | This is the mandatory descriptor files for portlets. It is used during deployment.. |
![]() | This is the mandatory descriptor for web applications. |
Below is the SimplestHelloWorldPortlet/src/main/java/org/gatein/portal/examples/portlets/SimplestHelloWorldPortlet.java
Java source:
package org.gatein.portal.examples.portlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.portlet.GenericPortlet;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
public class SimplestHelloWorldPortlet extends GenericPortlet
{
public void doView(RenderRequest request,RenderResponse response) throws IOException
{PrintWriter writer = response.getWriter();
writer.write("Hello World !");
writer.close();
}
}
![]() |
All portlets must implement the
The
|
![]() |
If only the |
![]() | Use the RenderResponse to obtain a writer to be used to produce content. |
![]() | Write the markup to display. |
![]() | Closing the writer. |
Portlets are responsible for generating markup fragments, as they are included on a page and are surrounded by other portlets. This means that a portlet outputting HTML must not output any markup that cannot be found in a <body>
element.
GateIn 3.0 requires certain descriptors to be included in a portlet WAR file. These descriptors are defined by the Jave EE (web.xml
) and Portlet Specification (portlet.xml
).
Below is an example of the SimplestHelloWorldPortlet/WEB-INF/portlet.xml
file. This file must adhere to its definition in the JSR-286 Portlet Specification. More than one portlet application may be defined in this file:
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0"> <portlet> <portlet-name>SimplestHelloWorldPortlet</portlet-name> <portlet
-class> org.gatein.portal.examples.portlets.SimplestHelloWorldPortlet </portlet-class> <support
s> <mime-type>text/html</mime-type> </supports> <portlet
-info> <title>Simplest Hello World Portlet</title> </portlet-info> </portlet> </portlet-app>
![]() | Define the portlet name. It does not have to be the class name. |
![]() |
The Fully Qualified Name ( |
![]() |
The The declared MIME types must match the capability of the portlet. It allows administrators to pair which modes and window states are supported for each markup type.
This does not have to be declared as all portlets must support the
Use the |
![]() |
When rendered, the portlet's title is displayed as the header in the portlet window, unless it is overridden programmatically. In the example above the title would be |
This section discusses:
Adding more features to the previous example.
Using a JSP page to render the markup.
Using the portlet tag library to generate links to the portlet in different ways.
Using the other standard portlet modes.
The example used in this section can be found in the JSPHelloUser
directory.
Execute mvn package
in this directory.
Copy JSPHelloUser/target/JSPHelloUser-0.0.1.war
to the deploy
directory of JBoss Application Server.
Select the new JSPHelloUser
tab in your portal.
The EDIT
button only appears with logged-in users, which is not the case in the screenshot.
The package structure in this tutorial does not differ greatly from the previous example, with the exception of adding some JSP files detailed later.
The JSPHelloUser portlet contains the mandatory portlet application descriptors. The following is an example of the directory structure of the JSPHelloUser portlet:
JSPHelloUser-0.0.1.war |-- META-INF | |-- MANIFEST.MF |-- WEB-INF | |-- classes | | `-- org | | `-- gatein | | `-- portal | | `-- examples | | `-- portlets | | `-- JSPHelloUserPortlet.class | |-- portlet.xml | `-- web.xml `-- jsp |-- edit.jsp |-- hello.jsp |-- help.jsp `-- welcome.jsp
The code below is from the JSPHelloUser/src/main/java/org/gatein/portal/examples/portlets/JSPHelloUserPortlet.java
Java source. It is split in different pieces.
package org.gatein.portal.examples.portlets;
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
public class JSPHelloUserPortlet extends GenericPortlet
{
public void doView(RenderRequest request, RenderResponse response)throws PortletException, IOException
{
String sYourName = (String) request.getParameter("yourname");if (sYourName != null)
{
request.setAttribute("yourname", sYourName);
PortletRequestDispatcher prd =getPortletContext().getRequestDispatcher("/jsp/hello.jsp");
prd.include(request, response);
}
else
{
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/welcome.jsp");
prd.include(request, response);
}
}
...
![]() | Override the doView method (as in the first tutorial). |
![]() |
This entry attempts to obtain the value of the render parameter named |
![]() | Get a request dispatcher on a file located within the web archive. |
![]() | Perform the inclusion of the markup obtained from the JSP. |
As well as the VIEW
portlet mode, the specification defines two other modes; EDIT
and HELP
.
These modes need to be defined in the portlet.xml
descriptor. This will enable the corresponding buttons on the portlet's window.
The generic portlet that is inherited dispatches the different views to the methods: doView
, doHelp
and doEdit
.
...
protected void doHelp(RenderRequest rRequest, RenderResponse rResponse) throws PortletException, IOException,
UnavailableException
{
rResponse.setContentType("text/html");
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/help.jsp");
prd.include(rRequest, rResponse);
}
protected void doEdit(RenderRequest rRequest, RenderResponse rResponse) throws PortletException, IOException,
UnavailableException
{
rResponse.setContentType("text/html");
PortletRequestDispatcher prd = getPortletContext().getRequestDispatcher("/jsp/edit.jsp");
prd.include(rRequest, rResponse);
}
...
Portlet calls happen in one or two phases. One when the portlet is rendered and two when the portlet is actioned then rendered.
An action phase is a phase where some state changes. The render phase will have access to render parameters that will be passed each time the portlet is refreshed (with the exception of caching capabilities).
The code to be executed during an action has to be implemented in the processAction method of the portlet.
...public void processAction(ActionRequest aRequest, ActionResponse aResponse) throws PortletException, IOException,
UnavailableException
{String sYourname = (String) aRequest.getParameter("yourname");
aResponse.setRenderParameter("yourname", sYourname);
}
...
![]() |
|
![]() | Here the parameter is retrieved through an action URL . |
![]() |
The value of |
The help.jsp
and edit.jsp
files are very simple. Note that CSS styles are used as defined in the portlet specification. This ensures that the portlet will render well within the theme and across portal vendors.
<div class="portlet-section-header">Help mode</div>
<div class="portlet-section-body">This is the help mode, a convenient place to give the user some help information.</div>
<div class="portlet-section-header">Edit mode</div>
<div class="portlet-section-body">This is the edit mode, a convenient place to let the user change his portlet preferences.</div>
The landing page contains the links and form to call our portlet:
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet" %> <div class="portlet-section-header">Welcome !</div> <br/> <div class="portlet-font">Welcome on the JSP Hello User portlet, my name is GateIn Portal. What's yours ?</div> <br/> <div class="portlet-font">Method 1: We simply pass the parameter to the render phase:<br/> <a href="<port
let:renderURL><portlet:param name="yourname" value="John Doe"/> </portlet:renderURL>">John Doe</a></div> <br/> <div class="portlet-font">Method 2: We pass the parameter to the render phase, using valid XML: Please check the source code to see the difference with Method 1. <portlet:rende
rURL var="myRenderURL"> <portlet:param name="yourname" value='John Doe'/> </portlet:renderURL> <br/> <a href="<%= m
yRenderURL %>">John Doe</a></div> <br/> <div class="portlet-font">Method 3: We use a form:<br/> <portlet:actio
nURL var="myActionURL"/> <form action="
<%= myActionURL %>" method="POST"> <span class="portlet-form-field-label">Name:</span> <input class="portlet-form-input-field" type="text" name="yourname"/> <input class="portlet-form-button" type="Submit"/> </form> </div>
![]() | The portlet taglib, needs to be declared. |
![]() |
The first method showed here is the simplest one. |
![]() |
In this method the |
![]() |
The variable |
![]() | The third method mixes form submission and action request. Again, a temporary variable is used to put the created URL into. |
![]() | The action URL is used in HTML form. |
In the third method the action phase is triggered first then the render phase is triggered, which outputs some content back to the web browser based on the available render parameters.
In order to write a portlet using JSF a 'bridge' is needed. This software allows developers to write a portlet application as if it was a JSF application. The bridge then negotiates the interactions between the two layers.
An example of the JBoss Portlet Bridge is available in examples/JSFHelloUser
. The configuration is slightly different from a JSP application. This example can be used as a base to configure instead of creating a new application.
As in any JSF application, the file faces-config.xml
is required. It must contain the following information:
<faces-config>
...
<application>
<view-handler>org.jboss.portletbridge.application.PortletViewHandler</view-handler>
<state-manager>org.jboss.portletbridge.application.PortletStateManager</state-manager>
</application>
...
</faces-config>
The portlet bridge libraries must be available and are usually bundled with the WEB-INF/lib
directory of the web archive.
The other difference compare to a regular portlet application, can be found in the portlet descriptor. All details about it can be found in the JSR-301 specification that the JBoss Portlet Bridge implements.
<?xml version="1.0" encoding="UTF-8"?> <portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0"> <portlet> <portlet-name>JSFHelloUserPortlet</portlet-name> <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> <portlet-mode>edit</portlet-mode> <portlet-mode>help</portlet-mode> </supports> <portlet-info> <title>JSF Hello User Portlet</title> </portlet-info> <init-param> <name
>javax.portlet.faces.defaultViewId.view</name> <value>/jsf/welcome.jsp</value> </init-param> <init-param> <name
>javax.portlet.faces.defaultViewId.edit</name> <value>/jsf/edit.jsp</value> </init-param> <init-param> <name
>javax.portlet.faces.defaultViewId.help</name> <value>/jsf/help.jsp</value> </init-param> </portlet> </portlet-app>
![]() |
All JSF portlets define |
![]() | This is a mandatory parameter to define what's the default page to display. |
![]() | This parameter defines which page to display on the 'edit' mode. |
![]() | This parameter defines which page to display on the 'help' mode. |
A gadget is a mini web application, embedded in a web page and running on an application server platform. These small applications help users perform various tasks.
GateIn 3.0 supports gadgets such as: Todo gadget, Calendar gadget, Calculator gadget, Weather Forecasts and and RSS Reader.
Default Gadgets:
The calendar gadget allows users to switch easily between daily, monthly and yearly view and, again, is customizable to match your portal's theme.
This application helps you organize your day and work group. It is designed to keep track of your tasks in a convenient and transparent way. Tasks can be highlighted with different colors.
This mini-application lets you perform most basic arithmetic operations and can be themed to match the rest of your portal.
An RSS reader, or aggregator, collates content from various, user-specified feed sources and displays them in one location. This content can include, but isn't limited to, news headlines, blog posts or email. The RSS Reader gadget displays this content in a single window on your Portal page.
Further gadgets can be obtained from the Google Gadget site. GateIn 3.0 is compatible with most of the gadgets available here.
The following sections require more textual information.
After referencing the gadget successfully, then import it into the local repository.
GateIn 3.0 recommends using two virtual hosts for security. If the gadget is running on a different domain than the container (the website that 'contains' the app), it is unable to interfere with the portal by modifying code or cookies.
An example would hosting the portal from http://www.sample.com and the gadgets from http://www.samplemodules.com.
To do this, configure a parameter called gadgets.hostName. The value is the path/to/gadgetServer in GadgetRegisteryService
:
<component> <key>org.exoplatform.application.gadget.GadgetRegistryService</key> <type>org.exoplatform.application.gadget.jcr.GadgetRegistryServiceImpl</type> <init-params> <value-param> <name>gadgets.hostName</name> <description>Gadget server url</description> <value>http://localhost:8080/GateInGadgetServer/gadgets/</value> </value-param> </init-params> </component>
It is also possible to have multiple rendering servers. This helps to balance the rendering load across multiple servers.
When deploying on the same server ensure the gadget initiates before anything that calls it (for example; the webapp GateInGadgets
which uses org.exoplatform.application.gadget.GadgetRegister
).
A file called key.txt has to be generated for every installation of GateIn 3.0 to be secure. This file contains a secret key used to encrypt the security token used for authenticating the user.
In Linux systems this file can be generated with:
dd if=/dev/random bs=32 count=1 | openssl base64 > /tmp/key.txt
These servers have to be on the same domain as the gadget server. You can configure the container in eXoGadgetServer:/WEB-INF/classes/containers/default/container.js
.
"gadgets.content-rewrite" : { "include-urls": ".*", "exclude-urls": "", "include-tags": ["link", "script", "embed", "img", "style"], "expires": "86400", "proxy-url": "http://localhost:8080/eXoGadgetServer/gadgets/proxy?url=", "concat-url": "http://localhost:8080/eXoGadgetServer/gadgets/concat?" },
The Web Services for Remote Portlets specification defines a web service interface for accessing and interacting with interactive presentation-oriented web services. It has been produced through the efforts of the Web Services for Remote Portlets (WSRP) OASIS Technical Committee. It is based on the requirements gathered and on the concrete proposals made to the committee.
Scenarios that motivate WSRP functionality include:
More information on WSRP can be found on the official website for WSRP. We suggest reading the primer for a good, albeit technical, overview of WSRP.
The WSRP Technical Committee defined WSRP Use Profiles to help with WSRP interoperability. We will refer to terms defined in that document in this section.
GateIn 3.0 provides a Simple level of support for our WSRP Producer except that out-of-band registration is not currently handled. We support in-band registration and persistent local state (which are defined at the Complex level).
On the Consumer side, GateIn 3.0 provides a Medium level of support for WSRP, except that we only handle HTML markup (as GateIn 3.0 itself doesn't handle other markup types). We do support explicit portlet cloning and we fully support the PortletManagement interface.
As far as caching goes, we have Level 1 Producer and Consumer. We support Cookie handling properly on the Consumer and our Producer requires initialization of cookies (as we have found that it improved interoperabilty with some consumers). We don't support custom window states or modes, as Portal doesn't either. We do, however, support CSS on both the Producer (though it's more a function of the portlets than inherent Producer capability) and Consumer.
While we provide a complete implementation of WSRP 1.0, we do need to go through the Conformance statements and perform more interoperability testing (an area that needs to be better supported by the WSRP Technical Committee and Community at large).
GateIn 3.0 provides a complete support of WSRP 1.0 standard interfaces and offers both consumer and producer
services. WSRP support is provided by the
portal-wsrp.sar
service archive, included in
the main
jboss-portal.sar
service archive, if you've obtained GateIn 3.0 from a binary
distribution. If you don't intend on using WSRP, we recommend that you remove
portal-wspr.sar
from the main
jboss-portal.sar
service archive.
If you've obtained the source distribution of GateIn 3.0, you need to build and deploy the WSRP service
separately. Please follow the instructions on how to install
JBoss
Portal from the sources. Once this is done, navigate to
JBOSS_PORTAL_HOME_DIRECTORY/wsrp
and type:
build deploy
At the end of the build process,
portal-wsrp.sar
is copied to
JBOSS_HOME/server/default/deploy
.
If you have modified the port number on which Portal runs or bound your Application Server to a specific host name, you will also need update the port and/or hostname information for WSRP as found on GateIn 3.0's wiki.
It is possible to use WSRP over SSL for secure exchange of data. Please refer to the instructions on how to do so from GateIn 3.0's wiki.
GateIn 3.0 doesNOT, by default, expose local portlets for consumption by
remote WSRP consumers. In order to make a portlet remotely available, it must be made "remotable" by adding a
remotable
element to the
jboss-portlet.xml
deployment descriptor for
that portlet. If a
jboss-portlet.xml
file does not exist, one must be added to the
WEB-INF
folder of the web application containing the portlet.
In the following example, the "BasicPortlet" portlet is specified as being remotable. The
remotable
element is optional.
Example 8.1.
<?xml version="1.0" standalone="yes"?> <!DOCTYPE portlet-app PUBLIC "-//&PRODUCT;//DTD JBoss Portlet 2.6//EN" "http://www.jboss.org/portal/dtd/jboss-portlet_2_6.dtd"> <portlet-app> <portlet> <portlet-name>BasicPortlet</portlet-name> <remotable>true</remotable> </portlet> </portlet-app>
It is also possible to specify that all the portlets declared within a given
jboss-portlet.xml
file have a specific "remotable" status by default. This is done by adding a single
remotable
element to the root
portlet-app
element. Usually, this feature will be used to remotely expose
several portlets without having to specify the status for all the declared portlets. Let's look at an example:
Example 8.2.
<?xml version="1.0" standalone="yes"?> <!DOCTYPE portlet-app PUBLIC "-//&PRODUCT;//DTD JBoss Portlet 2.6//EN" "http://www.jboss.org/portal/dtd/jboss-portlet_2_6.dtd"> <portlet-app> <remotable>true</remotable> <portlet> <portlet-name>RemotelyExposedPortlet</portlet-name> ... </portlet> <portlet> <portlet-name>NotRemotelyExposedPortlet</portlet-name> <remotable>false</remotable> ... </portlet> </portlet-app>
In the example above, we defined two portlets with a default "remotable" status set to true. This means that
all portlets defined in this descriptor are, by default, exposed remotely by GateIn 3.0's WSRP producer.
Note, however, that it is possible to override the default behavior by adding a
remotable
element to a portlet description. In that case, the "remotable" status defined by the portlet takes precedence
over the default. In the example above, the
RemotelyExposedPortlet
inherits the "remotable"
status defined by default since it does not specify a
remotable
element in its description.
TheNotRemotelyExposedPortlet
, however, overrides the default behavior and is not remotely
exposed. Note that in the absence of a top-level
remotable
element, portlets are NOT
remotely exposed.
WSRP Consumers vary a lot as far as how they are configured. Most of them require that you either specify the URL for the Producer's WSDL definition or the URLs for the individual endpoints. Please refer to your Consumer's documentation for specific instructions. For instructions on how to do so in GateIn 3.0, please refer toSection 8.6, “Consuming remote WSRP portlets in GateIn 3.0”.
GateIn 3.0's Producer is automatically set up when you deploy a portal instance with the WSRP service.
You can access the WSDL file at
http://{hostname}:{port}/portal-wsrp/MarkupService?wsdl
. You can access the endpoint URLs
at:
http://{hostname}:{port}/portal-wsrp/ServiceDescriptionService
http://{hostname}:{port}/portal-wsrp/MarkupService
http://{hostname}:{port}/portal-wsrp/RegistrationService
http://{hostname}:{port}/portal-wsrp/PortletManagementService
The default hostname is
localhost
and the default port is 8080.
To be able to consume WSRP portlets exposed by a remote producer, GateIn 3.0's WSRP consumer needs to know how to access that remote producer. One can configure access to a remote producer using WSRP Producer descriptors. Alternatively, a portlet is provided to configure remote producers.
Once a remote producer has been configured, it can be made available in the list of portlet providers in the Management portlet on the Admin page of GateIn 3.0. You can then examine the list of portlets that are exposed by this producer and configure the portlets just like you would for local portlets.
GateIn 3.0's default configuration exposes some of the sample portlets for remote consumption. As a way to
test the WSRP service, a default consumer has been configured to consume these portlets. To make sure that
the service indeed works, check that there is a portlet provider with the
self
identifier in the portlet providers list in the Management portlet of the Admin page. All local portlets
marked as remotable are exposed as remote portlets via the
self
portlet
provider so that you can check that they work as expected with WSRP. The
portal-wsrp.sar
file contains a WSRP Producer descriptor (default-wsrp.xml
) that configures this
default producer. This file can be edited or removed if needed.
Let's work through the steps of defining access to a remote producer so that its portlets can be consumed within GateIn 3.0. We will configure access to BEA's public WSRP producer. We will first examine how to do so using the configuration portlet. We will then show how the same result can be accomplish with a producer descriptor.
As of Portal 2.6, a configuration portlet is provided to configure access to remote WSRP Producers
grahically. You can access it at
http://{hostname}:{port}/portal/auth/portal/admin/WSRP
or by logging in as a Portal administrator and clicking on the WSRP tab in the Admin portal. If all went
well, you should see something similar to this:
This screen presents all the configured producers associated with their status and possible actions on them. A Consumer can be active or inactive. Activating a Consumer means that it is ready to act as a portlet provider. Deactivating it will remove it from the list of available portlet providers. Note also that a Consumer can be marked as requiring refresh meaning that the information held about it might not be up to date and refreshing it from the remote Producer might be a good idea. This can happen for several reasons: the service description for that remote Producer has not been fetched yet, the cached version has expired or modifications have been made to the configuration that could potentially invalidate it, thus requiring re-validation of the information.
Next, we create a new Consumer which we will callbea
. Type "bea
" in
the "Create a consumer named:" field then click on "Create consumer":
You should now see a form allowing you to enter/modify the information about the Consumer. Set the cache expiration value to 300 seconds and enter the WSDL URL for the producer in the text field and press the "Refresh & Save" button:
This will retrieve the service description associated with the Producer which WSRP is described by the
WSDL file found at the URL you just entered. In our case, querying the service description will allow us
to learn that the Producer requires registration and that it expects a value for the registration
property namedregistration/consumerRole
:
Enter "public
" as the value for the registration property and press "Save &
Refresh" once more. You should now
see something similar to:
The Consumer for the
bea
Producer should now be available as a portlet provider and
is ready to be used.
A producer is configured, by default, by retrieving the producer's information via a WSDL URL. There are
rare cases, however, where URLs need to be provided for each of the producer's end points. You can do
exactly that by unchecking the "Use WSDL?" checkbox, as is the case for the
self
producer:
We will create a
public-bea-wsrp.xml
descriptor. Note that the actual name does not
matter as long as it ends with-wsrp.xml
:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE deployments PUBLIC "-//&PRODUCT;//DTD WSRP Remote Producer Configuration 2.6//EN"
"http://www.jboss.org/portal/dtd/jboss-wsrp-consumer_2_6.dtd">
<deployments>
<deployment>
<wsrp-producer id="bea" expiration-cache="300">
<endpoint-wsdl-url>http://wsrp.bea.com:7001/producer/producer?WSDL</endpoint-wsdl-url>
<registration-data>
<property>
<name>registration/consumerRole</name>
<lang>en</lang>
<value>public</value>
</property>
</registration-data>
</wsrp-producer>
</deployment>
</deployments>
This producer descriptor gives access to BEA's public WSRP producer. We will look at the details of the
different elements later. Note for now the
producer-id
element with a "bea
" value. Put this file in the deploy directory and start the server
(with GateIn 3.0 and its WSRP service deployed).
jboss-portal.sar/portal-wsrp.sar/dtd/jboss-wsrp-consumer_2_6.dtd
and
jboss-portal.sar/portal-wsrp.sar/xsd/jboss-wsrp-consumer_2_6.xsd
Let's now look at the Admin page and the Management portlet. Click on the "Portlet definitions" tab at the top. Once this is done, look at the list of available portlet providers. If all went well, you should see something similar to this:
We have 3 available portlet providers:
local, self
andbea
. The
local
portlet provider exposes all the portlets deployed in this particular instance of Portal. As
explained above, the
self
provider refers to the default WSRP consumer bundled with Portal that consumes
the portlets exposed by the default WSRP producer. The
bea
provider corresponds to BEA's public producer
we just configured. Select it and click on "View portlets". You should now see something similar to:
From there on out, you should be able to configure WSRP portlets just as any other. In particular, you can create an instance of one of the remote portlets offered by BEA's public producer just like you would create an instance of a local portlet and then assign it to a window in a page. If you go to that page, you should see something similar to below for this portlet:
A WSRP Producer descriptor is an XML file which name ends in
-wsrp.xml
and
which can be dropped in the deploy directory of the JBoss application server or nested in .sar files. It is
possible to configure access to several different producers within a single descriptor or use one file per
producer, depending on your needs. An XML Schema for the WSRP Producer descriptor format can be found at
jboss-portal.sar/portal-wsrp.sar/xsd/jboss-wsrp-consumer_2_6.xsd
, while a (legacy) DTD
can be found atjboss-portal.sar/portal-wsrp.sar/dtd/jboss-wsrp-consumer_2_6.dtd
.
Let's now look at which information needs to be provided to configure access to a remote producer.
First, we need to provide an identifier for the producer we are configuring so that we can refer to it
afterwards. This is accomplished via the mandatory
id
attribute of the
<wsrp-producer>
element.
GateIn 3.0 also needs to learn about the remote producer's endpoints to be able to connect to the remote web services and perform WSRP invocations. Two options are currently supported to provide this information:
<endpoint-config>
element and its
<service-description-url>
,
<markup-url>
,
<registration-url>
and
<portlet-management-url>
children. These URLs are
producer-specific so you will need to refer to your producer documentation or WSDL file to
determine
the appropriate values.
<endpoint-wsdl-url>
element. GateIn 3.0 will then
heuristically determine, from the information contained in the WSDL file, how to connect
appropriately
to the remote WSRP services.
Both the
id
attribute and either
<endpoint-config>
or
<endpoint-wsdl-url>
elements
are required for a functional remote producer configuration.
It is also possible to provide addtional configuration, which, in some cases, might be important to establish a proper connection to the remote producer.
One such optional configuration concerns caching. To prevent useless roundtrips between the local
consumer and the remote producer, it is possible to cache some of the information sent by the producer
(such
as the list of offered portlets) for a given duration. The rate at which the information is refreshed is
defined by the
expiration-cache
attribute of the
<wsrp-producer>
element which specifies the
refreshing period in seconds. For example, providing a value of 120 for expiration-cache means that the
producer information will not be refreshed for 2 minutes after it has been somehow accessed. If no value
is provided, GateIn 3.0 will always access the remote producer regardless of whether the remote
information has changed or not. Since, in most instances, the information provided by the producer does
not
change often, we recommend that you use this caching facility to minimize bandwidth usage.
Additionally, some producers require consumers to register with them before authorizing them to access their offered portlets. If you know that information beforehand, you can provide the required registration information in the producer configuration so that the Portal consumer can register with the remote producer when required.
Registration configuration is done via the
<registration-data>
element. Since GateIn 3.0 can generate the mandatory information for you, if the remote producer does
not
require any registration properties, you only need to provide an empty
<registration-data>
element. Values for the registration properties
required by the remote producer can be provided via
<property>
elements. See the example below for more details. Additionally, you can override the default consumer
name
automatically provided by GateIn 3.0 via the
<consumer-name>
element. If you choose to provide a consumer name, please remember that this should uniquely identify
your
consumer.
Here is the configuration of the
self
producer as found in
default-wsrp.xml
with a cache expiring every five minutes:
Example 8.3.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE deployments PUBLIC "-//&PRODUCT;//DTD WSRP Remote Producer Configuration 2.6//EN" "http://www.jboss.org/portal/dtd/jboss-wsrp-consumer_2_6.dtd"> <deployments> <deployment> <wsrp-producer id="self" expiration-cache="300"> <!-- we need to use the individual endpoint configuration because the configuration via wsdl forces an immediate attempt to access the web service description which is not available yet at this point of deployment --> <endpoint-config> <service-description-url> http://localhost:8080/portal-wsrp/ServiceDescriptionService </service-description-url> <markup-url>http://localhost:8080/portal-wsrp/MarkupService</markup-url> <registration-url> http://localhost:8080/portal-wsrp/RegistrationService </registration-url> <portlet-management-url> http://localhost:8080/portal-wsrp/PortletManagementService </portlet-management-url> </endpoint-config> <registration-data/> </wsrp-producer> </deployment> </deployments>
Here is an example of a WSRP descriptor with a 2 minute caching time and manual definition of the endpoint URLs:
Example 8.4.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE deployments PUBLIC "-//&PRODUCT;//DTD WSRP Remote Producer Configuration 2.6//EN" "http://www.jboss.org/portal/dtd/jboss-wsrp-consumer_2_6.dtd"> <deployments> <deployment> <wsrp-producer id="MyProducer" expiration-cache="120"> <endpoint-config> <service-description-url> http://www.someproducer.com/portal-wsrp/ServiceDescriptionService </service-description-url> <markup-url> http://www.someproducer.com/portal-wsrp/MarkupService </markup-url> <registration-url> http://www.someproducer.com/portal-wsrp/RegistrationService </registration-url> <portlet-management-url> http://www.someproducer.com/portal-wsrp/PortletManagementService </portlet-management-url> </endpoint-config> </wsrp-producer> </deployment> </deployments>
Here is an example of a WSRP descriptor with endpoint definition via remote WSDL file, registration data and cache expiring every minute:
Example 8.5.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE deployments PUBLIC "-//&PRODUCT;//DTD WSRP Remote Producer Configuration 2.6//EN" "http://www.jboss.org/portal/dtd/jboss-wsrp-consumer_2_6.dtd"> <deployments> <deployment> <wsrp-producer id="AnotherProducer" expiration-cache="60"> <endpoint-wsdl-url>http://example.com/producer/producer?WSDL</endpoint-wsdl-url> <registration-data> <property> <name>property name</name> <lang>en</lang> <value>property value</value> </property> </registration-data> </wsrp-producer> </deployment> </deployments>
Producers often offer several levels of service depending on consumers' subscription levels (for example). This is implemented at the WSRP level with the registration concept: producers assert which level of service to provide to consumers based on the values of given registration properties.
It is therefore sometimes necessary to modify the registration that concretizes the service agreement
between a consumer and a producer. An example of easily available producer offering different level of
services is BEA's public producer. We configured access to that producer in
Section 8.6.2.1, “Using the configuration portlet”.
If you recall, the producer was requiring registration and required a value to be provided for the
registration/consumerRole
property. The description of that property indicated that
three values were possible:public
,
partner
or
insider
each corresponding to a different service level. We registered using the
public
service level. This gave us access to three portlets as shown here:
Suppose now that we would like to upgrade our service level to the "insider" level. We will need to
tell BEA's producer that our registration data has been modified. Let's see how to do this. Assuming you
have configured access to the producer as previously described, please go to the configuration screen for
the
bea
producer and modify the value of the
registration/consumerRole
to
insider
instead ofpublic
:
Now click on "Update properties" to save the change. A "Modify registration" button should now appear to let you send this new data to the remote producer:
Click on this new button and, if everything went well and your updated registration has been accepted by the remote producer, you should see something similar to:
You can now check the list of available portlets from the
bea
provider and verify that
new portlets are now available:
It can also happen that a producer administrator decided to require more information from registered
consumers. In this case, invoking operations on the producer will fail with an
OperationFailedFault
. GateIn 3.0 will attempt to help you in this
situation. Let's walk through an example using the
self
producer. Let's assume that
registration is required without any registration properties (as is the case by default). If you go to
the configuration screen for this producer, you should see:
Now suppose that the administrator of the producer now requires a value to be provided for an
email
registration property. We will actually see how to do perform this operation
in GateIn 3.0 when we examine how to configure Portal's producer inSection 8.8, “Configuring GateIn 3.0's WSRP Producer”.
Operations with this producer will now fail. If you suspect that a registration modification is required,
you should go to the configuration screen for this remote producer and refresh the information held by
the consumer by pressing "Refresh & Save":
As you can see, the configuration screen now shows the currently held registration information and
the expected information from the producer. Enter a value for the
email
property
and then click on "Modify registration". If all went well and the producer accepted your new registration
data, you should see something similar to:
OperationFailedFault
as it is the generic exception returned by
producers if something didn't quite happen as expected during a method invocation. This means that
OperationFailedFault
can be caused by several different reasons, one
of them being a request to modify the registration data. Please take a look at the log files to see
if you can gather more information as to what happened. WSRP 2 introduces an exception that is
specific to a request to modify registrations thus reducing the ambiguity that currently exists.
Several operations are available from the consumer list view of the WSRP configuration portlet:
The available operations are:
There are rare cases where it might be required to erase the local information without being able to deregister first. This is the case when a consumer is registered with a producer that has been modified by its administrator to not require registration anymore. If that ever was to happen (most likely, it won't), you can erase the local registration information from the consumer so that it can resume interacting with the remote producer. To do so, click on "Erase local registration" button next to the registration context information on the consumer configuration screen:
Warning: This operation is dangerous as it can result in inability to interact with the remote producer if invoked when not required. A warning screen will be displayed to give you a chance to change your mind:
You can configure the behavior of Portal's WSRP Producer by using the WSRP administration interface, which
is the preferred way, or by editing the
conf/config.xml
file found in
portal-wsrp.sar
. Several aspects can be modified with respects to whether
registration is required for consumers to access the Producer's services. An XML Schema for the
configuration
format is available atjboss-portal.sar/portal-wsrp.sar/xsd/jboss-wsrp-producer_2_6.xsd
,
while a (legacy) DTD is available at
jboss-portal.sar/portal-wsrp.sar/dtd/jboss-wsrp-producer_2_6.dtd
The default producer configuration is to require that consumers register with it before providing access its
services but does not require any specific registration properties (apart from what is mandated by the
WSRP standard). It does, however, require consumers to be registered before sending them a full service
description. This means that our WSRP producer will not provide the list of offered portlets and other
capabilities to unregistered consumers. The producer also uses the default
RegistrationPolicy
paired with the default
RegistrationPropertyValidator
. We will look into property
validators in greater detail later inSection 8.8.3, “Registration configuration”. Suffice to say for now
that this allows users to customize how Portal's WSRP Producer decides whether a given registration property
is valid or not.
GateIn 3.0 2.6.3 introduces a web interface to configure the producer's behavior. You can access it by clicking on the "Producer Configuration" tab of the "WSRP" page of the "admin" portal. Here's what you should see with the default configuration:
As would be expected, you can specify whether or not the producer will send the full service description to
unregistered consumers, and, if it requires registration, which
RegistrationPolicy
to
use (and, if needed, whichRegistrationPropertyValidator
), along with required
registration property description for which consumers must provide acceptable values to successfully
register.
In order to require consumers to register with Portal's producer before interacting with it, you need to configure Portal's behavior with respect to registration. Registration is optional, as are registration properties. The producer can require registration without requiring consumers to pass any registration properties as is the case in the default configuration. Let's configure our producer starting with a blank state:
We will allow unregistered consumers to see the list of offered portlets so we leave the first checkbox ("Access to full service description requires consumers to be registered.") unchecked. We will, however, specify that consumers will need to be registered to be able to interact with our producer. Check the second checkbox ("Requires registration. Modifying this information will trigger invalidation of consumer registrations."). The screen should now refresh and display:
You can specify the fully-qualified name for your
RegistrationPolicy
and
RegistrationPropertyValidator
there. We will keep the default value. See
Section 8.8.3.1, “Customization of Registration handling behavior”
for more details. Let's add, however, a registration property called
email
. Click "Add property" and enter the appropriate information in the fields,
providing a description for the registration property that can be used by consumers to figure out its
purpose:
Press "Save" to record your modifications.
Registration handling behavior can be customized by users to suit their Producer needs. This is
accomplished by providing an implementation of the
RegistrationPolicy
interface. This interface defines methods that are called by Portal's Registration service so that
decisions can be made appropriately. A default registration policy that provides basic
behavior is provided and should be enough for most user needs.
While the default registration policy provides default behavior for most registration-related aspects,
there is still one aspect that requires configuration: whether a given value for a registration property
is acceptable by the WSRP Producer. This is accomplished by plugging a
RegistrationPropertyValidator
in the default registration policy. This allows users to define their own validation mechanism.
Please refer to the
Javadoc™
for
org.jboss.portal.registration.RegistrationPolicy
and
org.jboss.portal.Registration.policies.RegistrationPropertyValidator
for more
details on what is expected of each method.
Defining a registration policy is required for the producer to be correctly configured. This is accomplished by specifying the qualified class name of the registration policy. Since we anticipate that most users will use the default registration policy, it is possible to provide the class name of your custom property validator instead to customize the default registration policy behavior. Note that property validators are only used by the default policy.
The lack of conformance kit and the wording of the WSRP specification leaves room for differing interpretations, resulting in interoperability issues. It is therefore possible to encounter issues when using consumers from different vendors. We have experienced such issues and have introduced a way to relax the validation that our WSRP producer performs on the data provided by consumers to help with interoperability by accepting data that would normally be invalid. Note that we only relax our validation algorithm on aspects of the specification that are deemed harmless such as invalid language codes.
By default, the WSRP producer is configured in strict mode. If you experience issues with a given consumer, you might want to try to relax the validation mode. This is accomplished by unchecking the "Use strict WSRP compliance." checkbox on the Producer configuration screen.