Copyright © 1999-2008 UCS - unique computing solutions gmbh
Table of Contents
List of Figures
List of Tables
List of Examples
Managing and operating on hierarchical data structures is an issue in many projects, alongside with security concerns, versioning and querying. Till now, neither open nor closed source projects or frameworks offer possibilities to dynamically define and manipulate the structure of hierarchical data in a scope like [fleXive]. Hence the urge to provide a solution that combines flexibility, security and performance in one customizable package.
[fleXive] is a JavaEE 5 open-source (LGPL) framework for the development of complex and evolving (web-)applications. It speeds up development as it handles numerous important application issues and keeps your application flexible over the development-cycle. It is based on the lastest industry-standards like EJB 3, JSF, etc.
The [fleXive] backend application is an optional application built on top of the framework and is licensed under the GNU General Public License (GPL). It helps you to visually manage most aspects of [fleXive] - like defining data structures, building queries, manage users and security, etc.
[fleXive] concentrates on enterprise-scale content storage and retrieval, and includes comprehensive JSF support for displaying and manipulating these contents in web applications. The runtime environment can be included in existing JavaEE applications, but you can also build new applications and package them into stand-alone JavaEE applications. Our emphasis lies on the runtime environment, so if you are looking e.g. for tool-driven JSF development, take a look at the popular JBoss Seam framework and embed the [fleXive] runtime environment in that project.
The goal of [fleXive] is to relieve you from many tendious and repetitive programming tasks when building secure, data-centric (web-)applications.
If you are new to [fleXive] please use the following tools to get started:
The [fleXive] website will provide you with top-level information about [fleXive]. Have a look at the "Explore [fleXive]" section.
This reference documentation will provide all information to use [fleXive] for the development of your application.
In the [fleXive] wiki you will find more tutorials, HowTos, FAQs, samples and developer infos
For questions, use the [fleXive] forum and mailinglists. We also provide a issue trackings system for bug reports and feature requests. In case you are interested in the development of [fleXive], have a look at the Development Section on the [fleXive] website and join the developer mailinglist.
[fleXive] is a Professional Open Source project. UCS - unique computing solutions gmbh (http://www.ucs.at) offers commercial development support and training for [fleXive].
Table of Contents
Administrator privileges on your machine
The Sun Java 6 Platform, Standard Edition Development Kit
JAVA_HOME and JDK_HOME
environment variable to your JDK root directory (e.g.
JAVA_HOME=/usr/lib/jvm/java-6-sun
on Linux,
C:\Java\jdk1.6.0_03
on Windows)
An application server ( JBoss or Glassfish) with EJB3 support
${jboss.home}
denotes your JBoss installation
directory)
${glassfish.home}
denotes your Glassfish installation directory)
A database (at the moment only MySql is supported)
Download and install MySql 5.0 or later
Currently, choosing UTF-8 as the default charset causes issues with the database setup scripts. Use the default setting (Latin1) instead.
ImageMagick version 6.3.4 or later (optional)
Apache ANT 1.7 and the ant-optional package
A [fleXive] binary distribution or the [fleXive] source code, if you want to build [fleXive] from source
If you are interested in developing [fleXive], you will need the [fleXive] source code and an IDE like Eclipse or a proprietary solution.
The following sections are good starting points for future [fleXive] developers:
We provide the current generated [fleXive] API Documentation for you on our website.
The following required libraries have to be deployed:
Copy the following file from the
extlib
directory of your [fleXive] binary distribution to
${jboss.home}/server/default/lib:
mysql-connector-java-*.jar
(Alternatively you can download the latest stable
MySQL JDBC
driver)
Create and copy the datasource config file
flexive-ds.xml
to
${jboss.home}/server/default/deploy/
and adapt to your datasource(s)
or simply copy it from the [fleXive] source tree, if available.
In the
flexive-ds.xml
configure your database connections as follows:
For the
flexive
database
<!-- transactional datasource --> <xa-datasource> <jndi-name>jdbc/flexiveDivision1</jndi-name> <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class> <!-- Note: "&" has to be used instead of "&" for parameters --> <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</xa-datasource-property> <user-name>root</user-name> <password>a</password> </xa-datasource> <!-- non-transactional datasource for database structure patches, Quartz, etc. --> <no-tx-datasource> <jndi-name>jdbc/flexiveDivision1NoTX</jndi-name> <driver-class>com.mysql.jdbc.Driver</driver-class> <!-- Note: "&" has to be used instead of "&" for parameters --> <connection-url>jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</xa-datasource-property> <user-name>root</user-name> <password>a</password> </no-tx-datasource>
For the
flexiveConfiguration
database
<xa-datasource> <jndi-name>jdbc/flexiveConfiguration</jndi-name> <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class> <!-- Note: "&" has to be used instead of "&" for parameters --> <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/flexiveConfiguration?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</xa-datasource-property> <user-name>root</user-name> <password>a</password> </xa-datasource>
flexive-ds.xml
<!--
JBoss Datasource Configuration
See http://www.redhat.com/docs/manuals/jboss/jboss-eap-4.3/doc/Server_Configuration_Guide/html/Connectors_on_JBoss-Configuring_JDBC_DataSources.html
for detailed configuration infos
-->
<datasources>
<!--
transactional datasource, as configured in the global configuration
-->
<xa-datasource>
<jndi-name>jdbc/flexiveDivision1</jndi-name>
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
<!-- Note: "&" has to be used instead of "&" for parameters -->
<xa-datasource-property name="URL">jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</xa-datasource-property>
<user-name>root</user-name>
<password>a</password>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<no-tx-separate-pools/>
<!-- This disables transaction interleaving (which BTW, most DB vendors don't support) -->
<track-connection-by-tx/>
<isSameRM-override-value>false</isSameRM-override-value>
<!--pooling parameters-->
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>15</idle-timeout-minutes>
<!-- If you supply the usr/pw from a JAAS login module -->
<security-domain/>
<exception-sorter-class-name>
com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
</exception-sorter-class-name>
<valid-connection-checker-class-name>
com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
</valid-connection-checker-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</xa-datasource>
<!--
non-transactional datasource for database structure patches, Quartz, etc.
As per convention, non-transactional datasources have the same name like
transactional but use the suffix "NoTX"
-->
<no-tx-datasource>
<jndi-name>jdbc/flexiveDivision1NoTX</jndi-name>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<!-- Note: "&" has to be used instead of "&" for parameters -->
<connection-url>jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</connection-url>
<user-name>root</user-name>
<password>a</password>
<!--pooling parameters-->
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>15</idle-timeout-minutes>
<!-- If you supply the usr/pw from a JAAS login module -->
<security-domain/>
<exception-sorter-class-name>
com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
</exception-sorter-class-name>
<valid-connection-checker-class-name>
com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
</valid-connection-checker-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</no-tx-datasource>
<xa-datasource>
<jndi-name>jdbc/flexiveConfiguration</jndi-name>
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
<!-- Note: "&" has to be used instead of "&" for parameters -->
<xa-datasource-property name="URL">jdbc:mysql://localhost:3306/flexiveConfiguration?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8</xa-datasource-property>
<user-name>root</user-name>
<password>a</password>
<transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
<no-tx-separate-pools/>
<!-- This disables transaction interleaving (which BTW, most DB vendors don't support) -->
<track-connection-by-tx/>
<isSameRM-override-value>false</isSameRM-override-value>
<!--pooling parameters-->
<min-pool-size>5</min-pool-size>
<max-pool-size>20</max-pool-size>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>15</idle-timeout-minutes>
<!-- If you supply the usr/pw from a JAAS login module -->
<security-domain/>
<exception-sorter-class-name>
com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
</exception-sorter-class-name>
<valid-connection-checker-class-name>
com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
</valid-connection-checker-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</xa-datasource>
</datasources>
To set up the databases do the following
Adapt the
database.properties
file located in your
flexive-dist
directory of your [fleXive] distribution to your needs:
# This is the database configuration file used by the flexive setup tasks. # # Enter the settings for your database server connection to be used for development. # The server host or IP database.host=localhost # The server port database.port=3306 # The user name and password to be used for creating flexive database structures database.username=root database.password=a
Change to the
flexive-dist
directory of your [fleXive] distribution and run
ant db.create db.config.create
You will be prompted to enter a name for your division database (simply hit enter to keep
flexive).
Warning: Existing databases will be deleted.
Change to the
flexive-dist
directory of your [fleXive] distribution and run
ant ear. This will create the
flexive.ear
file inside the
flexive-dist
directory. Copy
flexive.ear
to
${jboss.home}/server/default/deploy.
Currently JBoss (4.2) uses the Java's default maximum heap size,
which means that [fleXive] will probably run out of memory during the
first startup. You have to set the maximum heap size using
-Xmx
to a larger value, at least 256m.
On Linux execute
export JAVA_OPTS="-Xms128m -Xmx512m"
On Windows execute
SET JAVA_OPTS=-Xms128m -Xmx512m
Change to the
${jboss.home}/bin
directory.
On Linux launch JBoss using
sh run.sh -c default -Djboss.bind.address=0.0.0.0
On Windows launch JBoss using
start run.bat -c default -Djboss.bind.address=0.0.0.0
To access the backend point your browser to http://localhost:8080/flexive/adm/
Alternatively you can specify your server's IP address, which is supported by default, too.
The default superuser's username and password for the Backend Administration are:
username: supervisor, password: supervisor
Extend
${jboss.home}/server/default/conf/jboss-log4j.xml
to configure logging:
<!-- Limit flexive -->
<category name="com.flexive">
<priority value="INFO"/>
</category>
<!-- Limit ajax4jsf -->
<category name="org.ajax4jsf">
<priority value="WARN"/>
</category>
Optional if you need JAAS support: Extend
${jboss.home}/server/default/conf/login-config.xml
with the following entry:
<application-policy name = "FxLogin">
<authentication>
<login-module code="com.flexive.core.security.FxDefaultLogin" flag="required"></login-module>
</authentication>
</application-policy>
Optional step: To use an external service deployer for JBoss Cache,
create the cache service file
99_JBossCacheJNDI42-service.xml
in
${jboss.home}/server/default/deploy/
or simply copy it from the [fleXive] source tree, if available:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<loader-repository>com.flexive:archive=flexive.ear
</loader-repository>
<!-- ========================================================== -->
<!-- Clustered SFSB cache config for use with JBoss Cache 1.4.x -->
<!-- ========================================================== -->
<mbean code="org.jboss.cache.jmx.CacheJmxWrapper"
name="jboss.cache:service=JNDITreeCache">
<!--<depends>jboss:service=Naming</depends>-->
<!--<depends>jboss:service=TransactionManager</depends>-->
<attribute name="TransactionManagerLookupClass">org.jboss.cache.transaction.GenericTransactionManagerLookup</attribute>
<!--
Node locking scheme :
PESSIMISTIC (default)
OPTIMISTIC
-->
<attribute name="NodeLockingScheme">PESSIMISTIC</attribute>
<attribute name="ClusterName">[fleXive]-Cache-JNDI</attribute>
<!--
Node locking level : SERIALIZABLE
REPEATABLE_READ (default)
READ_COMMITTED
READ_UNCOMMITTED
NONE
-->
<attribute name="IsolationLevel">READ_COMMITTED</attribute>
<!-- Valid modes are LOCAL
REPL_ASYNC
REPL_SYNC
-->
<attribute name="CacheMode">REPL_ASYNC</attribute>
<!-- We want to activate/inactivate regions as beans are deployed -->
<attribute name="UseRegionBasedMarshalling">true</attribute>
<!-- Must match the value of "useRegionBasedMarshalling" -->
<attribute name="InactiveOnStartup">true</attribute>
<!--
JGroups protocol stack config in XML format.
If your CacheMode is set to REPL_SYNC we recommend you comment
out the FC (flow control) protocol
On Windows machines, because of the media sense feature
being broken with multicast (even after disabling media sense)
set the UDP.loopback attribute to true
-->
<attribute name="ClusterConfig">
<config>
<UDP mcast_addr="${flexive.cluster.udpGroup:229.1.2.5}"
mcast_port="${jboss.ejb3sfsbpartition.mcast_port:45557}"
tos="8"
ucast_recv_buf_size="20000000"
ucast_send_buf_size="640000"
mcast_recv_buf_size="25000000"
mcast_send_buf_size="640000"
loopback="false"
discard_incompatible_packets="true"
enable_bundling="false"
max_bundle_size="64000"
max_bundle_timeout="30"
use_incoming_packet_handler="true"
use_outgoing_packet_handler="false"
ip_ttl="${jgroups.udp.ip_ttl:2}"
down_thread="false" up_thread="false"/>
<PING timeout="2000"
down_thread="false" up_thread="false" num_initial_members="3"/>
<MERGE2 max_interval="100000"
down_thread="false" up_thread="false" min_interval="20000"/>
<FD_SOCK down_thread="false" up_thread="false"/>
<FD timeout="10000" max_tries="5" down_thread="false" up_thread="false" shun="true"/>
<VERIFY_SUSPECT timeout="1500" down_thread="false" up_thread="false"/>
<pbcast.NAKACK max_xmit_size="60000"
use_mcast_xmit="false" gc_lag="0"
retransmit_timeout="300,600,1200,2400,4800"
down_thread="false" up_thread="false"
discard_delivered_msgs="true"/>
<UNICAST timeout="300,600,1200,2400,3600"
down_thread="false" up_thread="false"/>
<pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000"
down_thread="false" up_thread="false"
max_bytes="400000"/>
<pbcast.GMS print_local_addr="true" join_timeout="3000"
down_thread="false" up_thread="false"
join_retry_timeout="2000" shun="true"
view_bundling="true"
view_ack_collection_timeout="5000"/>
<FC max_credits="2000000" down_thread="false" up_thread="false"
min_threshold="0.10"/>
<FRAG2 frag_size="60000" down_thread="false" up_thread="false"/>
<pbcast.STATE_TRANSFER down_thread="false" up_thread="false" use_flush="false"/>
</config>
</attribute>
<!-- The max amount of time (in milliseconds) we wait until the
initial state (ie. the contents of the cache) are retrieved from
existing members.
-->
<attribute name="InitialStateRetrievalTimeout">17500</attribute>
<!-- Number of milliseconds to wait until all responses for a
synchronous call have been received.
-->
<attribute name="SyncReplTimeout">17500</attribute>
<!-- Max number of milliseconds to wait for a lock acquisition -->
<attribute name="LockAcquisitionTimeout">15000</attribute>
<!-- Specific eviction policy configurations. -->
<attribute name="EvictionPolicyConfig">
<config>
<attribute name="policyClass">org.jboss.cache.eviction.LRUPolicy</attribute>
<attribute name="wakeUpIntervalSeconds">5</attribute>
<name>flexiveMain</name>
<!-- So default region would never timeout -->
<region name="/_default_">
<attribute name="maxNodes">0</attribute>
<attribute name="timeToLiveSeconds">0</attribute>
</region>
<!-- cache region for contents -->
<region name="/Division1/FxContent">
<attribute name="maxNodes">1000</attribute>
<attribute name="timeToLiveSeconds">1000</attribute>
</region>
<!-- cache region for user configuration -->
<region name="/Division1/userConfig">
<attribute name="maxNodes">10000</attribute>
<attribute name="timeToLiveSeconds">1000</attribute>
</region>
</config>
</attribute>
</mbean>
</server>
If you want to use the external cache deplyoment you have to apply another modification:
Due to classloading issues ([fleXive] uses JBoss Cache 2.x, while
JBoss 4.2 uses JBoss Cache 1.x) you need to modify the deployment scanner in
${jboss.home}/server/default/conf/jboss-service.xml:
replace the entry
<attribute name="URLComparator">org.jboss.deployment.DeploymentSorter</attribute>with
<attribute name="URLComparator">org.jboss.deployment.scanner.AlphaNumericDeploymentSorter</attribute>This ensures that the cache service is started after flexive.ear is deployed and thus can use its cache implementation classes (this deployment scanner allows to influence the deployment order with a numeric prefix, otherwise it's the same as the default one).
Flexive needs several libraries to be deployed to the application server's internal
lib
directories. Execute these steps once glassfish has been setup.
We provide an ant task for you which copies all libraries needed to deploy [fleXive] on Glassfish V2 to the specified directory.
Change to the
flexive-dist
directory of your [fleXive] distribution and run
ant glassfish.libs.
You will be prompted to specify your
${glassfish.home}/domains/domain1/lib/ext
directory.
The
ant
task will copy all required libraries to this directory.
Copy the
jbosscache-core.jar
from the
extlib
directory of your [fleXive] binary distribution to
${glassfish.home}/lib
To set up the databases, follow these instructions.
Setting up Glassfish
Go to the target installation directory
java -Xmx256m -jar <<path-to-downloaded-jar>>
extracts the installation JAR to
./glassfish
cd glassfish
On Unix: chmod -R +x lib/ant/bin
Execute the
setup.xml
buildfile
Unix: lib/ant/bin/ant -f setup.xml
Windows: lib\ant\bin\ant -f setup.xml
Starting Glassfish
bin/asadmin start-domain domain1 starts the Glassfish server
http://localhost:4848/ and login using default credentials admin/adminadmin.
Additional information
To stop the Glassfish instance execute ${glassfish.home}/bin/asadmin stop-domain domain1
Admin UI Port: 4848
HTTP Port: 8080
HTTPS Port: 8181
Three connection pools, two for the division (transactional and non-transactional) and one for the configuration database, have to be set up.
Copy and paste the following data-source definitions to a file (e.g.
flexive-ds.xml)
and update your database connection settings:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems Inc.//DTD Application Server 9.0 Domain//EN" "sun-resources_1_3.dtd">
<resources>
<!--
Glassfish datasource configuration
To add these datasource, start Glassfish v2, and execute
${glassfish.home}/bin/asadmin add-resources /path/to/flexive-ds.xml
-->
<!-- Configure the global configuration datasource -->
<jdbc-connection-pool
name="flexiveConfiguration"
datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"
res-type="javax.sql.XADataSource">
<property name="user" value="root"/>
<property name="password" value="a"/>
<property name="url" value="jdbc:mysql://localhost:3306/flexiveConfiguration?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8"/>
</jdbc-connection-pool>
<jdbc-resource pool-name="flexiveConfiguration" jndi-name="jdbc/flexiveConfiguration" enabled="true" object-type="user"/>
<!-- Configure the first flexive division -->
<jdbc-connection-pool
name="flexiveDivision1"
datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"
res-type="javax.sql.XADataSource">
<property name="user" value="root"/>
<property name="password" value="a"/>
<property name="url" value="jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8"/>
</jdbc-connection-pool>
<jdbc-resource pool-name="flexiveDivision1" jndi-name="jdbc/flexiveDivision1" enabled="true" object-type="user"/>
<!-- Configure the first flexive division non-XA datasource -->
<jdbc-connection-pool
name="flexiveDivision1NoTX"
datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource"
res-type="javax.sql.DataSource">
<property name="user" value="root"/>
<property name="password" value="a"/>
<property name="url" value="jdbc:mysql://localhost:3306/flexive?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8"/>
</jdbc-connection-pool>
<jdbc-resource pool-name="flexiveDivision1NoTX" jndi-name="jdbc/flexiveDivision1NoTX" enabled="true" object-type="user"/>
</resources>
While Glassfish is running, execute ${glassfish.home}/bin/asadmin add-resources /path/to/flexive-ds.xml to create the connection pools and JDBC resources. You have to pass the absolute path to your XML file, otherwise Glassfish looks in its own config directory.
Should you need to edit or reset your datasources, you can do so in the Glassfish administration console under →
Change to the
flexive-dist
directory of your [fleXive] distribution and run
ant ear. This will create the
flexive.ear
file inside the
flexive-dist
directory.
Using the autodeploy directory:
Copy the
flexive.ear
to
${glassfish.home}/domains/domain1/autodeploy
Using the Admin UI:
In the Admin Console, go to →
Click
Deploy...
and upload the
flexive.ear
file.
Click
OK
To access the backend point your browser to http://localhost:8080/flexive/adm/ Alternatively you can specify your server's IP address, which is supported by default, too. The default superuser's username and password for the Backend Administration are username: supervisor, password: supervisor.
This procedure is for those building [fleXive] from the source code.
build.properties.sample
file located in the root directory of the
[fleXive] source code
to
build.properties.
jboss.home=/path/to/jboss
# path to deploy the ear
deploy.ear.path=${jboss.home}/server/all/deploy
# path to deploy needed runtime libs like emma.jar
deploy.lib.path=${jboss.home}/server/all/lib
# path to the embedded jboss ejb3 container neededfor unit testing
jboss.embedded.path=${jboss.home}/jboss-embeddable/
# (...)
ant db.update
before
ant all
in the root directory of the
[fleXive] source code,
where the
build.xml
file
resides.
This will recreate all configured databases and destroy existing data.
To deploy the ear execute
ant deploy
Don't forget to set your
${jboss.home}
variable in the
build.properties
file properly!
JDK_HOME
and
JAVA_HOME
variables accordingly as explained in section
Prerequisites.
If there are multiple JBoss installations running in your local network you need
to assign a different partition UDP group to each cluster! This can be done by adding
-Djboss.partition.udpGroup=230.2.3.4
(or any other multicast address you choose thats available)
to the start script/command shown in
Starting [fleXive].
In order to run JBossCache on Linux IPv4 needs to be defined as the preferred IP stack. In the Glassfish admin console, go to → → and add the following option:
-Djava.net.preferIPv4Stack=true
If an ant-task fails, make sure you have Apache ANT 1.7 installed.
If you get the following warning:
Problem: failed to create task or type javacc when executing ant
make sure you installed the ant-optional package.
In this chapter we will get hands-on and create a few demo applications with [fleXive]. These applications are part of the binary and source distributions, but you can also download them as separate projects:
To run any of these, unzip them to a local directory. Unzip the [fleXive] distribution from the
download page in the same directory, remove the precompiled examples from
flexive-dist/applications,
and execute
ant
in the example directory. You can then deploy the
dist/[example-name].ear
file to your application server. For example (using Linux):
$ unzip flexive-dist.zip $ unzip example-helloworld.zip $ ls flexive-dist helloworld $ cd helloworld $ ant $ ls dist helloworld.ear helloworld-shared.jar helloworld.war
Before setting out on your own, you might also want to read Chapter 4, Writing [fleXive] applications , which also explains how to use Apache Maven as your build tool.
Our first [fleXive] application implements a very simple blogging system with an input mask and a view for the submitted entries. We assume you have a flexive installation up and running on your local or on a remote machine. If not, follow the instructions in Chapter 2, Installing [fleXive] . The complete source code can be found here or in the subversion repository under src/examples/helloworld.
First we create a new [fleXive] project: go to the directory where you unpacked the distribution,
and execute
ant project.create.
You will be prompted for the name of the new project. Enter
helloworld
and confirm your input. If all went well, the output should look like the following:
/tmp/flexive-dist$ ant project.create
Buildfile: build.xml
check:
project.create:
[input] Name of the project you want to create:
helloworld
[flexive]
[flexive] Please confirm your input:
[flexive] Project name: helloworld
[flexive] Base directory: ../helloworld
[flexive]
[input] Are these settings correct? ([y], n)
y
[mkdir] Created dir: /tmp/helloworld
[copy] Copying 13 files to /tmp/helloworld
[copy] Copied 29 empty directories to 6 empty directories under /tmp/helloworld
[copy] Copying 1 file to /tmp/helloworld
[copy] Copying 1 file to /tmp/helloworld
[echo] Project helloworld created successfully. The project root directory is
[echo] ../helloworld
BUILD SUCCESSFUL
The new project is created in the parent directory of the distribution, i.e. in
../helloworld,
and looks like this:
flexive-dist
|-- ...
helloworld
|-- build.xml
|-- database.properties
|-- lib
|-- resources
| |-- META-INF
| | |-- faces-config.xml
| | `-- web.xml
| |-- messages
| |-- scripts
| | |-- library
| | |-- runonce
| | `-- startup
| `-- templates
|-- src
| `-- java
| |-- ejb
| | `-- ...
| |-- shared
| | `-- ...
| `-- war
| `-- ...
`-- web
`-- index.xhtml
helloworld/src/java
contains the Java sources of the application.
helloworld/resources/scripts
contains [fleXive] scripts that will be used to setup the data structures needed by the application.
helloworld/web
contains the XHTML pages and the web configuration files.
We start with defining the data model of the application. Our blog entry consists of a
posting title and the actual text. To set up the data model when deploying the application,
we create a
run-once script
in the
resources/scripts/runonce/
directory,
hello001.groovy.
A straight-forward way of setting up [fleXive] structures is by using the
GroovyTypeBuilder
for [fleXive] types, using the Groovy scripting language.
import com.flexive.shared.scripting.groovy.* import com.flexive.shared.value.* import com.flexive.shared.structure.* new GroovyTypeBuilder().blogEntry(description: new FxString(true, "Blog Entry"), usePermissions: false) { //caption(assignment: "ROOT/CAPTION") entryTitle(multiplicity: FxMultiplicity.MULT_1_1, description: new FxString(true, "Title")) //
entryText(multiplicity: FxMultiplicity.MULT_1_1, description: new FxString(true, "Text"), multiline: true) //
}
|
The method call blogEntry(...) will create the [fleXive] type "BlogEntry" (type names are not case sensitive). For the first tutorial, we will disable [fleXive] security completely for this type by setting usePermissions to false. | |
|
Create a new property assignment for the blog entry type that is referenced with the path "/entryTitle". The multiplicity of 1..1 indicates a mandatory field, the description will be shown in the input forms. | |
|
The text property is created similar to the title, but we add a "multiline" option since we don't want to enter our blog text in a single line. |
The next step is to define a JSF bean that provides the blog postings
to be shown on the front page of our application. We create a managed bean under
src/java/war,
which will be automatically compiled and packaged into the WAR archive.
import com.flexive.faces.model.FxResultSetDataModel; import com.flexive.shared.exceptions.FxApplicationException; import com.flexive.shared.search.FxResultSet; import com.flexive.shared.search.SortDirection; import com.flexive.shared.search.query.SqlQueryBuilder; import javax.faces.model.DataModel; public class HelloWorldBean { private DataModel blogEntries; public DataModel getBlogEntries() throws FxApplicationException { if (blogEntries == null) { final FxResultSet result = new SqlQueryBuilder() .select("@pk", "entryTitle", "entryText", "created_at") //.type("blogEntry") .orderBy("created_at", SortDirection.DESCENDING) //
.getResult(); blogEntries = new FxResultSetDataModel(result); } return blogEntries; } }
|
We choose the columns to be selected for the result set:
| |
|
To display the newest postings first, we order by descending creation date
( |
For the web interface of the application we use JSF 1.2, using Facelets for creating the JSF views (i.e. we use plain XHTML templates instead of JSP). [fleXive] includes components for rendering and editing [fleXive] contents in JSF applications, although you could also use other web framework like Struts or Tapestry - you'd just miss the convenient UI components that allow a rapid creation of web applications based on [fleXive].
If you are not familiar with JSF tag libraries please refer to the Tag Library Documentation or try the JSF section of the Java EE 5 Tutorial.
The main page renders all available blog entries and shows a link to a form
for creating new entries. Facelets'
<ui:repeat/>
tag is used to iterate over the rows of the datamodel returned by the JSF bean we created in the
previous
section. A row of the datamodel provides indexed access to the columns selected in the
search query of the previous section.
In this case,
#{columns[0]}
would be the content primary key
(@pk),
#{columns[1]}
returns the entry title, and so on. The source for this file can be found under
web/index.xhtml.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fx="http://www.flexive.com/jsf/core">
<head>
<fx:includes/>
</head>
<body>
<h:outputLink value="create.xhtml">Create a new entry</h:outputLink>
<!-- Render available blog postings, as returned by helloWorldBean.blogEntries -->
<!-- The Facelets ui:repeat tag is used to iterate over arrays, lists and JSF datamodels -->
<ui:repeat value="#{helloWorldBean.blogEntries}" var="column">
<h3>
<!-- Entry title. Access the value of each column through the expression language
and the column index -->
#{column[1]}
</h3>
<pre>#{column[2]} <!-- Entry text -->
<i>#{column[3]}</i> <!-- Creation date -->
</pre>
</ui:repeat>
<p><h:outputLink
onclick="window.open('http://wiki.flexive.org/confluence/display/FX/Listing+blog+entries', 'In_depth_explanation', 'width=950,height=600,left=50,top=200,scrollbars=yes,resizable=yes');"
value="#">
What happens on this page?
</h:outputLink>
</p>
</body>
</html>
The input form for creating new postings is placed in
web/create.xhtml
and uses the
<fx:content/>
and
<fx:value/>
components provided by [fleXive] to create a simple input form for [fleXive] contents.
All you need to provide is the name of the type you want to use as template
(blogEntry)
and the references to the properties to be rendered
(entryTitle,
entryText).
Finally, a JSF command link is used to render a button for saving the entered data.
The save command is executed by a [fleXive] system bean,
fxContentViewBean.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fx="http://www.flexive.com/jsf/core">
<head>
<title>Flexive Helloworld Application</title>
<fx:includes/>
</head>
<body>
<h:form>
<!-- Display all JSF messages -->
<h:messages/>
<p><h:outputLink value="index.xhtml">Back to blog</h:outputLink></p>
<!-- The fx:content tag references our type "Blog Entry" -->
<fx:content typeName="blogEntry" var="entry">
<!-- This renders an html input field for the type's property "Entry Title" -->
<fx:value property="entryTitle"/>
<!-- This renders an html input field for the type's property "Entry Text" -->
<fx:value property="entryText"/>
<!-- Save content using the FxContentViewBean, pass the content instance
stored in component_content via f:setPropertyActionListener -->
<h:commandLink action="#{fxContentViewBean.save}">
<f:setPropertyActionListener target="#{fxContentViewBean.content}" value="#{entry_content}"/>
Publish
</h:commandLink>
</fx:content>
</h:form>
<p><h:outputLink
onclick="window.open('http://wiki.flexive.org/confluence/display/FX/Creating+a+blog+entry', 'In_depth_explanation', 'width=950,height=600,left=50,top=200,scrollbars=yes,resizable=yes');"
value="#">
What happens on this page?
</h:outputLink>
</p>
</body>
</html>
In the
resources/META-INF
directory you find the configuration files for the (web) application. Since the provided
web.xml
already uses sensible defaults for running [fleXive] applications, all you need to do
is register your JSF bean in
resources/META-INF/faces-config.xml
and add navigation rules between the overview and the create page:
<managed-bean> <managed-bean-name>helloWorldBean</managed-bean-name> <managed-bean-class>HelloWorldBean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> <!-- A navigation rule for our input mask, if the content is saved successfully, "success" is returned, which leads to a direct rendering and response of the index.xhtml view. The entries made via the input mask are shown in this view. --> <navigation-rule> <from-view-id>/create.xhtml</from-view-id> <navigation-case> <display-name>success</display-name> <from-outcome>success</from-outcome> <to-view-id>/index.xhtml</to-view-id> </navigation-case> </navigation-rule>
To compile and deploy the framework and the example application simply run
ant in the project directory (helloworld). If the compilation was successful,
you find your application packaged under
dist/helloworld.ear.
You can then deploy this EAR to your application server, assuming that you followed the
installation instructions.
If you already have a [fleXive] EAR deployed in this instance, be sure to undeploy it before
you deploy
helloworld.ear.
If you need to setup or reset the database schema, update your database connection settings in
hello-world/database.properties
or
flexive-dist/database.properties
and run
ant db.create
in the corresponding directory.
After deploying the application, point your browser to http://localhost:8080/flexive/adm/ to open the backend administration application (default credentials: supervisor/supervisor), or http://localhost:8080/helloworld/ to open the application. Congratulations! You should see a link to the input mask.
You can find the datastructures of the hello-world application in the backend administration's structure editor. Log on to the backend administration and expand the tab named "Structure". flexive/adm/ You should be able to see the type Blog Entry in the tree. Expand it and click on one of the properties. In the "Edit Property Assignment" tab in the right frame you can see the properties originally set in the hello001.groovy script.

Now that you are familiar with some basic concepts of [fleXive], we are ready to explore more features. The first tutorial application introduces
binary support in [fleXive] contents,
combined create/edit forms, as well as
a more sophisticated way of rendering results, like automatic preview images of contents.
Our task for this tutorial is to create a centralized document store that allows us to store documents, images or videos and provides a web-based user interface for it. In the first tutorial we will implement the basic functionality, and then further refine and extend the features in following tutorials. Figure 3.1, “The document store application, version 1” shows the end result of the first tutorial, with the help of some stylesheets that can be found in the tutorial source tree.
First we create new project called
tutorial01-documentstore.
Use your local installation of the [fleXive] distribution as described in
the section called “Your first [fleXive] application”
to create a new project. The complete source code can be found
here.
We start by defining our data model used for storing documents. We assign the standard caption property
/caption
for document captions, and add a
Binary
property. We create the type in the
run-once script
resources/scripts/runonce/tutorial001.groovy:
/**
* Initialization script for the tutorial01 application.
*
* @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
* @version $Rev: 461 $
*/
import com.flexive.shared.scripting.groovy.*
import com.flexive.shared.value.*
import com.flexive.shared.structure.*
new GroovyTypeBuilder().document01(
description: new FxString(true, "Tutorial document 01"),
usePermissions: false)
{
// assign the root caption under /caption
caption(assignment: "ROOT/CAPTION")
// store the mandatory binary under /file
file(dataType: FxDataType.Binary,
multiplicity: FxMultiplicity.MULT_1_1,
description: new FxString(true, "File"))
}
The most interesting parts of this tutorial are the overview and upload pages. On the first we display all uploaded document objects, the latter allows the user to upload new files or edit existing ones.
The index page of the application renders a link to an empty upload form for uploading new document, and also renders all document objects already uploaded. Note that in the first version of the tutorial, security is completely disabled, thus every user can see and edit all document objects in the system.
Similar to the
helloworld
example application, we iterate over the rows of the result data model using
<ui:repeat>.
But this time, we render the result using the
<fx:resultValue>
tag, which essentially renders read-only
<fx:valueInput>
components for more sophisticated output formatting.
For the document browser, we select four columns in our content query:
@pk,
document01/file,
caption, and
created_at.
When we pass a value of the second column (document01/file) to the
<fx:resultValue>
component,
it actually renders an inline image that displays a preview of the uploaded file. For image
data types, this is a thumbnail, for other types like documents or presentations, it is
an icon corresponding to the document type.
So to render a list of thumbnail preview images, we write the following:
<ui:repeat var="row" value="#{tutorialBean.documents}"> <fx:resultValue value="#{row[1]}"/> </ui:repeat>
We can still write the result values as literal JSF-EL expressions when necessary, so for example to
add the caption after each image it would be sufficient to write
#{row[2]}. However, the
<fx:resultValue>
tag adds extra functionality like date formatting or support for inline HTML
properties (otherwise HTML contained in an
FxHTML
would be escaped by Facelets), so it's generally a good idea to use
<fx:resultValue>
for any result value rendered in the response.
Below each document object, we want to provide a link that opens the object in the
edit form. We could either pass the primary key selected in the first column through
a bean property, or pass
the content instance itself through the fxContentViewBean and supply it to the
<fx:content>
component.
JSF's
setPropertyActionListener
is a convenient way of setting bean properties when a command link or button is triggered.
The Edit link looks like this:
<h:commandLink action="edit" styleClass="editButton"> <f:setPropertyActionListener target="#{fxContentViewBean.content}" value="#{fxSystemBean.content[row[0]]}"/> Edit... </h:commandLink>
#{fxSystemBean.content[row[0]]}
returns the
FxContent
instance for the given primary key. Note that the JSF listener will only be fired
when the user actually clicks on the commandLink, thus the rather expensive load operation
will only be performed if the user clicks on the edit link.
Our
web/index.xhtml
lists all available documents and adds a link to the upload form.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fx="http://www.flexive.com/jsf/core">
<head>
<!-- Add flexive includes -->
<fx:includes/>
<!-- Add our own stylesheet for the result page -->
<link rel="stylesheet" type="text/css" href="css/tutorial01.css"/>
</head>
<body>
<!-- Output JSF error or info messages here -->
<h:messages globalOnly="true"/>
<p class="message">
Welcome to tutorial 1 - the first version of our document data store.
</p>
<!-- The main menu -->
<ul id="mainmenu">
<li>
<h:outputLink value="upload.xhtml">Upload document</h:outputLink>
</li>
</ul>
<!-- Render all available document objects, provided by #{tutorialBean.documents} -->
<h:form id="frm">
<ul class="documents">
<!-- Iterate over all document objects -->
<ui:repeat var="row" value="#{tutorialBean.documents}">
<li>
<!-- Render the file (preview image) -->
<fx:resultValue id="preview" value="#{row[1]}"/>
<!-- Render the document caption -->
<span class="caption">
<fx:resultValue id="caption" value="#{row[2]}"/>
</span>
<!-- Add an edit button below the image -->
<h:commandLink action="edit" styleClass="editButton">
<!--
Load the content instance of the current row and store it in
#{fxContentViewBean.content}. The edit page will then use this
content instance.
Note that this listener will only be fired when the user actually
clicks on the commandLink.
-->
<f:setPropertyActionListener target="#{fxContentViewBean.content}"
value="#{fxSystemBean.content[row[0]]}"/>
Edit...
</h:commandLink>
</li>
</ui:repeat>
</ul>
</h:form>
<p style="clear:both; padding-top:25px;"><h:outputLink
onclick="window.open('http://wiki.flexive.org/confluence/display/FX/Generating+thumbnails', 'In_depth_explanation', 'width=950,height=600,left=50,top=200,scrollbars=yes,resizable=yes');"
value="#">
What happens on this page?
</h:outputLink>
</p>
</body>
</html>
The upload form allows both to create new document objects by uploading files and editing existing ones. It works exactly as the form for entering new blog posts in the helloworld tutorial, except that we specify an explicit content instance that may be set from an edit link on the overview page:
<fx:content typeName="document01" content="#{fxContentViewBean.content}" var="document">
When this page is opened through the "Upload" link on the front page (or by entering the URL to
/upload.xhtml
in the browser location bar),
#{fxContentViewBean.content}
evaluates to
null
and a new content instance of type
document01
will be initialized. Otherwise, the content instance from retrieved from the
fxContentViewBean will be edited. A call to
fxContentViewBean.save
creates or updates the content instance in the database.
The upload form renders two new input components:
a file upload form, and
a multilanguage input for the caption property.
For file uploads to work, you need to set the
enctype
attribute of the HTML form to
multipart/form-data:
<h:form enctype="multipart/form-data">
We then create the upload page under
web/upload.xhtml
and add a basic content editor for creating new documents. The input component
supports binary properties, but you have to set the form encoding to
multipart/form-data.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fx="http://www.flexive.com/jsf/core">
<head>
<!-- Add flexive includes -->
<fx:includes/>
<!-- Add our own stylesheet for the result page -->
<link rel="stylesheet" type="text/css" href="css/tutorial01.css"/>
</head>
<body>
<h:messages globalOnly="true"/>
<h:form enctype="multipart/form-data">
<ul id="mainmenu">
<li>
<h:outputLink value="index.xhtml">Return to overview</h:outputLink>
</li>
</ul>
<!--
Create a new content of type document01 or edit an existing content instance if set in fxContentViewBean.
-->
<fx:content typeName="document01" content="#{fxContentViewBean.content}" var="document">
<fx:fieldSet legend="Document Upload">
<!-- Render the input form -->
<fx:value property="file"/>
<fx:value property="caption"/>
<!-- Render the submit button -->
<fx:formRow>
<h:commandButton action="#{fxContentViewBean.save}" value="Save">
<f:setPropertyActionListener target="#{fxContentViewBean.content}" value="#{document_content}"/>
<f:setPropertyActionListener target="#{fxContentViewBean.successMessage}"
value="Successfully saved the document."/>
</h:commandButton>
</fx:formRow>
</fx:fieldSet>
</fx:content>
</h:form>
<p><h:outputLink
onclick="window.open('http://wiki.flexive.org/confluence/display/FX/Uploading+documents', 'In_depth_explanation', 'width=950,height=600,left=50,top=200,scrollbars=yes,resizable=yes');"
value="#">
What happens on this page?
</h:outputLink>
</p>
</body>
</html>
Like in the previous tutorial, we add a simple JSF managed bean that provides our search results.
package com.flexive.examples.tutorial01;
import com.flexive.faces.model.FxResultSetDataModel;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.search.FxResultSet;
import com.flexive.shared.search.SortDirection;
import com.flexive.shared.search.query.SqlQueryBuilder;
import javax.faces.model.DataModel;
/**
* JSF managed bean for the tutorial01 application.
*
* @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
* @version $Rev: 469 $
*/
public class Tutorial01Bean {
private DataModel documents;
public DataModel getDocuments() throws FxApplicationException {
if (documents == null) {
final FxResultSet result = new SqlQueryBuilder()
.select("@pk", "document01/file", "caption", "created_at")
.type("document01")
.orderBy("created_at", SortDirection.DESCENDING)
.getResult();
documents = new FxResultSetDataModel(result);
}
return documents;
}
}
To serve the image URLs rendered by
<fx:resultValue>
to the browser, we need a servlet providing those images. For this purpose, you have to
add the [fleXive] thumbnail servlet to your
web.xml.
<servlet> <servlet-name>Thumbnail</servlet-name> <servlet-class>com.flexive.war.servlet.ThumbnailServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Thumbnail</servlet-name> <url-pattern>/thumbnail/*</url-pattern> </servlet-mapping>
The [fleXive] template project's
web.xml
already includes this mapping.
Finally, we have to register the data provider bean and provide navigation routes for
our buttons and links in the
faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config
PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<!-- Facelets View Handler Definition. Never remove this one. -->
<application>
<view-handler>
com.sun.facelets.FaceletViewHandler
</view-handler>
</application>
<!-- Define our managed bean -->
<managed-bean>
<managed-bean-name>tutorialBean</managed-bean-name>
<managed-bean-class>com.flexive.examples.tutorial01.Tutorial01Bean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<!-- Return to overview page if upload was successful -->
<navigation-rule>
<from-view-id>/upload.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/index.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<!-- Open editor from index page -->
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>edit</from-outcome>
<to-view-id>/upload.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
To compile and deploy the framework and the example application simply run
ant in the project directory (tutorial01-documentstore). If the compilation was successful,
you find your application packaged under
dist/tutorial01-documentstore.ear.
You can then deploy this EAR to your application server, assuming that you followed the
installation instructions.
If you already have a [fleXive] EAR deployed in this instance, be sure to undeploy it before
you deploy
tutorial01-documentstore.ear.
If you need to setup or reset the database schema, update your database connection settings in
tutorial01-documentstore/database.properties
or
flexive-dist/database.properties
and run
ant db.create
in the corresponding directory.
After deploying the application, point your browser to http://localhost:8080/flexive/adm/ to open the backend administration application (default credentials: supervisor/supervisor), or http://localhost:8080/tutorial01-documentstore/ to open the application.
Table of Contents
There are two convenient ways to start a new [fleXive] project:
to use the [fleXive] distribution and utilize its Apache Ant build infrastructure, or
to use Apache Maven and add [fleXive] to your project dependencies.
While 1) offers you out-of-the-box support for all [fleXive] features such as run-once scripts, plugins, weblet resources and so on, 2) is more modular and allows you to use [fleXive] just as any other library. Of course you can also use [fleXive] in your own enterprise application by adding the libraries.
The easiest way to get started with [fleXive] is to grab a distribution package from the download page, unzip it to a local development directory, and create a new project from scratch. In this chapter we will concentrate on creating new [fleXive] applications.
Looking at your local [fleXive] distribution directory, you will find the following directory layout:
. |-- META-INF/ |-- build.xml |-- applications/ |-- extlib/ |-- lib/ |-- templates/
build.xml
Contains the build file for creating new projects and components.
To get started, simply execute
ant
in your
flexive-dist/
directory.
applications
Contains all [fleXive] applications that should be included in the
EAR
file. The standard distribution includes the backend administration application in this directory.
To remove an application from the
EAR,
simply remove it from this directory and rebuild the EAR file.
lib
Contains all [fleXive] libraries packaged as JAR files.
extlib
Contains all third-party libraries required for compiling and running [fleXive] applications. Note that not all libraries in this directory will be packaged into the final application archive, for example the JSF API package is only required for compiling JSF applications.
templates
Contains project and component templates used by the build system.
META-INF
Currently this directory only holds a template application descriptor for [fleXive] applications.
Before proceeding to create a new [fleXive] application, make sure you have a working build system, i.e. at least
JDK 6 or higher, and
Apache Ant 1.7.0 or higher.
For running the application you need a working environment including MySQL as described in the installation chapter.
To get started with your first [fleXive] application, open a command shell and change to the directory containing the [fleXive] distribution. Type ant. You should be greeted by the welcome page of the [fleXive] build system:
Buildfile: build.xml
info:
[flexive]
[flexive] Welcome to the flexive build tool. Feel free to use the following build targets:
[flexive]
[flexive] project.create
[flexive] Creates a new flexive project directory.
[flexive]
[flexive] component.create
[flexive] Creates a new flexive UI component directory.
[flexive]
[flexive] db.create
[flexive] Create or reset the database schema of a flexive division.
[flexive] Warning: if the schema already exists, it will be dropped (i.e. you will lose all data
[flexive] stored in the schema).
[flexive]
[flexive] db.config.create
[flexive] Create or reset the global flexive configuration schema.
[flexive]
[flexive] ear
[flexive] Create a flexive.ear distribution including any flexive application stored in
[flexive] flexive-dist/applications.
[flexive]
[flexive] glassfish.libs
[flexive] Copies libraries needed for Glassfish compatibility to a directory
[flexive]
[input] Please enter a target name, or quit to exit:
Let's create a new project. Type
project.create
and hit return. You will be asked for a project name. This name will be used as the
root directory name for the project, so be careful to include only characters that
may appear in filenames and URLs. For a start, enter
flexive-test.
[fleXive] will create the project folder in the same directory where the [fleXive] distribution is stored, i.e. in the current parent directory. The major reason for this is that the project references the distribution directory, i.e. it includes all the libraries from the distribution directory and does not use its own copies. Thus new [fleXive] projects use little disk space, and you need only one [fleXive] distribution for all your projects.
After confirming your selection, the root directory layout for flexive-test will be created. Your screen should look approximately like this:
[input] Please enter a target name, or quit to exit:
project.create
check:
project.create:
[input] Name of the project you want to create:
flexive-test
[flexive]
[flexive] Please confirm your input:
[flexive] Project name: flexive-test
[flexive] Base directory: ../flexive-test
[flexive]
[input] Are these settings correct? ([y], n)
y
[mkdir] Created dir: /home/daniel/dev/idea-workspace/flexive/flexive-test
[copy] Copying 4 files to /home/daniel/dev/idea-workspace/flexive/flexive-test
[copy] Copied 14 empty directories to 9 empty directories under /home/daniel/dev/idea-workspace/flexive/flexive-test
[copy] Copying 1 file to /home/daniel/dev/idea-workspace/flexive/flexive-test
[copy] Copying 1 file to /home/daniel/dev/idea-workspace/flexive/flexive-test
[echo] Project flexive-test created successfully. The project root directory is
[echo] ../flexive-test
BUILD SUCCESSFUL
When the build tool has finished successfully, go to the newly created directory, e.g. cd ../flexive-test. The directory structure contains a blank project structure, which looks like the following:
.
|-- build.xml
|-- lib
|-- resources
| |-- META-INF
| | |-- faces-config.xml
| | |-- template.taglib.xml.sample
| | `-- web.xml
| |-- messages
| |-- scripts
| | |-- library
| | |-- runonce
| | `-- startup
| `-- templates
|-- src
| `-- java
| |-- ejb
| |-- shared
| `-- war
`-- web
`-- index.xhtml
Before examining the directory structure, let's do a quick test if the the environment
is working. Type
ant. The build should complete successfully, leaving you with
some artifacts in the
dist/
subdirectory:
flexive-test.ear,
flexive-test-shared.jar, and
flexive-test.war.
To deploy the application, first you need to setup the database schemas. Type
ant db.create db.config.create
and when prompted for the database schema use the default name,
flexive.
When the command completed successfully, your can deploy
flexive-test.ear
and have a working (albeit empty) [fleXive] application, including the backend administration application.
The project root directory contains a build file,
build.xml,
that can be customized for the project. By default, it builds JAR files for all layers,
including an EAR archive. The major subdirectories are:
src/java
contains the Java sources of the project. They are split up by layer, i.e. there are three distinct source trees for the EJB, web, and shared classes. This is especially useful for IDEs with support for multiple project modules, where you can also specify wanted (and forbidden) relationships between the layers.
web
contains the documents for the web application (if any).
lib
contains additional libraries and components used by the project.
resources
is the root folder for various project resources:
resources/scripts
contains the run-once, startup and library scripts of the application,
resources/messages
contains the application's localized message resources that can be accessed with the fxMessageBean,
resources/META-INF
contains the application's configuration files, mostly for the web layer,
resources/templates
is the standard folder for Facelets templates. You can choose any folder of course if you like, but then you'd have to modify the build script.
Your [fleXive] project comes with basic project files for the Eclipse and IntelliJ IDEA Java IDEs.
If this is your first Eclipse project with [fleXive], first you have to define a new user library that includes the JAR files of your [fleXive] distribution. We also could include these files in the individual project classpath, but this way is much cleaner and easier to work with especially for multiple projects.
Please go to → → → →
Click
and enter
flexive
for the library name. Press
Select the created library entry, and press the button.
Go to your [fleXive] distribution directory, and select all JAR files
in the
lib/
folder (you can use shift-select here).
Repeat the last two steps and add all JAR files of the
extlib/
folder and the
flexive-plugin-jsf-core.jar
from the
applications/
folder to the user library.
In the User Libraries page, click OK. You have now defined a global flexive library that includes all required JAR files for a [fleXive] application. Note that this is only available within the IDE, the actual build files do not use the IDE-specific library definitions.
Assuming that both your
flexive-dist
directory and the newly created project reside in your current Eclipse workspace directory,
you can add the project following these steps:
Open → + →
Select the project root directory.
Press Eclipse should now open the project in the workspace. To build the project EAR file using Ant, execute →
To check if the project and library has been loaded correctly, try to open
the
EJBExampleBean
class using
→ (
Shift+Ctrl+T
)
Eclipse should display the source code without errors.
We start with creating a new project for the
flexive-test
application directory.
Open →
Select and press
Choose your [fleXive] project directory in Project file location.
Click
Uncheck Create module and click
Let IDEA open the project. Next we add the template module file in the IDEA project settings dialog:
Add a new module by clicking on the "+" button at the top of the screen.
Choose
Import existing module
and select the IDEA module file
(flexive-test.iml) in your project directory.
Press Finish.
If this is your first IntelliJ IDEA project with [fleXive], please perform the following setup steps. Similar to Eclipse, we use a shared library definition to include [fleXive] in the project classpath.
Open →
Click on
On the left-hand-side, click on →
Add a new library by clicking on the "+" button at the top of the screen.
Enter
flexive-dist
as the library name.
Select your project module in the next screen and press
In the library overview screen, select the flexive-dist library and add the
flexive-dist/applications,
flexive-dist/lib
and
flexive-dist/extlib
directories using the
button.
Now you should have all [fleXive] classes available in your project. You can
test this by opening the example EJB (
EJBExampleBean)
using
→ (
Ctrl+N
)
IDEA should not display any errors like missing classes.
The blank [fleXive] application includes an example EJB and JSF bean that shows the intended use of the package structure, cfe stands for com.flexive.example:
src/java/shared/cfe.shared.interfaces.EJBExample
Defines the (remote) interface of an EJB.
src/java/ejb/cfe.ejb.EJBExampleBean
Contains the EJB implementation of the
EJBExample
interface.
src/java/war/cfe.war.ManagedExampleBean
A sample implementation of a JSF wrapper bean that invokes
an EJB business method. Note the use of flexive's
EJBLookup
class to retrieve the EJB wrapper bean, since the
@EJB
annotation isn't supported by all application servers at the time
of this writing.
You can remove the example beans if you like, or rename them and use them as a start for your own beans (if any).
A [fleXive] application is a collection of JAR files that follow a few naming conventions
that will be packaged into an EAR archive. Note
that each file is optional, so if you for example don't provide EJB3 beans, you don't need
to provide a
-ejb.jar
file.
[basename]-ejb.jar
Contains the EJB3 implementations for your component.
[basename]-shared.jar
Contains shared classes of your application that are not tied to either the JSF or the EJB layer. This contains the EJB interface definitions, data transfer objects or general utility classes.
[basename].war
Contains the actual web application frontend, consisting of HTML pages, JSF components and Facelets templates, as well as JSF managed beans and deployment descriptors.
*.jar
Of course you can also package any additional JAR files containing
libraries or even JSF plugins. They will be stored in the
lib/
folder of the EAR file and will be available
to every [fleXive] application in the EAR file.
If you created a new [fleXive] application with the tools of this chapter,
it will include a buildfile for Apache Ant that compiles and packages the application into
JAR and EAR files as described in the previous section. It uses a shared generic
build file from the
flexive-dist
directory that is customized by setting a few
Ant
properties:
fxProject
The project name, in our example flexive-test.
flexive.dist.dir
The [fleXive] distribution directory. By default this is a relative location. Keep this and do not alter the directory name if you want to keep the build environment independent from the actual workspace location.
[basename].shared.disabled
,
[basename].ejb.disabled
,
[basename].war.disabled
,
[basename].ear.disabled
If any of these properties is set, the corresponding archive file will not
be generated.
[basename]
is the project name as set in the
fxProject
property, in our example application we would skip the EAR file generation using
<property name="flexive-test.ear.disabled" value="1"/>.
The resulting JAR and EAR files will be stored in the
dist/
directory of the project. If you deploy the EAR file into your application server,
please make sure that no other [fleXive] EAR is deployed to prevent conflicts.
The default Apache Ant target builds the entire application, including an EAR file that can be deployed into your application server. Additional tasks are provided for setting up the database schemas.
package
(default)
Build all archives, except disabled ones as described in the previous section.
help
Display a short help message and offer documentation of the most relevant tasks.
db.create
Create or reset a [fleXive] division database schema. By default,
the first division uses the schema
flexive.
db.config.create
Create or reset the global [fleXive] configuration schema,
flexiveConfig.
Apache Maven is a build tool like Apache Ant, but with a completely different approach to managing software builds: where Apache Ant is like a procedural programming language for describing how to build your system, Apache Maven is more of a fully-fledged assembly line for building, packaging, testing, and running software projects.
The Maven repository at http://repo.flexive.org/maven2/ contains all [fleXive] libraries with correct
dependencies set up. To use it in your Maven project, add the following repository
to your
pom.xml:
<repository> <id>maven.flexive.org</id> <name>Flexive repo</name> <url>http://repo.flexive.org/maven2</url> <layout>default</layout> </repository>
The repository contains all released versions, including snapshots for all active branches that are updated from our internal continuous integration servers. Use these -SNAPSHOT revisions to use the latest features and bugfixes, but don't use them in a final product!
To resolve all dependencies of [fleXive], you also need to add the following repositories from java.net and JBoss:
<repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2/</url> <layout>default</layout> <snapshots> <updatePolicy>never</updatePolicy> </snapshots> </repository> <repository> <id>maven2-jboss</id> <name>JBoss Maven Repository</name> <url>http://repository.jboss.org/maven2</url> <layout>default</layout> <snapshots> <updatePolicy>never</updatePolicy> </snapshots> </repository> <repository> <id>maven-dev-repository.dev.java.net</id> <name>Java.net Dev Repository for Maven</name> <url>https://maven-repository.dev.java.net/repository/</url> <layout>legacy</layout> <snapshots> <updatePolicy>never</updatePolicy> </snapshots> </repository>
Archetypes provide a quick start for new projects. Currently we offer a archetype for a full enterprise application with web and console frontends. Since the archetype includes setup scripts for the H2 database, it can be used without external dependencies like MySQL. However, for administration tasks such as database setup you currently have to use the tools provided by the [fleXive] distribution.
The archetypes are (as the rest of the Maven modules) still under development, so please report rough edges or missing features in our issue tracker.
The archetype
flexive-archetype-ear
creates a multi-module enterprise application, which will be deployed as an EAR.
It also allows easy integration testing with OpenEJB and offers a standalone
webserver using Jetty and H2.
To get started, create a new project using the following command:
mvn archetype:generate -DarchetypeGroupId=com.flexive -DarchetypeArtifactId=flexive-archetype-ear -DarchetypeVersion=1.0-SNAPSHOT -DarchetypeRepository=http://repo.flexive.org/maven2/ -DgroupId=mygroup -DartifactId=hello-flexive -Dversion=0.1-SNAPSHOT
This will create a new project called "hello-flexive" in the current directory.
The [fleXive] version to be used can be specified in the main module's
pom.xml
file. By default, it is set to the current release required for all features
(currently this is
3.1-SNAPSHOT).
For a list of available versions, please look at the Maven repository
(e.g.
here).
The snapshot versions in our Maven repository (
3.0-SNAPSHOT
and
3.1-SNAPSHOT)
are updated automatically from our internal Continuous Integration server (Hudson)
with the latest stable build of the corresponding branch. A build is considered
"stable" in this context if no testcase fails.
The snapshot versions are a convenient way of getting and testing the latest features,
however these builds are not tested manually and may cause all kinds of havoc
including data loss. If possible, it is recommended to use stable versions.
The following commands (issued in the project directory) will walk you through the most important features:
mvn package
Compile and package the application. The resulting EAR file can be deployed in any supported application server (for setup instructions, consult Chapter 2, Installing [fleXive] ).
mvn install
Compile, package and install the application. If executed for the first time,
the H2 database schemas will also be created in the
database/h2
subdirectory.
mvn install -Pflexive-db-setup-h2
(since 3.1)
Compile, package and install the application. Manually activate the profile
flexive-db-setup-h2
to reset the H2 database in the
database/h2
subdirectory.
cd war
,
mvn jetty:run
(since 3.1)
Start an instance of the Jetty WebServer to deploy our application (including the backend administration application) and OpenEJB. The available applications can be browsed at http://localhost:8080.
Additional information on working with the Maven archetypes can be found in the following blog entries:
Please note that some features are dependent on the [fleXive] version:
H2/OpenEJB/Jetty support is only available in 3.1 or later.
In order to build your project with [fleXive] 3.0.x, you have to disable the
database
and
consoleapp
modules in your root
pom.xml.
Then you can package an EAR file using the
mvn package
command.
While you can browse all artifacts at http://repo.flexive.org/maven2/, the following list enumerates those you will most likely end up using in your application.
The [fleXive] EJB layer (packaging: ejb). Add this to your EAR module.
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-ejb</artifactId> <version>3.0.2</version> <type>ejb</type> </dependency>
The [fleXive] shared classes, including all EJB interfaces. Use this artifact in modules that need to access [fleXive] via EJB, but are not EJBs themselves (e.g. web modules).
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-shared</artifactId> <version>3.0.2</version> <type>jar</type> </dependency>
The [fleXive] backend application, including all required libraries and the actual WAR package.
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-backend</artifactId> <version>3.0.2</version> <type>jar</type> </dependency>
The JSF component library (includes flexive-web-shared).
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-plugin-jsf-core</artifactId> <version>3.0.2</version> <type>jar</type> </dependency>
Shared (framework agnostic) web classes, including authentication filters, servlets for delivering [fleXive] contents.
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-web-shared</artifactId> <version>3.0.2</version> <type>jar</type> </dependency>
[fleXive] is a comprehensive JavaEE 5 library with an EJB3 interface and a supplementing JSF component library. It is based on current Java enterprise technologies and focuses on flexibility and extensibility. Chapter 6, The [fleXive] core components and Chapter 7, JSF Support explain all key concepts services relevant to users of the [fleXive] framework.
The core of [fleXive] is a collection of EJB3 beans (called engines) that offer services such as content creation and retrieval, search queries and the definition of data structures. The client is usually a web application or another EJB3 that extends or embeds [fleXive] functionality.
The [fleXive] core is based on the following EJB engines:
The content engine implements the persistency layer that is used to create, update and delete [fleXive] contents. It is supported by an extensive security layer that offers fine-grained permission controls for all contents managed by [fleXive].
The content engine is supplemented by the search engine to search for contents in a SQL-like query language.
The structure engine is responsible for creating and editing the dynamic data structures used by the content engine.
The tree engine provides a scalable implementation for organizing contents in a hierarchical tree structure.
Further engines extend the basic functionality: the scripting engine allows to fire user-defined code at almost any event in the content engine, user management provides access to the user directory, the workflow engine implements workflows for content instances, and the configuration engine stores user preferences and system configuration parameters.
Table of Contents
If you remove all eyecandy created by more or less sophisticated user interfaces you will discover the driving force behind [fleXive]: the core layer. It is implemented at the EJB layer and is provided by stateless session beans. These “engines” serve as abstractions to concrete implementations (usually using some form of singleton pattern) for different databases or algorithms used.
The areas covered are structure definition, content manipulation, SQL like queries, organizing contents in trees, scripting, workflow, user management and how security is handled.
Handling users and user information is a basic business of virtually every major software system in use today. At this juncture [fleXive] is no exception. User management in [fleXive] is based on accounts – that is a user and its basic information (name, login name, contact data, e-mail addresss, ...) are stored in accounts.
In the table below you see the listing of all attributes an account in [fleXive] owns.
| Attribute | Description |
|---|---|
| id, name, login name, e-mail, description, contactDataId | Some basic attributes. The Id uniquely identifies the account. The description allows you to characterize the account while the contactDataId identifies the contact data (e.g. postal address, telephon number) linked to this user. Name and login name stand for themselves. |
| mandatorId | The mandator the account belongs to. Note that every account has to be assigned to exactly one mandator. |
| language | Identifies the preferred language for the account. Whenever there are available user interface translations or contents in several languages the system will select to display the ones in the language specified here, if available. |
| active | An account can be set to status inactive. Thus no login is possible while it is not activated again. Note: both flags, active and validated, have to be set to true for the login to work. |
| validated | Flags for admins indicating that the user data was audited and is no fake. Setting this flag to false prevents a login for the corresponding account until the flag is set to true. This setting can be used when auto-creating accounts to force validation of the account data. Note: both flags, active and validated, have to be set to true for the log in to work. |
| validFrom, validTo | The valid from/to dates may be used to define a time periode in which the user may log in. |
| defaultNodeId | The desired start node in the tree for the user. |
| allowMultiLogin | True if multiple logins for the account are possible at the same time. |
| updateToken | The update token may be used in external API calls modifying this account to improve security. |
There are two system defined accounts:
the guest user. Everyone who is not logged in is treated as GUEST.
the supervisor. This user is in all roles and may operate on all mandators.
These two accounts can not be removed.
In the following we will look at how to create, update and remove accounts.
Creating a user requires special rights. More precisely only callers (i.e. a user) in role ACCOUNT MANAGEMENT may create users, and only for their mandator. An exception is a user in the role GLOBAL_SUPERVISOR who may create users for all mandators. An example of how to create a new user is given next.
Example 6.1. Creating a new user
import com.flexive.shared.EJBLookup; import com.flexive.shared.interfaces.AccountEngine; import com.flexive.shared.security.AccountEdit AccountEngine ae = EJBLookup.getAccountEngine(); AccountEdit accountEdit = new AccountEdit() accountEdit.setEmail("admin@flexive.org") accountEdit.setName("admin") long accountId = ae.create(accountEdit, "password")
Returns the ID of the created account.
After the account creation one can assign the roles the account is in and the groups it belongs to. For a detailed explanation of the meaning and functioning of roles and groups refer to the the section called “Security”.
For assigning a role to an account the following rules apply:
the caller must be in role ACCOUNT MANAGEMENT
the account/user has to belong to the callers mandator
the caller may only assign roles that he is assigned to himself
GROUP_GLOBAL_SUPERVISOR may set the roles for all users in the system.
Assigning a group to an account presumes the following prerequisites:
the caller must be in role ACCOUNT MANAGEMENT
the account/user has to belong to the callers mandator
the caller may only assign groups that also belong to his mandator, plus GROUP_EVERYONE and GROUP_OWNER
GROUP_GLOBAL_SUPERVISOR may set all groups for all users. Note that by default a newly created account is assigned to the group EVERYONE. To get more information about groups go to the the section called “Security”.
[fleXive] provides two methods for updating an account. One is for updating only some personal data of the specified user (name, login name, password, e-mail address, and language). The other one lets you update all attributes of the user. For both methods applies: setting one parameter to null in the method call means keeping its original value (i.e. the value of the corresponding account attribute is not changed).
The following code updates the previously created administrator account (ID 1) by setting its name to “System administrator” and its e-mail address to “sysadmin@flexive.org”.
Example 6.2. Updating a user
import com.flexive.shared.EJBLookup; import com.flexive.shared.interfaces.AccountEngine; AccountEngine ae = EJBLookup.getAccountEngine(); ae.update(1, null, “System administrator”, null, "sysadmin@flexive.org", null);
Note that by setting the parameters password, login name and language to null the original values (“pw123”, “admin”, 1) are preserved.
There are two prerequisites for the caller to remove an account:
the caller must be in role ACCOUNT MANAGEMENT
the account/user has to belong to the callers mandator
GlobalSupervisor may remove users belonging to any mandator. The accounts USER_GUEST and USER_GLOBAL_SUPERVISOR may not be removed in any case.
For removing an account one only needs to know the account ID. Thus removing the previously created and updated administrator account works as follows:
[fleXive] implements an access control list based approach to security combined with roles. Since handling access permissions on a per user basis would result in tremendous amounts of data, [fleXive] checks permissions based on access control lists on a per user group basis. User accounts can be assigned to any number of user groups and if in rare cases an explicit user based security is needed, the use of one group for each user is recommended.
User accounts are stored in the database and consist basically of the login name together with a hashed password of the assigned groups and roles. For more information about accounts see the section called “User Management”.
Authentication is currently performed against the database by calling
FxContext.login(..)
(or as an alternative directly using
AccountEngine.login(..)).
JAAS based login can be implemented very easily by uncommenting some code in
LoginLogoutHandler. It is planned to make this configurable in future versions
of [fleXive].
At the core and web layer (provided they run in the same virtual machine) a context is available which
is stored in a
ThreadLocal
called
FxContext
which provides amongst others the following information:
getDivisionId(): number of the current division
getLanguage(): the current users preferred language
getTicket(): the current users
UserTicket
instance
The
UserTicket
is the central class providing information about the current user ranging from the user name, mandator, user
group memberships, assigned roles to all access control lists the user belongs to indirectly. Hence it
serves as the central authorization data provider for any security check performed.
[fleXive] was designed to be used by application service providers (ASP) with only one deployed enterprise application instance using multiple databases. These database instances are called Divisions and contain their own accounts, structures and content - totally separated from other users of [fleXive].
Divisions are mapped to server names using regular expressions. A web based user interface is provided by the Global Configuration Plugin.
Mandators are - contrary to divisions - not transparent to the user but a means of separating accounts and data. Data can be shared between mandators by assigning user groups from multiple mandators to access control lists. Transfer of data between mandators is a planned upcoming feature of [fleXive]. An example when mandators should be used is e.g. to model different departments of a larger company.
Access control lists - which are assigned to user groups - define a list (Read, Edit, Create, etc.) of permissions attached to an arbitrary object like content instances, types, properties (and property assignments) or select lists. See this article on Wikipedia for more information about access control lists. For the purpose of easier organization and logical grouping [fleXive] categorizes ACL's into the following groups:
Instance: Attached to
content instances.
Structure: Attached to
types,
properties and property assignments.
Workflow: Attached to workflow steps.
Briefcase: Attached to briefcases, to allow sharing them among different
users.
Selectlist: Attached to select lists, the only relevant permission is
Create
to allow users to create new items for that list.
Selectlist Item: Attached to selectlist items, to restrict visibility.
Each access control list allows setting the following permissions independently: Read, Edit, Create, Delete, Relate and Export.
Roles can be assigned individually to users and groups. Every user (or account) is eligible to use any role assigned to the user or any group he belongs to. It is considered best practice to assign roles to groups and only in very rare cases (e.g. flagging someone as a global supervisor) should the role assignments be done on an individual (per user) basis.
[fleXive] provides the following roles:
GlobalSupervisor: no restrictions at all.
MandatorSupervisor: may do everything for "his" mandator
ACLManagement: create/update/delete ACL's for "his" mandator
AccountManagement: create/update/delete users, groups and roles (may only
add roles he is assigned himself, may not alter assigned roles that he has not assigned himself)
for "his" mandator. Everybody may read user and group informations (of course no passwords which
are hashed anyways), but only for his own mandators unless he is a global supervisor.
SelectListEditor: may see the user interface to edit selectlist items (role
entitles to no CRUD rights!), actual permissions are taken from the select lists createItemACL
WorkflowManagement: create/update/delete steps and workflows for "his"
mandator
StructureManagement: create/update/delete types, relations, groups,
properties, assignments and selectlists and assign scripts to structures for "his" mandator
ScriptManagement: create/update/delete scripts
ScriptExecution: execute scripts that can be run "standalone" (i.e. not
triggered by events)
BackendAccess: may login to the backend (does not imply any rights)
One of the core tasks of [fleXive] is to store and query data. Like in relational databases or well-formed XML files, data needs to match a predefined structure. When [fleXive] was designed the requirements were as follows:
A good analogy to explain how structures in [fleXive] are organised are classes and object instances: a
class in
an object oriented programming language describes which attributes (in [fleXive] called
properties) are available.
If an attribute itself acts like a container for other attributes we call it a
group.
We call the class analogon
Type
(implemented in the class
FxType), attributes
property
(implemented in
FxProperty) and a collection of attributes
Group
(implemented in
FxGroup).
To enable reuse of properties and groups these entities are independent of types and need to be assigned to types (and respectively groups). The benefit of this system - although it might sound a bit confusing at first glance - is that different types and assignments can share the same property which can be a big advantage for query operations [1] .
A special extension to types are relations (implemented in
FxType, mode:
Relation) which mimics the behaviour of attributed n:m relations known from SQL.
Structure elements can be addressed using XPath-like expressions as shown in the section called “Content Engine”.
| Element | Class | Description |
|---|---|---|
| Type |
FxType
|
A type, identified by its name, defines behaviour (what kind of permissions may be applied, storage model or language mode is to be used, etc.) and structure (properties and groups are assigned to types). |
| Relation |
FxType
(mode:Relation)
|
A relation is basically a type that relates (or links) two other types together. A good analogy is an attributed relation known from SQL. It is possible to define how many times a specific instance may be used as a relation source or destination and which types may be related. |
| Property |
FxProperty
|
A property defines a name and a datatype (and some options). Thats all there is to it! It can only exist (and is of relevance) if it is assigned to a type or group. The purpose of keeping properties and their assignments separate is the ability to share them and query across multiple types with a single property. |
| Group |
FxGroup
|
A group serves as a container for properties and combines them to an entity. Groups - just
like properties can not exist without assignments to types (
FxGroupAssignment). The purpose for their existance is like for properties: the
ability to share and query across multiple types.
|
| Assignment |
FxAssignment
|
An assignment is the correlation of groups and properties to types. A group or property can only be used in instances if it is connected to a group assignment or a type. |
| Property assignment |
FxPropertyAssignment
|
The assignment of a property to a group assignment or a type. If the property that is being assigned permits, settings like the access control list or options may be overridden. |
| Group assignment |
FxGroupAssignment
|
The assignment of a group to a group assignment or a type. If the group that is being assigned permits, settings like the access control list or options may be overridden. |
Following the convention on how to update or create new instances of classes in [fleXive], for every class
exists (or should exist ;-) ) an editable class. These editable classes can either be instantiated with
ClassName.createNew(..)
or
classInstance.asEditable()
depending on if you want to create a new instance or edit an existing.
A type, identified by its name, defines behaviour (what kind of permissions may be applied, storage model or language mode is to be used, etc.) and structure (properties and groups are assigned to types).
A system internal and somewhat special type is the "Root" type: It serves as a virtual repository to which groups or properties can be assigned, which in turn can be reused in other types or assignments without the need of inheritance.
The following is a list of parameters that can be passed as arguments to
FxTypeEdit
when editing or creating a new type:
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| ACL |
setACL(ACL acl)
|
X | X | The ACL which is checked when new instances of this type are created. Will only be checked if the type is configured to check permissions. |
| Category |
setCategory(TypeCategory category)
|
X | X | A type can be assigned the categories
User
orSystem. A User categorized type can be edited by anyone with
proper roles (StructureManagement), whereas System categorized types are
ment to be [fleXive] internal and only to be changed by users with the role
GlobalSupervisor. The mode may only be changed by users with the role
GlobalSupervisor
(under ordinary circumstances changing the category of a type should never be
necessary).
|
| Check validity |
setCheckValidity(boolean checkValidity)
|
X | X | If set totrue, content instances can be assigned a
validFrom
and
validUntil
Date.
FxContent
instances provide anisValid(long time)-Method to check if they
are valid at the requested time. This feature is particularly useful in queries since
only valid content instances will be returned. Using this feature allows for instance
time triggered publication (and removal) of articles.
|
| Description |
setDescription(String description)
|
X | X | Set a description for the type. Of relevance only to user interfaces. |
| Enable parent assignments |
setEnableParentAssignments(boolean enableParentAssignments)
|
X | - | If a type is derived from another type, this flag decides if the derived assignments
should be enabled. This is by default enabled when creating a derived type:
FxTypeEdit.createNew(String name, FxString description, ACL acl, FxType parent)
and can be disabled using this method before the derived type is saved.
|
| History age |
setHistoryAge(long historyAge)
|
X | X | If
trackHistory
is enabled for this type, the
historyAge
determines the duration for which history entries exist. All entries older than
this time (in milliseconds) will be removed. History entries are changes to the type or
instances and are not fully implemented yet.
|
| Language mode |
setLanguage(LanguageMode language)
|
X | (X) | Set one of the supported language modes:
|
| Maximum destination count (Relation) |
setMaxRelDestination(int maxRelDestination)
|
(X) | (X) | Restrict the total number of instances that may be related to a source instance using
this relation type. The value
0
means unlimited. This value can only be set if the type is a relation and no instances
would invalidate this restriction.
|
| Maximum source count (Relation) |
setMaxRelSource(int maxRelSource)
|
(X) | (X) | Restrict the total number of instances that may be related to a destination instance
using
this relation type. The value
0
means unlimited. This value can only be set if the type is a relation and no instances
would invalidate this restriction.
|
| Maximum versions |
setMaxVersions(long maxVersions)
|
X | X | Set the max. number of instance versions to keep, if negative unlimited,
0
does not keep any versions.
|
| Mode |
setMode(TypeMode mode)
|
X | (X) | Set if this type is to be used as a regular type or as a relation. Changing the mode is
currently only allowed if no content instance exist.
Possible modes are:
|
| Name |
setName(String name)
|
X | X | The name of the type. Has to be unique. |
| Permissions |
setPermissions(byte permissions)
|
X | X | Set the permissions to check. The parameter contains the bitcoded types of permissions
that
should be checked. Please use the
setUseXXXPermission()-convenience methods, where XXX is Type,
Property, Step or Instance. If you want to use bit coded permissions, use these
constants:
|
| Remove instances with relation types |
setRemoveInstancesWithRelationTypes(boolean
removeInstancesWithRelationTypes)
|
- | (X) | Only applies to relations: If relation entries are removed (for instance,
you no longer want to relate Type A and B using this relation), all instances of
this relation type that relate the removed
FxTypeRelation will also be removed.
|
| State |
setState(TypeState state)
|
X | X | Changing the state allows to (de-)activate a type.
|
| Track history |
setTrackHistory(boolean trackHistory)
|
X | X | Enables history tracking (will log changes to the type itself or instances). How long
history entries are kept can be set with
setHistoryAge(long)
|
| Use instance permissions |
setUseInstancePermissions(boolean use)
|
X | X | Should instance permissions be checked? If enabled, the ACL assigned to instances will be checked. |
| Use property permissions |
setUsePropertyPermissions(boolean use)
|
X | X | Should property (assignment) permissions be checked? If enabled, the ACL assigned to property assignments will be checked. Property permission checks are disabled by default and should only be used if really necessary (i.e. if you want to hide specific company or department internal properties from users that under normal circumstances should be allowed to read the instance). |
| Use step permissions |
setUseStepPermissions(boolean use)
|
X | X | Should step permissions be checked? If enabled, the ACL assigned to workflow steps will be checked. Enable these checks if you want to prevent users from seeing/using instances in certain steps (i.e. an editor should not be allowed to edit already published articles - this is something only users from the quality assurance department should be allowed to do). |
| Use type permissions |
setUseTypePermissions(boolean use)
|
X | X | Should type permissions be checked? If enabled, the ACL assigned to this type will be checked. Disabling this check, will allow everyone to create or remove instances of this type (if instance permission checks are enabled, they will of course still be checked in the case of a removal). |
| Workflow |
setWorkflow(Workflow workflow)
|
X | (X) | Assign a workflow to this type. For existing types the workflow can only be changed if no instances exist. |
In the following example we create a new type "Customer", provide a multilingual description and assign an access control list:
Example 6.4. Creating a new FxType
TypeEngine typeEngine = EJBLookup.getTypeEngine();
FxString typeDesc = new FxString("A generic customer");
typeDesc.setTranslation(FxLanguage.GERMAN, "Ein generischer Kunde");
ACL customerACL = CacheAdmin.getEnvironment().getACL(ACL.Category.STRUCTURE.getDefaultId());
FxTypeEdit type = FxTypeEdit.createNew("Customer", typeDesc, customerACL);
typeEngine.save(type);
FxType typeByName = CacheAdmin.getEnvironment().getType("Customer");
|
We use the utility class
| |
|
The multilingual
| |
|
The german translation is assigned. | |
|
We don't want to create a new ACL and use the default ACL for structures
( | |
|
A new type instance is created with most settings using default values, only setting the name, description and ACL. | |
|
The
| |
|
This line just serves as an example how to retrieve the a reference to the new type. Saving the instance automatically forced a reload of the cached environment, that why we can retrieve it from the cache here. |
Another way to create a new type would be to use the
GroovyTypeBuilder:
Example 6.5. Creating a new FxType using the GroovyTypeBuilder
import com.flexive.shared.scripting.groovy.* import com.flexive.shared.value.* import com.flexive.shared.security.* import com.flexive.shared.* def typeDesc = new FxString("A generic customer") typeDesc.setTranslation(FxLanguage.GERMAN, "Ein generischer Kunde") new GroovyTypeBuilder().customer(description: typeDesc, acl: CacheAdmin.environment.getACL(ACL.Category.STRUCTURE.defaultId)) { } def type = CacheAdmin.environment.getType("Customer") return type.name
If executed in the Groovy-Console the code above would return CUSTOMER.
Every data type can potentially support values for different languages - depending on the properties' and property assignments' multilingual support settings.
| Data type | FxValue class | Description |
|---|---|---|
| HTML |
FxHTML
|
HTML markup. Unlimited in length. Offers the ability to use HTML editors in user interfaces. |
| String1024 |
FxString
|
A String with a maximum length of 1024 characters. Use this data type in favor of
Text
if you don't need more than 1024 characters, since usually
Text
is stored in
CLOB
Database columns while
String1024
uses
VARCHAR
columns.
|
| Text |
FxString
|
Like
String1024
but unlimited in length.
|
| Number |
FxNumber
|
Numerical type corresponding to the Java
Integer
class.
|
| LargeNumber |
FxLargeNumber
|
Numerical type corresponding to the Java
Long
class.
|
| Float |
FxFloat
|
Numerical type corresponding to the Java
Float
class.
|
| Double |
FxDouble
|
Numerical type corresponding to the Java
Double
class.
|
| Date |
FxDate
|
A date corresponding to the Java
Date
class. Note that there is no time information saved!
|
| DateTime |
FxDate
|
A date corresponding to the Java
Date
class. This data type stores date and time information.
|
| DateRange |
FxDateRange
|
A date range corresponding to two Java
Date
class instances with a start- and enddate.
Note that there is no time information saved!
|
| DateTimeRange |
FxDateTimeRange
|
A date range corresponding to two Java
Date
class instances with a start- and enddate.
Time information is saved as well using this data type.
|
| Boolean |
FxBoolean
|
A boolean value corresponding to the Java
Boolean
class.
|
| Binary |
FxBinary
|
This data type stores information about a binary content and allows streaming of that content. For more information please refer to the section called “Handling binary content” |
| Reference |
FxReference
|
A reference to a content instance, identified by its primary key
(FxPK).
|
| InlineReference |
-
|
This data type is only planned but not implemented. It will allow to create contents that do not exist on their own (and can not be loaded on their own or queried for) but embedded in another content instance. Their XPath addressing will be relative to their parent group in the embedding content. |
| SelectOne |
FxSelectOne
|
This data type allows the selection of one entry of a SelectList. See the section called “Select lists” for further information about select lists. |
| SelectMany |
FxSelectMany
|
This data type allows the selection of many entries of a SelectList. See the section called “Select lists” for further information about select lists. |
Working with binary content is like working with ordinary input- and outputstreams in Java. For uploading a
binary an
InputStream
and for downloading an
OutputStream has to be passed. Transfer of binaries is handled by [fleXive]'s own streaming
framework fxStream. It uses nonblocking tcp sockets as transport medium if the client and server part
exist in different virtual machines and is able to detect if they run within the same VM for optimal
performance.
Example 6.6. Handling binary content
File testFile = new File("src/framework/testresources/image/Exif.JPG"); FxType type = CacheAdmin.getEnvironment().getType(IMAGE_TYPE); FileInputStream fis = new FileInputStream(testFile); BinaryDescriptor binary = new BinaryDescriptor(testFile.getName(), testFile.length(), fis); //This example shows how easy it is to upload a binary programatically to theFxContent img = co.initialize(type.getId()); img.setValue("/ImageBinary", new FxBinary(false, binary)); //
img.setValue("/Filename", new FxString(false, "Exif.JPG")); FxPK pk = co.save(img); FxContent loaded = co.load(pk); FxBinary bin = (FxBinary)loaded.getValue("/ImageBinary"); File comp = File.createTempFile("Exif","JPG"); FileOutputStream fos = new FileOutputStream(comp); bin.getBestTranslation().download(fos); //
fos.close();
ContentEngine
and download it from the saved instance.
|
A
| |
|
Simply set a
| |
|
Downloading binary content is as simple as providing an
|
Select lists usually contain a set of related select items, which are used in GUIs to enable the user to make a selection of one or more of these items. [fleXive] allows in-memory creation of slim select lists for quick GUI display, as well as defining and persisting fully fledged deeply nested select lists. Names of selectlist items have to be unique within their respective list.
The following is a list of parameters that can be passed as arguments to
FxSelectListEdit
when editing or creating a new select list:
| Parameter | Method | Description |
|---|---|---|
| Parent list |
-
|
Set the parent list of this select list (used for nested select lists). Can only be set when the select list is created. |
| Name |
setName(String name)
|
Set the name of the select list (has to be unique). |
| Label |
setLabel(FxString label)
|
Set a label text to be displayed in user interfaces. |
| Description |
setDescription(FxString description)
|
Set a description text to be displayed in user interfaces. |
| Allow dynamic item creation |
-
|
May items be created dynamically (in UI's other than backends). Can only be set when the select list is created. This flag is merely used as information for UI's where users may create select list items themselves. Independent of this flag the users can only create items if they have the permission (see Table 6.7, “Relevant permissions for working with select list items” for details). |
| Create Item ACL |
-
|
The ACL which controls the permissions for adding and removing select items to the select list. Can only be set when the select list is created. |
| New Item ACL |
-
|
The ACL which is assigned to newly created items by default. Can only be set when the select list is created. |
| Default Item |
setDefaultItem(FxSelectListItem defaultItem)
|
A select list item, which is selected by default. |
The following is a list of parameters that can be passed as arguments to
FxSelectListItemEdit
when editing or creating a new select list item:
| Parameter | Method | Description |
|---|---|---|
| List |
-
|
The select list to which this select item belongs. |
| Name |
setName(String name)
|
Set the name of the item (has to be unique within the assigned list). |
| Parent Item |
setParentItem(FxSelectListItem item)
|
Set the parent item of this select list item (used for nested select lists). |
| ACL |
setAcl(ACL acl)
|
The ACL of the select item (see Table 6.7, “Relevant permissions for working with select list items” for details). |
| Label |
setLabel(FxString label)
|
Set a label text to be displayed in user interfaces. |
| Data |
setData(String data)
|
Set additional data stored in the select item. |
| Color |
setColor(String color)
|
Set the item's display color. |
| Icon Id |
setIconId(long iconId)
|
Set the id of this select list item's icon. |
| Icon version |
setIconVer(int iconVer)
|
Set the version of the icon to be displayed. |
| Icon quality |
setIconQuality(int iconQuality)
|
Set the quality of the icon to be displayed. |
The item's Life Cycle Information getLifeCycleInfo() is generated and managed by
the database and can not be edited.
As mentioned earlier, two kinds of select lists can be created. The first kind is
not suitable for being persisted and is used for example for quick display
in UI input components. FxSelectList provides
FxSelectList(String name),
createList(String name, List<? extends SelectableObjectWithLabel> items) and
createListWithName(String name, List<? extends SelectableObjectWithName> items)
for these purposes. FxSelectListItem provides
FxSelectListItem(long id, FxSelectList list, long parentItemId, FxString label)
for select list items that are not persisted.
The second kind of select lists, which can be persisted, are created via FxSelectListEdit and
its constructor or createNew(..) helper methods.
Similar to persistable select lists, persistable select list items are created via
the FxSelectListItemEdit class by invoking its constructor or its
createNew(..) method.
The persistance is handled via methods provided
by theSelectListEngine.
While only users in the role of SelectListEditor may create, update and delete select lists, select list items are handled differently. Whether a specific user is permitted to add itmes to and remove items from a specific select list is handled by the createItem ACL of the select list. Whether users may read, edit, select and deselect a specific select list item is handled by the ACL of the select item itsself. The operations and relevant permissions are specified in Table 6.7, “Relevant permissions for working with select list items”.
| Operation | ACL | Permission | Description |
|---|---|---|---|
| Creating a select list item | FxSelectList.createItemACL | CREATE |
The createItemACL of the item's select list controls which users
may create and hence add select list items to this specific select list.
|
| Deleting a select list item | FxSelectList.createItemACL | DELETE |
Similar to the creation of new select list items, the createItemACL
of the item's select list also controls which users
may remove select list items from this specific select list.
|
| Editing a select list item | FxSelectListItem.acl | EDIT | Whether a user may change the data of a specific select list item is controlled by the EDIT permission of the ACL of the select list item itsself. |
| Reading a select list item | FxSelectListItem.acl | READ | Whether a user may read a specific select list item and its data (for exmaple in the content editor) is controlled by the READ permission of the select list item. |
| Adding a select list item to a selection (when working with contents) | FxSelectListItem.acl | READ, CREATE | Whether a user may add specific select list item to his seleciton is controlled by the READ (otherwise the user wouldn't see that the item exists) and by the CREATE permission of the select list item. |
| Removing a select list item to a selection (when working with contents) | FxSelectListItem.acl | READ, DELETE | Whether a user may remove specific select list item from his seleciton is controlled by the READ (otherwise the user wouldn't see that the item exists) and by the DELETE permission of the select list item. |
Multiplicity - or often referred to as
cardinality
- defines the minimum and maximum occurance of an assignment. An assignment in the context of [fleXive] is a
group- or a property assignment. Multiplictiy is implemented in the class
FxMultiplicity
which contains some predefined and often used constants:
FxMultiplicity.MULT_0_1: zero or one occurances (optional)
FxMultiplicity.MULT_1_1: exactly one occurance (required)
FxMultiplicity.MULT_0_N: zero or more occurances (optional)
FxMultiplicity.MULT_1_N: one or more occurances (required)
The value
N
means any number of occurances.
A multiplicity of 1 to 5 elements would be coded like this:
new FxMultiplicity(1,5)
A property defines a name and a datatype (and some options). It can only exist (and is of relevance) if it is assigned to a type or group. The purpose of keeping properties and their assignments separate is the ability to share them and query across multiple types with a single property.
There are two ways to assign a property to an existing type or group:
Either by calling
FxPropertyEdit.createNew(..)
method which can clone existing or create entirely
new properties or by reusing an existing property assignment by calling
FxPropertyAssignmentEdit.createNew(..).
Example 6.7. Creating new properties
AssignmentEngine assignmentEngine = EJBLookup.getAssignmentEngine();
ACL customerACL = CacheAdmin.getEnvironment().getACL(ACL.Category.STRUCTURE.getDefaultId());
FxPropertyEdit name = FxPropertyEdit.createNew("Name",
new FxString("Name of the person"),
new FxString("Enter the persons name"),
FxMultiplicity.MULT_1_1,
customerACL,
FxDataType.String1024);
assignmentEngine.createProperty(typeId, name.setAutoUniquePropertyName(true), "/");
|
Obtain a reference to the assignment engine bean which is responsible for manipulating assignments | |
|
Get the default ACL used for structures | |
|
Create a new property with the alias "Name" | |
|
Description | |
|
Hint for user interfaces | |
|
Configure this property to be required (See the section called “Multiplicity” for more information) | |
|
Assign the ACL to be used. This ACL will only be checked if the type this property will be assigned to has property permission checks enabled | |
|
The data type will be a String with a maximum length of 1024 characters. See the section called “Data types” for an overview of available data types. | |
|
Since properties can not exist if they are not assigned to a type, we assign them to the type
with the id
|
To reuse an existing property-assignment is even simpler:
Example 6.8. Reusing property assignments
assignmentEngine.save(
FxPropertyAssignmentEdit.reuse(
"ROOT/CAPTION",
"Customer",
"/",
"AnotherCaption"),
false);
|
Obtaining a reference to the assignment engine bean is omitted in this example. We use the
| |
|
The convenience factory method
| |
|
The XPath of the assignment that is to be reused in, preceeded by the type. Here we reuse the "Caption" property that is assigned to the virtual Root-Type which acts as a repository (see the section called “Root Type” for more information about the virtual root type). | |
|
The type we want to assign it to. Please note that the type name is not case sensitive. | |
|
The new parent XPath relative to the type. Since want to assign it to the root group we use "/". Would we want to assign the property to a group called "Info" which is a subgroup of a group named "About", we'd use the XPath "/About/Info". | |
|
The
| |
|
This boolean parameter is used if the assignment is a group assignment. If set to |
The following is a list of parameters that can be passed as arguments to
FxPropertyEdit
and
FxPropertyAssignmentEdit
when editing or creating a new propery/assignment:
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| ACL |
setACL(ACL acl)
|
X | X | The ACL which is checked when the type using this property or assignment is configured
to check property permissions. Setting this value for
FxPropertyAssignmentEdit
is only allowed if the referenced property allows to override its ACL.
|
| Hint |
setHint(FxString hint)
|
X | X | Set a hint text to be displayed in user interfaces. |
| In overview |
setInOverview(boolean inOverview)
|
X | X | Display in overviews. This is an option for user interfaces and a convenience method to
set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_SHOW_OVERVIEW
("SHOW.OVERVIEW") is used. The property can restrict this setting
from being overwritten in an assignment.
|
| Label |
setLabel(FxString label)
|
X | X | Set a label text to be displayed in user interfaces. |
| Multilingual |
setMultiLang(boolean multiLang)
|
X | X | Allow multilingual values for this property. This is a convenience method to
set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_MULTILANG
("MULTILANG") is used. The property can restrict this setting
from being overwritten in an assignment.
|
| Multiline |
setMultiLine(boolean multiLine)
|
X | X | A hint for user interfaces if this property should be rendered using input elements with
multiple lines. Useful for String/Text based properties (See
the section called “Data types”) which should be displayed in a
textarea instead of a single input field. Decision how to render the component is up to
the user interface. This is a convenience method to
set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_MULTILINE
("MULTILINE") is used. The property can restrict this setting
from being overwritten in an assignment.
|
| Multiplicity |
setMultiplicity(FxMultiplicity multiplicity)
|
X | (X) | Set the multiplicity of this property. Can only be changed if no instances exist that would violate the new setting. (See the section called “Multiplicity” for more information). The property can restrict this setting from being overwritten in an assignment. |
| Options |
setOption(..)
setOptionOverrideable(..)
clearOption(..)
|
X | X | Option related operations. See the section called “Property and group options”. |
| Searchable |
setSearchable(boolean searchable)
|
X | X | Allow user interfaces to use this propery/assignment in visual query editors.
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_SEARCHABLE
("SEARCHABLE") is used.
|
| Use HTML editor |
setUseHTMLEditor(boolean useHTMLEditor)
|
X | X | Hint for user interfaces to use a HTML editor when editing values of this property. Only
makes sense for String/Text based data types.
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_HTML_EDITOR
("HTML.EDITOR") is used.
|
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| Auto unique property name |
setAutoUniquePropertyName(boolean autoUniquePropertyName)
|
X | - |
Property names have to be unique to allow querying them. Setting this option to
true
will automatically choose a name that has not been used for a propery by adding an
underscore and a running number to property names until it is unique. Set this option
only if you do not plan on "sharing" a property between different types or dont need
to query based on properties but rather property assignments.
|
| Data type |
setDataType(FxDataType dataType)
|
X | - | Set the data type of this property. Please see the section called “Data types” for more information. |
| Fulltext indexed |
setFulltextIndexed(boolean fulltextIndexed)
|
X | X | Enable fulltext indexing and queries for a property. See the section called “Fulltext search” for more information. |
| Name |
setName(String name)
|
X | - | Set the name of this property. This name is used in assignments as a proposal for the XPath alias (unless a different one is requested). Currently the name can not be changed for existing properties. To query across assignments using the same property, this name is used. |
| Overrideable ACL |
setOverrideACL(boolean overrideACL)
|
X | X | Restrict if assignments may use an ACL different from the one defined for the property.
If set to
false
an assignment may still set an ACL but the ACL of the property is used and the
assignments is ignored.
|
| Overrideable HTML editor |
setOverrideHTMLEditor(boolean overrideHTMLEditor)
|
X | X | Restrict assignment to override the HTML editor option (User interface hint).
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_HTML_EDITOR
("HTML.EDITOR") is used.
|
| Overrideable Multilinguality |
setOverrideMultiLang(boolean overrideMultiLang)
|
X | X | Restrict assignment to override the multilinguality option (allow multilingual values).
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_MULTILANG
("MULTILANG") is used.
|
| Overrideable Multiline |
setOverrideMultiLine(boolean overrideMultiLine)
|
X | X | Restrict assignment to override the multiline option (User interface hint).
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_MULTILINE
("MULTILINE") is used.
|
| Overrideable multiplicity |
setOverrideMultiplicity(boolean overrideMultiplicity)
|
X | X | Restrict if assignments may override the multiplicity of this property. (See the section called “Multiplicity” for more information). |
| Overrideable Overview |
setOverrideOverview(boolean overrideOverview)
|
X | X | Restrict assignment to override the overview option (User interface hint).
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_SHOW_OVERVIEW
("SHOW.OVERVIEW") is used.
|
| Overrideable Searchable |
setOverrideSearchable(boolean overrideSearchable)
|
X | X | Restrict assignment to override the searchable option (User interface hint).
This is a convenience method to set an option (See
the section called “Property and group options”). The constant
FxStructureOption.OPTION_SEARCHABLE
("SEARCHABLE") is used.
|
| Referenced list |
setReferencedList(FxSelectList referencedList)
|
X | (X) | If the properties data type is
SelectOne
or
SelectMany
(See
the section called “Data types”
for more information) the referenced selectlist (See
the section called “Select lists”) can be assigned. Updating an existing
property is only allowed if no data instances using the original selectlist exist.
|
| Referenced type |
setReferencedType(FxType referencedType)
|
X | (X) | If the properties data type is
Reference
(See
the section called “Data types”
for more information) the referenced type (See
the section called “Types”) can be assigned. Updating an existing
property is only allowed if no data instances using the original type exist.
|
| Unique mode |
setUniqueMode(UniqueMode uniqueMode)
|
X | (X) | Set the uniqueness level of this property. May only be changed if no content instances using this property exist. (See the section called “Uniqueness of values” for more information). |
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| Alias |
setAlias(String alias)
|
X | - | Set the alias of a property assignment. Property assignments may define an alias to allow multiple use of the same property but using a different name. The alias is the rightmost part of the XPath used to address an assignment. Changing an alias for existing assignments is not supported (yet). |
| Default language |
setDefaultLanguage(int language)
|
X | X | Defining a (optional) default language preselects this language in multilingual values as the default language. |
| Default multiplicity |
setDefaultMultiplicity(int defaultMultiplicity)
|
X | X | The default multiplicity determines how many values will be initialized for an assignment. Useful in user interfaces to pre-create a set of entries instead of one. |
| Enabled |
setEnabled(boolean enabled)
|
X | (X) | Enables or disables a property assignment - making it unavailable to editors, etc. Updating an existing assignment is currently experimental and might have side-effects ... |
| Parent group assignment |
setParentGroupAssignment(FxGroupAssignment parent)
|
X | - | If this assignment is assigned to a group, the assignment of the parent group (in the context of the current type) |
| Position |
setPosition(int position)
|
X | X | Set the position of the assignment (within the same parent group). Changing an assignment's position will be update all other affected assignments within the same group. Invalid values will be adjusted (to 0 or the max. possible position) |
[fleXive] supports the following modes to enforce that a value is unique:
None: No uniqueness is enforced
Global: Globally unique across all usages of a property
Type: Unique within all instances of a type
DerivedTypes: Unique within all instances of a type, its parents and
types derived from it
Instance: Unique within a content instance
A group basically only defines a name (and some options). It can only exist (and is of relevance) if it is assigned to a type or another group. The purpose of keeping groups and their assignments separate is the ability to share them and be consistent with how properties and property assignments are handled.
Just like using properties, there are two ways to assign a group to an existing type or another group:
Either by calling
FxGroupEdit.createNew(..)
method which can clone existing or create entirely
new groups or by reusing an existing group assignment by calling
FxGroupAssignmentEdit.createNew(..).
Example 6.9. Creating a new group
AssignmentEngine assignmentEngine = EJBLookup.getAssignmentEngine();
assignmentEngine.createGroup(
typeId,
FxGroupEdit.createNew(
"Address",
new FxString("The customers address"),
new FxString("Enter the customers address here"),
true,
FxMultiplicity.MULT_1_1).
setAssignmentGroupMode(GroupMode.AnyOf),
"/");
FxPropertyEdit street = FxPropertyEdit.createNew(...);
FxPropertyEdit zip = FxPropertyEdit.createNew(...);
assignmentEngine.createProperty(typeId, street, "/Address");
assignmentEngine.createProperty(typeId, zip, "/Address");
|
Obtain a reference to the assignment engine which is needed to create groups, properties and (of course) assignments | |
|
We're about to create a new group | |
|
Since groups can not be created without assigning them to a type, we have to provide the id of the type we want to assign this group to. | |
|
The second parameter to
| |
|
"Address" is the name we chose for the new group | |
|
The label (for user interfaces) | |
|
The hint (for user interfaces again) | |
|
This parameter allows overriding the multiplicity assigned in the next line by assignments to the group | |
|
We make the group required, setting the multiplictiy to 1..1 (See the section called “Multiplicity” for more information) | |
|
This parameter is optional, since it is the default value. We set the group mode to allow
any
of the group's children to be present. An alternative would be
| |
|
We need to provide the XPath relative to the root group of the type where we want to assign the group to. Using "/" will assign it directly to the root group. | |
|
We create a property called "Street" which we want to assign to the address group later. | |
|
Same for the property "ZIP" | |
|
The "Street" property is created like in the section called “Properties and property assignments”, but we assign it to our new created group "/Address" | |
|
And again for the "ZIP" code |
Here's another example for creating groups using the
GroovyTypeBuilder
and for attaching content using the GroovyContentBuilder:
Example 6.10. Creating a new group using the
GroovyTypeBuilder
import com.flexive.shared.scripting.groovy.* import com.flexive.shared.value.* import com.flexive.shared.security.* import com.flexive.shared.* import com.flexive.shared.structure.*new GroovyTypeBuilder().person(description: new FxString("Person"),
acl: CacheAdmin.environment.getACL(ACL.Category.STRUCTURE.defaultId),
multilang: true) {
firstname(dataType: FxDataType.String1024, multilang: false, description: new FxString(FxLanguage.ENGLISH, "First name"), multiplicity: FxMultiplicity.MULT_0_N)
lastname(assignment: "PERSON/FIRSTNAME", description: new FxString(FxLanguage.ENGLISH, "Last name"), hint: new FxString(FxLanguage.ENGLISH, "Last name required"), multiplicity: FxMultiplicity.MULT_1_N)
ADDRESS(description: new FxString(FxLanguage.ENGLISH, "Address"), multiplicity: FxMultiplicity.MULT_0_N) {
street(dataType: FxDataType.String1024, description: new FxString(FxLanguage.ENGLISH, "Street (Nr)"), multiplicity: FxMultiplicity.MULT_0_N) } }
def builder = new GroovyContentBuilder("PERSON")
builder {
firstname("John")
lastname("Doe")
address {
street(new FxString(false, "Ameaningfulstreetname 444")) } }
EJBLookup.getContentEngine().save(builder.getContent())
|
Create the type "Person" using the
| |
|
.. use the default ACLs .. | |
|
.. and set the type to support multiple languages. | |
|
Create the first property "firstname". | |
|
Another property for the last name (implicitly required because auf the default multiplicity of 1..N), which is a derived assignment from "FIRSTNAME". | |
|
Here, the group "Address" having the XPath "ADDRESS" is created. Important: UPPERCASE LETTERS (Either the whole group name or simply the first letter, as in "Address") always denote the creation of a Group. | |
|
The group's only property: "street". | |
|
Retrieve a
| |
|
Call the builder passing the parameters (XPathname([contentValue]): | |
|
Set the firstname to "John". | |
|
Set the lastname to "Doe". | |
|
"Open up" the group's XPath value using "address". | |
|
Pass the value "Ameaningfulstreetname 444" to the XPath ADDRESS/STREET | |
|
Save the content by retrieving the ContentEngine EJB and calling its
|
The following is a list of parameters that can be passed as arguments to
FxGroupEdit
and
FxGroupyAssignmentEdit
when editing or creating a new group/assignment:
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| Assignment GroupMode |
setAssignmentGroupMode(GroupMode mode)
|
X | (X) | Set the group mode to allow any of its possible children or just one to be present. Can only be changed for existing groups/assignments if no instances exist (See the section called “Group modes” for more information) |
| Hint |
setHint(FxString hint)
|
X | X | Set a hint text to be displayed in user interfaces. |
| Label |
setLabel(FxString label)
|
X | X | Set a label text to be displayed in user interfaces. |
| Multiplicity |
setMultiplicity(FxMultiplicity multiplicity)
|
X | (X) | Set the multiplicity of this group. Can only be changed if no instances exist that would violate the new setting. (See the section called “Multiplicity” for more information). The group can restrict this setting from being overwritten in an assignment. |
| Options |
setOption(..)
setOptionOverrideable(..)
clearOption(..)
|
X | X | Option related operations. Seethe section called “Property and group options”. |
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| Name |
setName(String name)
|
X | - | Set the name of this group. This name is used in assignments as a proposal for the XPath alias (unless a different one is requested). Currently the name can not be changed for an existing group. |
| Overrideable multiplicity |
setOverrideMultiplicity(boolean overrideMultiplicity)
|
X | X | Restrict if assignments may override the multiplicity of this group. (See the section called “Multiplicity” for more information). |
| Parameter | Method | Create | Edit | Description |
|---|---|---|---|---|
| Alias |
setAlias(String alias)
|
X | - | Set the alias of a group assignment. Group assignments may define an alias to allow multiple use of the same group but using a different name. The alias is part of the XPath and is used to address an assignment. Changing an alias for existing assignments is not supported (yet). |
| Default multiplicity |
setDefaultMultiplicity(int defaultMultiplicity)
|
X | X | The default multiplicity determines how many values will be initialized for an assignment (i.e. how many groups are created upon initialization). Useful in user interfaces to pre-create a set of entries instead of one. |
| Enabled |
setEnabled(boolean enabled)
|
X | (X) | Enables or disables a group assignment - making it unavailable to editors, etc. Updating an existing assignment is currently experimental and might have side-effects ... |
| Parent group assignment |
setParentGroupAssignment(FxGroupAssignment parent)
|
X | - | If this assignment is assigned to a group, the assignment of the parent group (in the context of the current type) |
| Position |
setPosition(int position)
|
X | X | Set the position of the assignment (within the same parent group). Changing an assignments position will be upate all affected other assignments within the same group. Invalid values will be adjusted (to 0 or the max. possible position) |
[fleXive] supports the two modes for groups
OneOf
andAnyOf:
OneOf: Only one of the groups children may be present, honoring their
regular indices. This mode only makes sense if all subgroups/properties are optional!
AnyOf: Any of the groups children may be present, honoring their regular
indices. This is the default setting.
Both properties and groups allow the definition of options. These options can hold any arbitrary
String
or
Boolean
value and, if defined, the assignments to the property or group may override the value. The name of an option
must be unique.
See the API documentation of class FxStructureOption for more details.
The following options are currently actively supported:
| Constant Value | Description |
|---|---|
| MULTILANG |
Value supports multiple languages (
true
/
false
)
|
| SHOW.OVERVIEW |
Values are shown in UI overview components (
true
/
false
)
|
| HTML.EDITOR |
Value can be edited using an HTML editor in user interfaces (
true
/
false
)
|
| SEARCHABLE |
Values can be queried (this is just a hint for user interfaces!) (
true
/
false
)
|
| MAXLENGTH |
Values that support a maximum input length (currently only
String1024
and
Text
) will be rendered with input length restrictions when editing.
|
| MULTILINE | Render a textarea instead of a single input line. If the value is greater than 1 it determines the number of rows. |
When the persistence engines (structure and content) of [fleXive] were designed, we tried to solve this problem by using a generic approach (unlike hibernate, which accesses data instances with (auto)generated classes). Since we wanted to use hierarchical data structures which are quite like XML in nature, the weapon of choice was an XPath-like approach: Values are accessed using their XPath.
As a simple example lets consider the following XML file:
<?xml version=“1.0“ standalone=“yes“?> <Person> <Name>Max Muster</Name> <Phone>+43 1 12345</Phone> <Phone>+43 1 800 FLEXIVE</Phone> <Address> <Street>Private road</Street> </Address> <Address> <Street>Office lane</Street> </Address> </Person>
With [fleXive] we would create a type called
Person. Using the
GroovyTypeBuilder
we would execute the following code in the groovy console:
Example 6.11. Creating a "Person" type
import com.flexive.shared.scripting.groovy.* import com.flexive.shared.value.* import com.flexive.shared.security.* import com.flexive.shared.structure.* import com.flexive.shared.* new GroovyTypeBuilder().Person { name(FxDataType.String1024) phone(FxDataType.String1024, multiplicity: new FxMultiplicity(1,2)) Address(multiplicity: FxMultiplicity.MULT_1_N) { street(FxDataType.String1024) } } def person = CacheAdmin.environment.getType("Person")The groovy code is pretty self-explanatory thanks to the concept of groovy builders. By convention property names are lowercase while groupnames are uppercase. See the section called “Structure Engine” for more information about structures.
Lets have a look at the following table, which is based on the XML data example, to visualize the mapping of XPath's to values:
| XPath | Value |
|---|---|
| PERSON/NAME[1] | Max Muster |
| PERSON/PHONE[1] | +43 1 12345 |
| PERSON/PHONE[2] | +43 1 800 FLEXIVE |
| PERSON/ADDRESS[1]/STREET[1] | Private road |
| PERSON/ADDRESS[2]/STREET[1] | Office lane |
The XPath starts with the name of the type (which is optional if addressing in a
FxContent
instance, since there the type if obviously known but is needed for example in query results) and the path
to
address the property. Please note that XPaths are not case-sensitive and that an index of 1 is optional:
PERSON/ADDRESS[2]/STREET[1]
for example is identical to
PERSON/ADDRESS[2]/STREET.
FxContent
serves as a container for groups, properties and general information about a content instance. It is used to
initialize (create an empty content instance), create, save and load content instances
[2]
.
Example 6.12. Creating "Person" instances
import com.flexive.shared.value.* import com.flexive.shared.content.* import com.flexive.shared.* def ce = EJBLookup.contentEngine def person = CacheAdmin.environment.getType("Person") FxContent co = ce.initialize(person.id) //Copy and pasting the groovy code above into the groovy console will yield the following result on the application servers stdout after execution:co.setValue("/Name", new FxString(false, "John Doe")) co.setValue("/Phone", new FxString(false, "+43 1 12345")) //
co.setValue("/Phone[2]", new FxString(false, "+43 1 800 FLEXIVE")) //
co.setValue("/Address/Street", new FxString(false, "Private road")) //
co.setValue("/Address[2]/Street", new FxString(false, "Ofice lane")) //
FxPK pk = ce.save(co) //
FxContent loaded = ce.load(pk) //
FxString street = (FxString)loaded.getValue("/Address[2]/Street") //
println "Loaded street: ${loaded.getValue("/Address[2]/Street")}" street.setValue("Office lane") //
println "New street: ${loaded.getValue("/Address[2]/Street")}" ce.save(loaded) ce.remove(pk) //
11:13:48,898 INFO [STDOUT] Loaded street: Ofice lane 11:13:48,899 INFO [STDOUT] New street: Office lane
The purpose of this example is to demonstrate how easy it is to create, update and remove content instances.
Since a content instance is tied to a type (See
the section called “Types”
for more information) the first thing that needs to be done is to initialize a new (empty) content instance
. Initializing a content instance creates as many property or group entries as
defined in the respective
assignments'
default multiplicity. Setting a value to a property assignment
is done by creating a new
FxValue
instance corresponding to the property's
data type
for the XPath of the assignment. The same applies if a group is involved, as shown in
for the street property of the first address group. Adding a new index is done by simply setting the XPath
to the desired value like in
or
. Please note that using an index of 3 would be illegal if no index of 2
exists.
Removing an XPath is as simple as calling
co.remove("/Address[2]")
if the second address group should be removed.
A content is created
and updated by calling the
ContentEngine's
FxPK save(FxContent content)
method which returns the
primary key
of a content instance. Loading is done by providing the primary key to the
ContentEngine's
load(FxPK pk)-method.
.
Changing a value can be done by assigning a new
FxValue
instance (like in
) or by reading the current value
,
and changing it
which is updated in the content instance as can be seen from the
println
command on the application servers console.
To remove a content instance (
) only the primary key - and all required
permissions - is needed.
To be able to identify a content, a primary key (implemented in
FxPK) is needed. This primary key consists of the unique identifier (usually
equivalent to the id the content is stored in the database with) and the version. Depending on the
configuration
of the
type
the content belongs to it is possible to create different versions of a content.
A primary key can consist of a distinct version (a numerical value) or a predefined constant:
MAX: Constant to select the maximum (highest) available version
LIVE: Constant to select the version whose workflow step is flagged as
live
To create a new version simply call the
ContentEngine's
createNewVersion(FxContent co)
method.
Information about all versions of a content can be retrieved using the
ContentEngine's
FxContentVersionInfo getContentVersionInfo(FxPK id)
method.
FxContentVersionInfo
contains information about the minimum-, maximum- and most recently modified version numbers as well as
information which user was the last to update each version and if a live version exists.
[fleXive] contents can be queried using a SQL-like dialect called FxSQL. The core idea of FxSQL is to provide a flat virtual table that contains all content instances visible to the current user, with full support for [fleXive] data types and multilingualism.
While it is possible to submit queries in plain FxSQL, there are alternatives for Java and Groovy developers in the form of query builders. They handle proper formatting of values and are the preferred way of formulating queries unless you need some of the more esoteric features of FxSQL.
The overall query layout is similar to SQL. Currently queries are always on the virtual table "content" that provides a flat view on all content instances.
SELECT @pk, caption --FILTER version=live --
WHERE caption LIKE 'test%' --
ORDER BY caption --
![]()
|
We select the
virtual property
| |
|
Filters work similar to the SQL
| |
|
The
| |
|
To control the sorting of the result set, you can specify one or more columns in the
|
The rest of this section contains an enumeration of FxSQL features along with example queries demonstrating that feature.
When search results are displayed to the user, it is often desirable to let the user choose the
displayed columns. For this purpose, the
ResultPreferencesEngine
provides a simple API for retrieving
and setting the preferred column layout based on the result type (e.g. Articles or Images). To
select these user-defined column layouts, add the
@*
virtual property:
SELECT caption, @*
This is the only virtual property that actually expands to multiple columns, so it should appear at the end of the select clause.
To select all available columns, use the wildcard selector
*.
The wildcard expands to all properties of the content type returned to the user.
When the result rows contain objects of more than one type, only the properties assigned
to the root type are selected (i.e. properties that are defined for all types).
This example selects all properties of the article type, since only articles
(with a caption) will be returned:
SELECT * WHERE #article/caption IS NOT NULL
A main feature of the [fleXive] search engine is full-text indexing. By specifying the "*" property in the where clause, you will search across all indexed properties of a content.
SELECT @pk, caption WHERE * = 'test'
Note that the only supported operator for full-text queries is "=" (equals) and that properties have to be enabled for fulltext indexing (See the section called “Property parameters”).
When specifying a search property by name, you search across all properties of the given name. If you want to search in specific assignments, i.e. only in the address assignment of your contact data type, you have to either specify the assignment ID (prefixed by a # character) or the XPath prefixed by the type and without cardinalities instead of the property name.
Select the
property
caption
and search in the assignment with ID=25 for main street:
SELECT @pk, caption WHERE #25 like 'main street'
Select the
assignment
ARTICLE/TEASER/TEASER_TITLE
where the assignment
ARTICLE/TITLE
starts with Earthquake:
SELECT @pk, #ARTICLE/TEASER/TEASER_TITLE WHERE #ARTICLE/TITLE LIKE 'Earthquake%'
The
virtual property
@permissions
selects the permissions of the calling user on the content instance:
SELECT @pk, @permissions
The column returns instances of
com.flexive.shared.security.PermissionSet.
[fleXive] also supports searching inside the content tree and returns tree paths of contents linked in the tree.
To select all contents attached to a node or its children, use the
IS CHILD OF
condition and specify
the tree node ID (the root node is always 1).
SELECT @pk WHERE IS CHILD OF 1
To include only contents attached directly to a given tree node, use
IS DIRECT CHILD OF.
SELECT @pk WHERE IS DIRECT CHILD OF 1
To select all tree paths of a content in the tree, use the
virtual property
@path. The column then
returns a
FxPaths
object that contains all tree paths
including their content captions. The standard search result table renders these objects as
breadcrumbs.
SELECT @path
Note that this also returns contents not linked in the tree, so you may want to limit your search to children of the root node:
SELECT @path WHERE IS CHILD OF 1
Tree nodes have a position field that explicitly specifies the ordering of
nodes in a folder. You can access the node position in the
SELECT
and
ORDER BY
clauses using the
virtual property
@node_position.
SELECT @pk, @node_position, caption WHERE IS DIRECT CHILD OF 1 -- select all nodes of the root folder ORDER BY @node_position DESC -- invert tree sort order
FxSQL supports date and time functions similar to MySQL for
FxDate
and
FxDateTime
properties. They can be used to select or query against a partial date value.
For example, this query selects all contents created in March 2008:
SELECT created_at WHERE YEAR(created_at) = 2008 AND MONTH(created_at) = 3
Date and time functions may be used both in the
SELECT
and
WHERE
clauses. When a function is used in a column select, it may also be used
for sorting if you specify the column index instead of the name, for example:
SELECT created_at, SECOND(created_at) ORDER BY 2
| Function | Description |
|---|---|
| YEAR() | Selects the year of a date. |
| MONTH() | Selects the month of a date, month 1 is january. |
| DAY() | Selects the day of the month of a date. |
| HOUR() | Selects the hour. This part is only defined for
FxDateTime
objects.
|
| MINUTE() | Selects the minute. This part is only defined for
FxDateTime
objects.
|
| SECOND() | Selects the second. This part is only defined for
FxDateTime
objects.
|
Of course you can select all system properties, like
acl
or
created_by,
in your query. Some system properties are linked to their backing table.
For example, the
acl
property is linked to the
FXS_ACL
table, so you can select any column in that table using a field suffix:
acl.description
selects the description column, and
acl.color
returns the color code. The most useful field, however, is the virtual
acl.label
field that returns the ACL label in the calling user's language.
Another useful example is to select the user name that created a content, instead of the account ID:
SELECT @pk, created_by.username
The following table contains all system properties that are linked to their backing table, including the most important fields. For a complete field list, please refer to the database schema.
| Property (Table) | Fields |
|---|---|
| acl (FXS_ACL) |
Selects the content ACL. Additional fields include:
|
| created_by, modified_by (FXS_ACCOUNTS) |
Selects the user that has created/lastly modified the content
(
created_by
and
modified_by
both have the same fields). Interesting fields include:
|
| mandator (FXS_MANDATOR) |
Selects the mandator of the content instance. To select the mandator name, use
mandator.name.
|
| step (FXS_WF_STEPS) |
Selects the workflow step of the content instance. You can select the following fields:
|
Filters work similar to the SQL having clause by providing a post-processing filter mechanism on the results of the actual query. That is, filters are applied every time the search query is executed, and are not stored in the cached result tables. This means that changing filters usually does not invalidate the query cache, whereas changing the WHERE clause always does.
Filters applicable to the [fleXive] content table:
TYPE=type
Return only contents of the given type. You may specify either the type name (e.g. "ARTICLE") or the type ID (e.g. "2").
VERSION=AUTO|MAX|LIVE|ALL
Restricts the Version, AUTO defaults to MAX. If version=live is selected and an object has no live version, it is omitted.
SEARCH_LANGUAGES=languageId[,languageId]?
Restrict results to languages contained in the comma separated list of language IDs.
General filters:
BRIEFCASE=briefcaseId[,briefcaseId]?
Results have to be contained in the briefcases
IGNORE_CASE=[true|false]
Query case sensitive?
MAX_RESULTROWS=number
Limit number of rows returned.
To control the sort order of the result set, you can specify one or more
columns in the
ORDER BY clause.
Usually you specify the name of a column previously selected in the
SELECT
clause, but you can also use the 1-based column index. The direction of the sort
(ascending or descending) is set using the
ASC
and
DESC
modifiers, respectively.
The following two queries order the
result by the properties
priority
and
caption
(i.e. first the result is sorted by priority, then by caption), the first
uses named columns, the second specifies the column indices instead:
SELECT @pk, caption, priority ORDER BY priority DESC, caption
SELECT @pk, caption, priority ORDER BY 3 DESC, 2
As described in
the section called “Select user-defined columns”,
@*
expands to whatever columns the user defined for the result content type.
This causes a problem when the result should be sorted manually by one of these columns,
e.g. because the result table has sortable column headers
(as in the result table in the backend administration application): the FxSQL parser has no way
to expand
@*
(because the search has not been submitted yet and thus the content type is unknown),
but yet it must be possible to sort by one of these columns.
For example, this query does not work:
SELECT @* ORDER BY caption DESC -- caption not found in SELECT clause
As a workaround, it is possible to specify otherwise invalid column indices in the
ORDER BY
clause
if
@*
was selected. Of course this may lead to runtime errors if the user-defined columns
are less than the ORDER BY index. In the most probable use case, on-the-fly
sorting of a result table, this is not a problem, since the user
can only sort by columns that have already been rendered.
For example, the following is a valid FxSQL query but relies on the user having defined at least 3 columns for the result type:
SELECT @* ORDER BY 3 DESC -- order by the third column of @*
For testing and documentation purposes you can embed comments in your query. For a standard
line-based SQL comment use
"--",
for an inline or multiline comment use C-style comments in the form of
"/*...*/"
SELECT /* inline comment */ id WHERE id > 0 -- conditions
The following operators are supported by FxSQL. Note that not all data types support all operators.
| Operator | Short name | Description | [fleXive] Datatypes |
|---|---|---|---|
| = | equals | Checks for equality | All except FxBinary |
| != | not equals | Checks for inequality | All except FxBinary |
| <= | less than | Compares a property or assignment to a given constant value | All numerical and time types |
| > | greater than | Compares a property or assignment to a given constant value | All numerical and time types |
| <= | less or equal | Compares a property or assignment to a given constant value | All numerical and time types |
| >= | greater or equal | Compares a property or assignment to a given constant value | All numerical and time types |
| LIKE | string match with wildcards |
Like string equality, but allows the use of the
%
wildcard which matches any string, including the empty string. For example,
"LIKE 'Test%'"
matches all string values that
begin with "Test".
|
FxString |
Currently all conditions in FxSQL queries involve at least one literal value, e.g.
WHERE title = 'test'. The following table lists all supported literal
value formats and the [fleXive] types they may be applied to.
| Type | Example | Description | [fleXive] Datatypes |
|---|---|---|---|
| String | 'string value' | A simple string value. Single quotes are escaped like in SQL using double quotes,
e.g. 'te''st' is the representation of the string value
te'st.
|
FxString, FxHTML |
| Integer | 21 | Any integer number. The valid range depends on the property datatype, e.g. FxNumber (int) or FxLargeNumber (long). | FxNumber, FxLargeNumber, FxSelectOne, FxSelectMany, internal referential datatypes (e.g. ACL, mandator) |
| Date | '2008-03-18' | A date value. Note that in current FxSQL you cannot use a date literal
to compare against a DateTime value - you need to write a manual range
condition for the given day instead, e.g.:
created_at > '2008-03-10 00:00:00.000' AND created_at < '2008-03-11
00:00:00.000'
to find contents created on 2008/03/10.
|
FxDate, FxDateRange |
| DateTime | '2008-03-18 15:23:05.159' |
A precise time value with millisecond resolution. Note that millisecond resolution
is currently only supported for the system properties
CREATED_AT
and
MODIFIED_AT. For other properties the millisecond part is always 0.
|
FxDateTime, FxDateTimeRange |
| Boolean | true | A boolean value (true or false). | FxBoolean |
To ease the pain of having to learn yet another query language, the
SqlQueryBuilder
provides a thin builder interface for FxSQL queries. The query is created
using chained calls to the builder and results in a FxSQL query.
For example:
new SqlQueryBuilder() //.select("@pk", "created_at", "caption") //
.type("article") //
.orderBy("created_at", SortDirection.DESCENDING) //
.getResult(); //
![]()
|
A new query builder is created. | |
|
We select three columns, the
virtual property
| |
|
We want to search only for contents of type
| |
|
Order the result by creation date, return newest articles first
( | |
|
The
|
A call to
SqlQueryBuilder#getQuery()
returns the actual FxSQL query, for example, the code snippet above built this FxSQL query:
SELECT @pk, created_at, caption WHERE (typedef = 3) ORDER BY created_at DESC
The basic query condition specifies
a property or assignment whose value will be compared,
a
PropertyValueComparator
specifying the compare
operator (like equals or greater), and
a constant value to compare the content value against, like "5" or "Test". Comparisons between content values are not supported.
SqlQueryBuilder
offers an overloaded, general-purpose
condition()
method for specifying arbitrary conditions,
and custom condition methods for tree queries and content type constraints:
condition(...)
Add a condition to the query. This method is overloaded to support both
assignment and property queries, for the actual method signatures please refer
to the [fleXive] JavaDoc. If you want to apply a function (e.g.
YEAR(prop)),
you have to use the generic
condition(String, ...)
methods.
isChild(nodeId)
Adds a tree search condition to the query that limits the search to children (direct and indirect) of the given tree node ID. See the section called “Tree Search”.
isDirectChild(nodeId)
Like
isChild, but includes only direct
children of the given node.
type(contentType)
Adds a content type constraint to the query, i.e. the expression will match only contents of the given type. Note that this is semantically different from a content type filter, which will be covered in the next section.
Query filters provide filtering capabilities on top of the search result. Currently only the type and briefcase filters are supported.
filterType(contentType)
Applies a content type filter on the result. The query conditions are
not modified by this statement, so it can be called after the query building
has been finished, which is not the case for the
type()
condition.
filterBriefcase(briefcaseId)
Searches only inside the contents of the given briefcase. When no query conditions are specified, the briefcase contents are returned.
The
SqlQueryBuilder
provides support for building nested conditions like
caption LIKE 'Test%' AND (created_by = 21 OR created_by = 22).
andSub(),
orSub()
Creates a new sub-query whose conditions will be joined with the
AND
or
OR
operator respectively. After creating a sub-query, you can
issue condition builder calls exactly like in the top level.
The top-level query is opened implicitly and uses the
AND
operator.
closeSub()
Closes a sub-query. The implicitly opened top-level query does not have to be closed.
Example 6.13. Creating a nested query
new SqlQueryBuilder() .select("@pk") .type("article") .orSub() .condition("caption", PropertyValueComparator.EQ, "Test caption") .condition("caption", PropertyValueComparator.EQ, "Another test caption") .closeSub() .getQuery();Result:
SELECT @pk
WHERE (typedef = 3 AND (caption = 'Test caption' OR caption = 'Another test caption'))
When submitting a query directly with the
SqlQueryBuilder,
miscellaneous search parameters can be set. Note that these parameters are not
part of the FxSQL query, so if you submit the search by yourself, you cannot use
these methods.
setMaxRows(int)
Sets the maximum number of rows returned by the search query. Useful mostly for paged result tables.
setStartRow(int)
Sets the first row of the search result that should be included in the result set. The first row has index 0.
setViewType(ResultViewType)
Sets the view type (list or thumbnails) when displaying the results to the
user. This is only relevant when you select the
@*
property, which will select the user's predefined result columns
and sort settings based on this view type. The view type as well as the
location can also be specified in the constructor.
For Groovy developers, the
GroovyQueryBuilder
offers an even more convenient way of formulating FxSQL queries. Note that this class
(like the rest of Groovy support classes) is still experimental, so not all features may
work as intended.
Currently the Groovy builder supports defining the column selection, nested
property conditions and briefcase filtering. The query builder actually builds
a query tree similar to the JSF search query editor, so the result object is of type
QueryRootNode. From this class you can get either the final
FxSQL query from the
sqlQuery
property, or the
SqlQueryBuilder
which has been used for building the query from the
queryBuilder
property.
Example 6.14. Specifying a query with the
GroovyQueryBuilder
new GroovyQueryBuilder().select(["@pk", "caption", "@*"]) { filterBriefcase(21) eq("caption", "test") not_empty("filename") or { gt("id", 0) lt("id", 100) } lt("created_at", new Date()) }.sqlQueryResult:
SELECT @pk, caption, @*
FILTER briefcase=21
WHERE (CAPTION = 'test' AND FILENAME IS NOT NULL AND
(ID > 0 OR ID < 100)
AND CREATED_AT < '2008-04-04 11:17:27.886')
The method names (except
select
and
filterBriefcase
)
are the enumeration values of
PropertyValueComparator, so any comparison supported by the
SqlQueryBuilder
is also supported by the Groovy builder.
When a FxSQL query is submitted to the database, all rows are fetched at once and
returned in a
FxResultSet
object. It contains all result rows within the user-defined limits
(unlike the cursor-approach of JDBC), including miscellaneous
information like the selected column names, or row count information.
There are two ways of accessing the result rows:
The
FxResultSet#getResultRows()
method returns an iterable object that returns
FxResultRow
objects
for each row in the resultset. Take a look at the example demonstrating accessing
the row columns both by index and column name.
Example 6.15. Iterating over a
FxResultSet
FxResultSet result = new SqlQueryBuilder().select("@pk", "caption") .condition("caption", PropertyValueComparator.LIKE, "test caption%") .getResult(); for (FxResultRow row: result.getResultRows()) { assert row.getPk(1).equals(row.getPk("@pk")); assert row.getFxValue(2).equals(row.getFxValue("caption")); }
The result row table can be accessed with
FxResultSet#getRows(), which returns a
List<Object[]>. This is best when you need indexed
access in constant time and need only the raw result values (usually
FxValue
objects).
The following code iterates over all rows of a result set:
Example 6.16.
Directly accessing the search results using
getRows()
for (int i = 0; i < result.getRowCount(); i++) { for (int j = 0; j < result.getColumnCount(); j++) { System.out.print(result.getRows().get(i)[j] + ' '); } System.out.println(); }
Sometimes you do not want to work with the full result table, but only need
a list of property values, for example primary keys. The
FxResultSet#collectColumn(int)
method projects a single column of the result set to a linear list.
For example, to collect all primary keys of a [fleXive] type you submit
a query against that type, selecting only the
@pk
column, and then project the first result column using
collectColumn:
final List<FxPK> folderPks = new SqlQueryBuilder().select("@pk").type("FOLDER").getResult().collectColumn(1);
[fleXive] comes with a tree component, accessible via the
TreeEngine
interface with means to create and manage tree based structures and attach FxContent instances to tree
nodes. There are actually 2 logically (and physically in terms of used database tables) distinct trees: a
live
(published) and an
edit
(preview) tree. There always exists a virtual root node with node Id
1
defined in
FxTreeNode.ROOT_NODE
. If there is no content manually attached, [fleXive] will create an instance of type
Folder
and attach it. The same thing applies if a content instance that is attached to a node is removed: it will be
replaced by an autogenerated
Folder
instance. An exception is removal of a content linked to a leaf node. In this case the tree node will be removed
as well.
The tree does not implement its own security but makes use of the permissions of the attached content instances. This means in practice that only those nodes can be seen to which the user has at least read access to. As a consequence any child nodes attached to a node that a user has no read permission for will not be accessible [3] . The same security constraints apply to queries.
Currently the following scripts for tree engine events can be created:
AfterTreeNodeAdded
AfterTreeNodeRemoved
BeforeTreeNodeRemoved
AfterTreeNodeActivated
BeforeTreeNodeActivated
AfterTreeNodeFolderReplacement
Please refer to the section called “Scripting Engine” and the section called “Script administration” for further information.
The tree component supports two different modes: Live andEdit.
In typical web applications a single mode (the edit mode) might be sufficient, but when it comes to applications that are publishing-centric - or if there should be a way to preview how the tree will look before it goes live - using two modes simplifies development a lot.
Activation is the process of copying a node (optionally with all subnodes) from the edit to the live tree. The dirty flag helps to distinguish between the live and the edit tree. Anything changed in the edit tree that is not activated on the live tree is flagged as dirty. As a consequence no node on the live tree can ever be flagged as dirty! If a node with a particular id was removed in the live tree and still exists in the edit tree, then this node and all its children will be flagged as dirty in the edit tree. All nodes in the edit tree that do not exist in the live tree are flagged dirty as well. The dirty flag basically helps to visualize which nodes in the edit tree differ from nodes in the live tree.
The tree API tries to be as intuitive and simple as possible. Please see the API documentation for further reference as we'll just cover some basics in this chapter.
Example 6.17. Creating a new tree node attached to the root node
//Lookup the tree interface TreeEngine tree = EJBLookup.getTreeEngine(); //create new node FxTreeNodeEdit node = FxTreeNodeEdit.createNew("NodeName"); FxString label = new FxString(false, "NodeLabel"); node.setLabel(label); //create the node long id1 = tree.save(node); //load it FxTreeNode node_loaded = tree.getNode(id1); //delete it tree.delete(node_loaded, false)
Example 6.18. Clear both Live and Edit Tree in the groovy console
com.flexive.shared.EJBLookup.treeEngine.clear(com.flexive.shared.tree.FxTreeMode.Edit)
com.flexive.shared.EJBLookup.treeEngine.clear(com.flexive.shared.tree.FxTreeMode.Live)
Quite often it is desireable to adjust (or automatically generate) some values in a content instance, send emails or perform functionality that the original developer of a software component didn't have in mind when he wrote it. Changing the component is one way - but this neither endorses reusability of the component nor the modification of the required actions.
[fleXive] is built with the intention to allow various kinds of scripting. Scripts can be either executed
manually (on demand) or when a certain event occurs. The scope of events are defined in
FxScriptScope
for
types, assignments, binary processing,
user accounts
or
tree
actions.
The scripting language of choice is Groovy, but if JavaSE 6 or higher is used any language supported by the Java Scripting API can be used.
Scripts that are not executed manually or on demand are supplied with context dependent variables in so
called
bindings. A script that is called before a content instance is saved will for example
require a reference to the content instance in its bindings.
Please see the
API documentation of
FxScriptEvent
for further information.
[fleXive] allows the definition of scripts which are executed once during the initial setup of a division
(database) called
run-once
scripts, or for every time the application server is started called
startup
scripts.
These scripts are located in the
resources/scripts/runonce
and
resources/scripts/startup
directories for applications extending [fleXive] and in
src/framework/resources/fxresources/scripts/runonce
and
src/framework/resources/fxresources/scripts/startup
for the framework itself.
The workflow engine is responsible for creating and maintaining workflows. Unless you need to create your own workflows programmatically, the most important information in this section is the conceptual overview which explains the basic concepts of the workflow engine. The [fleXive] workflow engine is a specialized workflow implementation used by the content engine, it does not offer a generic workflow implementation.
A workflow combines a collection of workflow states and the enumeration of all possible transitions between those states. In [fleXive], the following terminology is used:
An abstract definition of a workflow step. It defines a localized label and a unique target.
Supports the concept of unique steps for versioned contents. When a workflow step with a unique target is assigned to a content instance version, all other versions of the instance that are in the same workflow step are moved to the unique target step. Since this transition is executed every time a content instance is saved, there can be at most one content instance version in a step with a unique target at any given time.
The canonical example for this is the Edit/Live/Archived workflow for publishing website contents: a content is created in the Edit workflow step. When it is deemed to be ready for publication, the latest version is set to the Live workflow step and is shown on the web page. When an update is necessary, a new version is created in the Edit workflow step which can be modified without altering the Live instance. When the new version is to be published, it is set to the Live workflow step. However, there already exists an old version in the Live step: to resolve this, the Live step has a unique target (e.g. Archived). When the latest version is set to the Live step, the previous Live version is moved to the Archived step.
A concrete workflow that consists of steps and routes, including the permission configuration for both.
A concrete assignment of a step definition to a workflow, including an ACL that defines the permissions on objects in this workflow step.
A transition between two steps of a workflow, including the user group for which it should be available.
Although workflows can be edited in the backend administration application, you can also setup new workflows programmatically. The workflow engine is spread across four EJB interfaces, each one handling the create/update/delete operations for its part of the workflow: step definitions, steps, routes, and the workflow objects themselves. We'll show how to create a simple workflow from scratch, for a more elaborate explanation of the methods please refer to the [fleXive] JavaDoc.
Example 6.19. Creating a workflow step definition
First of all, we need some step definitions to work with. We start with creating editable
StepDefinition
objects, which are then stored in the database using the
StepDefinitionEngine.
// create step definition objects final StepDefinitionEdit definition1 = new StepDefinition(new FxString("step 1"), "first step", -1).asEditable(); final StepDefinitionEdit definition2 = new StepDefinition(new FxString("step 2"), "second step", -1).asEditable(); // store them in the database and store IDs definition1.setId(EJBLookup.getWorkflowStepDefinitionEngine().create(definition1)); definition2.setId(EJBLookup.getWorkflowStepDefinitionEngine().create(definition2));
Example 6.20. Creating a new workflow with steps and routes
There are EJB interfaces for adding steps and routes to an existing workflow. However, when we create a new workflow we would have to first create an empty workflow, and then create each step and route using EJB calls. To simplify things a bit, the workflow engine can create the steps and routes for a new workflow object using intermediate IDs. An intermediate step or route ID is negative (IDs retrieved from the database are always positive) and can be referenced in routes. Finally we retrieve the created workflow from the environment (workflows are always cached, so there isn't even a method to load workflows directly from the database) and perform some validity checks.
// create a workflow and auto-create steps using intermediate IDs final Step step1 = new Step(-10, definition1.getId(), ACL.Category.WORKFLOW.getDefaultId()); final Step step2 = new Step(-20, definition2.getId(), ACL.Category.WORKFLOW.getDefaultId()); // create a route between step1 and step2 final Route route = new Route(-1, UserGroup.GROUP_EVERYONE, -10, -20); // create workflow object and store it in the database final Workflow workflow = new Workflow(-1, "test wf", "my test workflow", Arrays.asList(step1, step2), Arrays.asList(route)); final long workflowId = EJBLookup.getWorkflowEngine().create(workflow); final Workflow dbWorkflow = CacheAdmin.getEnvironment().getWorkflow(workflowId); assert dbWorkflow.getRoutes().size() == 1; // route available? assert dbWorkflow.getSteps().size() == 2; // both steps available? // check from and to steps of our route assert dbWorkflow.getRoutes().get(0).getFromStepId() == dbWorkflow.getSteps().get(0).getId(); assert dbWorkflow.getRoutes().get(0).getToStepId() == dbWorkflow.getSteps().get(1).getId();
The configuration engine provides a hierarchical storage for configuration properties. It allows to override default parameters with user-specific settings, and uses the distributed cache to avoid database lookups.
A configuration parameter is characterized by the following properties:
The key is a unique identifier for the actual configuration entry.
The path allows to organize configuration entries similar to a file system. Keys are unique (only) for the same path.
The scope tells the configuration engine where the configuration entry can be stored:
for entries that are shared across the entire [fleXive] installation
for entries that are shared across a [fleXive] division
for entries that can be customized by the user.
The scope also includes fallback scopes, for example a user parameter may use division for its fallback scope: the value stored in the division configuration acts as a default value for all users, who can (but do not have to) override this with their own setting.
A parameter is defined using one of the ParameterFactory factory methods. The parameter interface uses Java generics to provide typesafe manipulation of the configuration values, for example it is not possible (using the configuration APIs) to store a String value in an Integer parameter. Let's start with declaring a configuration parameter of type Integer:
public static final Parameter<Integer> INT_PARAM = ParameterFactory.newInstance( Integer.class, /* value class */ "/config/test", /* path */ ParameterScope.USER, "param1" /* key */, 21 /* default value */ );
Our new parameter uses user scope, which (by definition) uses the division configuration as a fallback. This means we can now store values both in the division and user configuration. Of course, unless we are logged in as a global supervisor, we cannot update the division configuration, but any user can update his or her own configuration. By default, the configuration engine uses the "least shared" scope, in our case the user scope. The following call puts a value in the configuration of the calling user and displays the new value:
EJBLookup.getConfigurationEngine().put(INT_PARAM, 123456); System.out.println("User parameter value: " + EJBLookup.getConfigurationEngine().get(INT_PARAM));
Having global supervisor privileges (e.g. in a run-once script) we can also update the fallback value using the division configuration engine (which has the same interface as the configuration engine, but always uses division scope):
EJBLookup.getDivisionConfigurationEngine().put(INT_PARAM, 123456);
The configuration engine supports generic value types through serialization to XML
with XStream. The
ParameterFactory
methods return optimized parameter implementations for most primitive values
(e.g. Integer, Long or String), and uses the XStream-based implementation for everything
else. For example, the following code declares and uses a parameter for values of type
java.awt.Point:
// declare parameter final Parameter<Point> POINT_PARAM = ParameterFactory.newInstance(Point.class, "/config/test", /* path */ ParameterScope.USER, "pointParam", /* key */ new Point(10, 20) /* default value */); // store Point value EJBLookup.getConfigurationEngine().put(POINT_PARAM, new Point(3, 4)); // retrieve stored value System.out.println("Point parameter: " + EJBLookup.getConfigurationEngine().get(POINT_PARAM));
[1] Imagine an online-shop scenario with different kinds of articles, each implemented using their own type. Something they have in common is their name. Instead of having to build a query for searching the name property that is an OR-construct including all involved types it would be easier to assign to all types a property called "name" and query this property instead of the assignments.
[2] The term
content instance
is used to describe all data related to a concrete instance of a
FxType
where values are assigned to properties. A good analogy would be the instantiation (
FxContent
) of a class (
the section called “Structure Engine”
FxType
).
[3] That applies to user interfaces if the tree was loaded from a parent of the inaccessible node. But since any node can act as the root node (which has to be passed when loading the tree) it is possible to see such nodes, if the path from the root node to the desired node is accessible
Table of Contents
[fleXive] offers a set of JSF components for working with [fleXive] contents and data structures. This includes components to render and edit FxContent instances, submit search queries and show their results, and create custom content queries. To make your own reusable components, [fleXive] provides a simple-to-use plugin API for wiring your own reusable JSF and EJB components for other [fleXive] applications.
[fleXive] JSF support consists of two major modules:
The JSF component library contains a set of JSF components for working with [fleXive] structures in any JSF application
The JSF plugin API allows applications or plugins to extend the backend administration application and make themselves extensible.
A comprehensive guide on extending [fleXive] functionality is the section called “Writing reusable [fleXive] components ”.
To use the [fleXive] component library, you have to
Add flexive-plugin-jsf-core.jar to your application's classpath
Add the namespace declaration
xmlns:fx="http://www.flexive.com/jsf/core"
to the top of your XHTML documents.
On every page using [fleXive] components, you have to add a call to <fx:includes/> somewhere in the HTML head section. This tag includes javascript files and CSS stylesheets needed by the components via Weblets.
If you want to use components based on Yahoo UI, you have to include a call to
<fx:yuiSetup/>
near the end of the HTML body section. This initializes all YUI components
on the current page and loads the required Javascript libraries.
You also have to specify the YUI skin class on your page, the most convenient way
is through the body tag, e.g.:
<body class="yui-skin-sam">.
The [fleXive] components for displaying and editing contents are most crucial for any application that works with [fleXive] contents.
The
<fx:content>
component offers direct access to FxContent values through the property XPath.
It also supports groups and multiplicities, i.e. iteration over properties and groups that
may exist more than once in an instance.
When no content ID is specified, a FxType name or ID is passed to create a fresh instance
of the given property type. Instances can then be saved through the
fxContentViewBean,
or any other JSF managed bean that has a FxContent property.
<fx:content var="varname" (pk="pkexpr" | type="typeId" | typeName="typeName") [preserveContent="true|false"]> ... </fx:content>
The name of the variable that provides the content. The actual FxContent instance itself is provided in "#{var}_content", i.e. if var="project", then the FxContent is supplied in "project_content".
A primary key. May be supplied as an FxPK object, a long ID, a String containing a primary key (e.g. "5", "5.1", "5.MAX"), a FxReference, or a FxSelectListItem whose ID will be interpreted as a content ID.
The new content type ID (if no PK was specified)
The new content type name (if no PK was specified)
The content instance to be displayed (if no PK was specified).
If set to true, the content instance will be stored in the JSF viewstate. This is memory-expensive, but is necessary for editor pages that modify the content structure, e.g. pages that allow to add or remove groups or properties. By default, this option is set to false.
A main service of the
<fx:content>
component is to
provide direct access to the property values stored in FxContent instances
through JSF-EL expressions. Inside the body of
<fx:content var="content">,
the following expressions may be used:
#{content.name}
,
#{content['name']}
,
#{content['/name']
Return the FxValue of the property identified by the XPath "/name[1]".
For properties like "/name" that are attached directly to the content root node, the
notations
#{content.name},
#{content['name']}, and
#{content['/name']}
are
equivalent. The following examples won't always specify all possible notations.
#{content.name$label}
Return the assignment label of type FxString for the given XPath.
#{content['group/text']}
Return the given group property. Note that
#{content.group.text}
currently does not work.
#{content.element$list}
Provide a list for all entries of the group (or property) "element". If "element" is
a
property, the iterator returns FxValue objects, otherwise it returns a
list of virtual fx:content instances with a prefix for the current group element
(
Example 7.1, “Render FxContent property values”
illustrates how this feature
can be used for looping over groups with
<ui:repeat>).
#{content.element$listAll}
Like
#{content.element$list}, but includes empty elements.
#{content.reference$.property}
Resolve the given reference property and provides a content for its properties. An
expression can also use several nested resolving operations, e.g.
#{content.reference$.owner$.email}.
#{content['group/text$new']}
Create a new instance of "group" and return its text property. Note: the current implementation is a hack and works only for simple cases.
#{content['group/text$mayCreateMore']}
Returns true if evaluating the $new suffix for the given XPath will succeed. Useful when using the $new suffix for assignments with a limited multiplicity, since it could cause an exception otherwise.
Be aware that
<fx:content>
is a render-time component. That is, the content instance is
provided during view rendering (i.e. between encodeBegin and encodeEnd), but not during view
creation. This means, unfortunately, that it is not possible to use Facelets' compile-time
tags like
<c:forEach>
or
<c:if>
with EL expressions that contain content instances provided by
<fx:content>.
Instead, render-time components must be used to achieve similar results - for example,
<ui:repeat>
to iterate over lists or
<ui:fragment rendered="...">
for conditional page rendering.
Example 7.1. Render FxContent property values
Here the primary key is provided by the managed bean "myBean". The content is stored in the request variable "component".
<fx:content pk="#{myBean.pk}" var="component"> Component: #{component.name} <br/> <!-- Resolve a content reference to a project content with $ --> Assigned project: #{component.project$.name} (PK = #{component.project})<br/> <!-- Iterate over all elements of the "comment" group with $list --> <ui:repeat var="comment" value="#{component.comment$list}"> <h5>Comment by #{comment.author$.caption} on #{comment.created_at}:</h5> <p>#{comment.text}</p> </ui:repeat> <!-- Access via full XPath --> #{component['/comment[5]/author$.caption']} </fx:content>
Example 7.2. Edit an existing FxContent instance
We use the
<fx:value>
component to create an input field for the "name" property of a given instance.
<a4j:form id="frm"> <fx:content pk="#{myBean.pk}" var="component"> <!-- Render an input row for the "name" property of the enclosing content tag --> <fx:value property="name"/> <!-- Save content using the FxContentViewBean, pass the content instance stored in component_content" via f:setPropertyActionListener --> <h:commandButton action="#{fxContentViewBean.save}" value="Create component"> <f:setPropertyActionListener target="#{fxContentViewBean.content}" value="#{component_content}"/> </h:commandButton> </fx:content> </a4j:form>
Example 7.3. Create a new FxContent instance
This page provides a basic input form for creating new instances of the "Component" type. Note that a new content instance contains empty values for all basic properties - that's why this example works. For creating a more complex instance that actually creates new properties, see the next example. Note that this code is basically the same as Example 7.2, “Edit an existing FxContent instance”, except that a typename is passed instead of a PK - there's no difference in code between editing an existing or a new instance (provided that the latter has been properly initialized with empty values).
<a4j:form id="frm"> <fx:content id="componentView" typeName="Component" var="component"> <!-- Render an input row for the "name" property of the enclosing content tag --> <fx:value property="name"/> <!-- Save content using the FxContentViewBean, pass the content instance stored in "component_content" via f:setPropertyActionListener --> <h:commandButton action="#{fxContentViewBean.save}" value="Create component"> <f:setPropertyActionListener target="#{fxContentViewBean.content}" value="#{component_content}"/> </h:commandButton> </fx:content> </a4j:form>
Example 7.4. Add values to properties with multiplicities
Assume type "issue" has a group "comment" with multiplicity 1..n. In order to edit existing instances, you could iterate over the existing using the $list suffix. For creating a new "comment" group, you specify the XPath without multiplicities and set the "new" attribute.
<a4j:form id="frm"> <fx:content pk="#{myBean.pk}" var="issue"> <!-- Enter a new comment --> <fx:value property="comment/text" new="true"/> <!-- Save button etc... --> </fx:content> </a4j:form>
Example 7.5. Add a new empty group to a content instance
Assume you're editing a contact data type with a group named ContactPerson. You want to render editors for all existing contact persons, as well as an empty one for adding new contacts. This is accomplished with the fxContentViewBean which has a method for creating new empty elements or groups called "add".
<!-- Specify preserveContent="true" to keep structural changes made to the content instance --> <fx:content pk="#{myBean.pk}" var="data" preserveContent="true"> <!-- Show editors for all existing contact person groups stored under the XPath /contactPerson --> <ui:repeat var="contactPerson" value="#{data.contactPerson$listAll}"> <fx:fieldSet legend="Contact Person"> <fx:value var="contactPerson" property="name"/> <fx:value var="contactPerson" property="email"/> </fx:fieldSet> </ui:repeat> <!-- Render a button to add a new contact person group to the "data" content instance --> <h:commandButton action="#{fxContentViewBean.add}" value="Add another person"> <!-- Pass the content instance to the JSF bean --> <f:setPropertyActionListener value="#{data_content}" target="#{fxContentViewBean.content}"/> <!-- Specify the XPath of the element to be added --> <f:setPropertyActionListener value="/CONTACTPERSON" target="#{fxContentViewBean.xpath}"/> </h:commandButton> </fx:content>
Example 7.6. Use InputMappers for application-specific properties
Some content properties cannot be populated in a generic way. InputMappers wrap the FxValue provided by the content instance in another FxValue, for example a FxSelectOne select item value.
<a4j:form id="frm"> <fx:content pk="#{myBean.pk}" var="issue"> <!-- Provide a list of contents for this reference property --> <fx:value property="owner" inputMapper="#{myBean.accountMapper}"/> <!-- Save button etc... --> </fx:content> </a4j:form>
In this very basic implementation the input mapper is constructed from a collection of PKs returned by our business method "someOtherBean.getAccountPks()". There already exists an input mapper for FxPK objects in the flexive framework that takes a select list and interprets the item IDs as PK IDs. A "real" input mapper would probably use less generic labels and use the account caption instead.
public class MyBean { public InputMapper getAccountMapper() { FxSelectListEdit selectList = new FxSelectList("Accounts").asEditable(); for (FxPK pk: someOtherBean.getAccountPks()) { new FxSelectListItem(pk.getId(), selectList, -1, "Account " + pk); } return new FxPkSelectOneInputMapper(selectList); } }
The
<fx:children>
component provides an iterator over properties and groups of a content instance.
The current value is stored in a variable with the same name as the property.
The same can be achieved by using the
$list
JSF-EL suffix as described in
the section called “Accessing property values with JSF-EL”.
<fx:content ...> <fx:children value="content" property="xpath" [var="varname"]> ... </fx:children> </fx:conent>
The name or XPath of the property or group to be iterated upon.
The content or group value holding the required property.
Name of the variable where the current value will be exposed in the tag body. If not set, the property name will be used (i.e. the last part of the given property XPath).
Note: due to its implementation, if a
fx:children
component is nested in another
fx:children
that has a var attribute, the inner
fx:children
instance also must specify the var attribute.
The following examples are based on the products demo application described in an ONJava.com article.
Example 7.7. Iterating over property values
In this example, we iterate over the
image
property of a product. The property is of type
Binary
and holds one or more images for the given product.
<fx:content var="product" pk="#{productBean.pk}"> <fx:children value="#{product}" property="image"> <fx:thumbnail binary="#{image}"/> </fx:children> </fx:content>
Example 7.8. Iterating over group values and nested iterators
In this example, we create an outer loop for the group property
variant
and use a nested
<fx:children>
component to render the images for each group.
Data structure (partial):
<fx:content var="product" pk="#{productBean.pk}"> <!-- Iterate over the "variant" group of product --> <fx:children value="#{product}" property="variant"> <!-- Iterate over the "image" property of the current variant --> <fx:children value="#{variant}" property="image"> <fx:thumbnail binary="#{image}"/> </fx:children> </fx:children> </fx:content>
For writing FxContent editors, the
<fx:value>
component provides a complete property value input row consisting of
the assignment label translated in the current user's locale, and
a
<fx:fxValueInput>
instance that renders suitable input tags for all supported data
types, including multilanguage and binary support.
The output is controlled by the
<fx:formRow>
component.
All parameters of
<fx:formRow>
can also be set for
<fx:value>, except those that control the actual value being
rendered.
Note:
<fx:value>
is a Facelets compile-time component, this means that its input variables must be bound during
view creation. Data exposed by components like
<ui:repeat>
is not available during "compile time" and cannot be used as input for
<fx:value>. Use compile-time iterators such as
<c:forEach>
instead.
<fx:value property="xpath" [var="varname"] [labelKey="messagekey"] [any attribute for fx:formRow]/>
The XPath of the property.
The value of the "var" attribute of the
<fx:content>
component whose content should be edited. If nested directly in a
fx:content tag, this attribute can be omitted.
An optional message key that overrides the assignment label.
For basic usage examples, look at the
the section called “Examples”
in the
<fx:content>
documentation.
For examples on controlling the output of the
<fx:formRow>
component, see
the section called “Examples”
of that component.
The
<fx:fxValueInput>
component is a universal
component for editing FxValue objects. It supports every datatype of FxValue,
for example plain text, numbers, dates, or even binaries. It is used in the
backend administration application content and search query editors and in every other place
where a FxValue needs to be edited. It also includes multilanguage support
by providing a language select list next to the input elements.
The default input language of multi-language values can be set in the user preferences
of the backend administration application or programmatically through the user configuration parameter
com.flexive.shared.configuration.SystemParameters.USER_DEFAULTINPUTLANGUAGE.
<fx:fxValueInput value="fxvalue" [id="clientid"] [externalId="externalId"] [readOnly="true|false"] [readOnlyShowTranslations="true|false"] [forceLineInput="true|false"] [inputMapper="inputMapper"] [valueFormatter="valueFormatter"] [filter="true|false"] [containerDivClass="CSS class"] [disableMultiLanguage="true|false"] [disableLytebox="true|false] [onchange="onchange"] [autocompleteHandler="handler instance"]/>
An expression of type FxValue of the value to be edited. Note that the value must not be null, otherwise the input component cannot determine the actual FxValue type.
An optional client ID of the input component.
An optional external ID that overrides the clientId in JSF validation error messages - may be necessary in complex, generic forms, but usually you won't need this.
If true, only the value itself will be rendered.
If true, all translations of a property will be shown, else only the best matching translation (in the calling users language or the language marked as default).
For multiline and HTML properties, force to render a single-line input field (e.g. for search query forms).
An optional input mapper for rendering the input control. For example, properties referencing other content may need a custom input mapper that provides a select list of content instances. For more information take a look at the InputMapper documentation and at Example 7.6, “Use InputMappers for application-specific properties”.
A
FxValueFormatter
to be used for rendering the value (in read-only mode only). If not specified,
the JSF renderer will choose the appropriate default formatter for
the actual value class.
If set to false, HTML output will not be filtered. If set to true (the default), HTML entities will be used for sensitive characters (e.g. "<" instead of "<").
If this CSS class property is set, the component will be embedded in a div-container using this CSS class as style.
Disables multi language support even if the FxValue object is multilingual. Only the default translation will be displayed and updated.
Set this property to true if the Lytebox javascript library used for rendering inline previews of images should not be used.
An optional javascript expression to be called when an input element changed its value.
An instance of the
flexive.yui.AutoCompleteHandler
javascript class. This class provides a query method for a
client-side autocomplete widget provided by Yahoo UI, as well as a method
for formatting the response.
Example 7.10. An autocomplete handler for user names
In this example, we use the predefined JSON/RPC autocomplete provider AutoCompleteProvider#userQuery(String query). It searches for user account names based on the user input and returns possible matches.
<script type="text/javascript"> <!-- // Initialize the Javascript handler that submits the queries via JSON/RPC and evaluates the response var userAcHandler = new flexive.yui.AutoCompleteHandler(function(query) { // execute JSON/RPC call and evaluate the response return eval("(" + flexive.util.getJsonRpc().AutoCompleteProvider.userQuery(query) + ")"); }); //--> </script> <fx:fxValueInput value="#{backingBean.userName}" autocompleteHandler="userAcHandler"/>
The backing bean property
#{backingBean.userName}
is a property of type
FxString
and will hold the submitted user name.
Example 7.11. Writing custom autocomplete handlers
An autocomplete provider returns its response to a query in a twodimensional Javascript array. An entry consists of:
The value that will be set in the text input (index: 0).
The displayed label in the autocomplete box, possibly including markup (index: 1).
The Javascript class
flexive.yui.AutoCompleteHandler
provides a small wrapper that is used by the fxValueInput component. To implement
a custom wrapper you need to overwrite the
query
method and perform the autocomplete query inside. This can (but does not have to)
include a server call, e.g. using JSON/RPC. In this example, we define a new query
function that automatically generates "suggestions" based on the input value
by appending a suffix:
<script type="text/javascript"> <!-- var customHandler = new flexive.yui.AutoCompleteHandler(function(query) { var q = decodeURIComponent(query); return [[q + "/a", q + ": <i>a label</i>"], [q + "/b", q + ": <i>b label</i>"], [q + "/c", q + ": <i>c label</i>"]]; }); //--> </script> <fx:fxValueInput value="#{backingBean.fxStringValue}" autocompleteHandler="customHandler"/>
Provides a select list that switches the input language of all
<fx:fxValueInput/>
components on the current page. This tag is implemented in JavaScript,
and it does not cause an extra round-trip to the server.
The resultValue tag is a specialized wrapper for the
<fx:fxValueInput>
to be used for rendering a column value of a search result row.
If the value passed is an instance of
FxValue, a read-only instance
of
<fx:fxValueInput>
is created which then renders the given value.
Otherwise (e.g. for
FxPK
primary keys or primitive values)
a generic formatter is used, which can be customized e.g. for formatting
of tree paths with a link formatter.
<fx:resultValue value="value" [linkFormatter="formatter"] [itemLinkFormat="format"] [contentLinkFormat="format"]/>
The value to be rendered. Can be of any type.
A
ContentLinkFormatter
instance that will be used for rendering links to contents.
This applies to the rendering of the virtual properties
@pk
and
@path, as described in
the section called “Tree Search”.
If not specified, the default
ContentLinkFormatter
will be used. For a description of the supported link format,
please refer to the [fleXive] JavaDoc of this class.
The format string passed to the linkFormatter for content links, i.e.
FxPK
values. For a description of the supported link format of the default formatter,
please refer to the [fleXive] JavaDoc of this class.
The format string used for
FxPaths
values as returned by the virtual
@path
property. For a description of the supported link format of the default
formatter,
please refer to the [fleXive] JavaDoc of this class.
A basic example of using
<fx:resultValue>
can be found in
the section called “Tutorial 1: The Document Store Application”.
Iterates over a collection of content instances. This component is essentially an iterator wrapping a fx:content component. The content instances can be specified through nested Groovy queries or a SqlQueryBuilder instance.
<fx:contentList var="varname" [indexVar="indexVarName"] [queryBuilder="queryBuilderExpr"] [explode="true|false"]> [<f:facet name="groovyQuery"> ... </f:facet>] [<f:facet name="header"> ... </f:facet>] [<f:facet name="empty"> ... </f:facet>] ... </fx:contentList>
Name of the variable that provides the content. The actual FxContent instance itself is provided in "#{var}_content", i.e. if var="project", then the FxContent is supplied in "project_content".
Optional name for a variable that exposes the current iteration index.
An expression of type SqlQueryBuilder that is used for selecting the contents.
Indicates whether the content instances should be "exploded", i.e. whether empty properties should be initialized. Useful for content editors. Defaults to false.
Embeds a query using FxQueryBuilder directly in the HTML page. For example:
<f:facet name="groovyQuery"> like("caption", "A%") type("myArticleType") </f:facet>
An optional header row. Rendered only if at least one content instance is available.
An optional empty message. This is the only output that is rendered if the query returns no contents, and if no "empty" facet is specified, nothing is rendered at all.
Renders a thumbnail for a given
FxPK
or
FxBinary
reference.
The primary key of the content instance. If the PK is specified, the default thumbnail of the content is rendered.
A specific binary entry of type
FxBinary
to be rendered. This is useful for for content types with more than one binary
property. For an example, see
the section called “
<fx:children>
”.
If true, only the thumbnail URL will be rendered (relative to the context root,
e.g.
thumbnail/pk5.1/s0).
The desired size of the thumbnail. Valid values are taken from BinaryDescriptor.PreviewSizes:
PREVIEW1,
PREVIEW2
(default),
PREVIEW3,
ORIGINAL.
Renders a Yahoo UI datatable widget for a [fleXive] result set. This provides an easy way of rendering a result to the user, and it includes a paginator widget.
<fx:resultTable var="name" value="query" [viewType="list|thumbnails"] [clickHandler="clickHandler"]/>
The name of the Javascript variable that exposes the Yahoo UI DataTable widget.
The result set to be rendered, or a plain text FxSQL query. The following types are supported:
String
A FxSQL query, for example:
SELECT @pk, #image/caption
com.flexive.shared.search.FxResultSet
The result of a FxSQL query.
com.flexive.shared.search.query.SqlQueryBuilder
A FxSQL query builder instance the will be used for fetching the result set.
The view type of the datatable. "list" renders a common table view for each of the columns, while "thumbnails" renders only the thumbnails as in the backend administration's thumbnail view. Note that the thumbnail view only works if the object's primary key ("@pk") was selected in the query, otherwise no valid thumbnail image can be rendered.
An optional Javascript method for handling clicks on the table with
signature
function handler(targetEl, pk).
The primary key parameter is only available if the @pk column
was selected in the FxSQL query.
Example 7.12. Using fx:resultTable to submit and render FxSQL queries
The following code snippet renders two result tables: the first in thumbnail mode,
the second in list mode. The query is formulated in FxSQL and is passed using the
value
attribute.
<fx:resultTable var="thumbnailsTable" value="SELECT @pk, #image/caption" viewType="thumbnails"/> <fx:resultTable var="listTable" value="SELECT *"/>
Provides support for updating a result table after an Ajax request. This allows to combine Ajax4jsf with the client-side fx:resultTable widget. After an Ajax response was rendered, this tag updates a result table with the current result values. Take a look at the backend administration's search results page for a more sophisticated example.
The name of the javascript variable that exposes the Yahoo UI DataTable widget.
The result set to be rendered, or a plain text FxSQL query - see <fx:resultTable> documentation.
Example 7.13. Updating a resultTable through an Ajax4jsf update
When re-rendering this Ajax4jsf output panel (e.g. through a command button or an onchange listener) the result table of the given name will be refreshed based on the current result:
<a4j:outputPanel id="refreshResults"> <fx:resultTableUpdater var="resultTable" value="#{fxSearchResultBean.result}"/> </a4j:outputPanel> <!-- Render the result table (this part of the page should not be included in the Ajax response) --> <fx:resultTable var="resultTable" value="#{fxSearchResultBean.result}"/>
A color picker based on Yahoo UI, shown in a popup dialog. The color value will be written to the given form element (a text or hidden input element). The color picker can be nested directly in a JSF input element (e.g. h:inputText). In this case it will be rendered directly before the actual input element.
ID of the input element where the picked color value will be stored. If not set, the parent component ID will be used.
Example 7.14. Using the color picker with a JSF input component
In this example, an existing JSF text input component will be augmented with a color picker.
<h:inputText id="color" value="#{myBean.color}" size="7"> <fx:colorPicker/> </h:inputText>
Example 7.15. Using the color picker for a form input
The color picker can be used "stand-alone" for any text-based form input.
To do so, just set the form input element ID with the
inputId
parameter.
<input type="text" id="myinput" size="7"/> <fx:colorPicker inputId="myinput"/>
Includes external resources (CSS and JavaScripts) used by [fleXive] components. Place this in the
<head>
section of your HTML page.
<fx:includes [all="true|false"] [htmlEditor="true|false"] [jsonRpc="true|false"] [yui="true|false"]/>
If true, includes all resources supported by the tag. This leads to a performance penalty for loading and initializing the Javascript libraries, but is more convenient if performance is not critical.
If true, the HTML editor (currently TinyMCE) will be loaded and
initialized. Since TinyMCE must be initialized in the
<head>
section, unfortunately you have to set this attribute manually
when a
<fx:fxValueInput>
component is used for a HTML property.
Includes the JSON-RPC-Java library for Ajax calls via
JSON/RPC (the Javascript proxy can then be obtained with
flexive.yui.getJsonRpc()).
Includes the Yahoo UI bootstrap library. If you want to use Yahoo UI components,
you also have to include a call to
<fx:yuiSetup/>
near the end of your page. You also have to specify the skin class on your page, the
most convenient way is through the body tag, e.g.:
<body class="yui-skin-sam">.
Note that you can include the
<fx:includes>
tag more than once. This is useful if a component (e.g. the HTML editor)
is only needed on some pages and
<fx:includes>
is already included in the main page template.
Initializes the Yahoo UI widgets on the current page and loads all required libraries. Note that you still have to initialize the YUI bootstrap loader using <fx:includes yui="true">.
When using Yahoo UI you have to specify the skin class on your page, the most convenient way
is through the body tag, e.g.:
<body class="yui-skin-sam">.
Renders a row container for a single input field
and renders a label for the input element. The input field(s) are stored in the tag body.
You can set a CSS-based tooltip using the tooltipKey attribute, or use labelKey and
append ".tooltip" to the base message key (e.g.
message.key
and
message.key.tooltip).
<fx:formRow id="inputid" [label="label"] [labelKey="messagekey"] [newLine="true|false"] [width="widthInPx"] [height="heightInPx"]> [input element] </fx:formRow>
The client ID of the input element contained in the body.
The input element label.
The input label tooltip.
The input element label key (overrides label).
Message key of the tooltip (overrides tooltip, if not specified and labelKey is set,
it defaults to#{labelKey}.tooltip)
Specifies whether a newline should be rendered after the input row (default: true).
Sets a fixed width on the row div (in pixels).
Sets a fixed height on the row div (in pixels).
Renders a HTML fieldset container for a group of formRow elements.
<fx:fieldSet [(legend="legend" | legendKey="legendKey")] [width="widthInPx"]> ... </fx:fieldSet>
The legend to be displayed in the fieldset header.
The message key of the localized message for the legend (overrides legend).
An optional attribute to specify a fixed width in pixels.
[fleXive] provides a set of JSF managed beans for working with [fleXive] in JSF applications. Some of them are usually used only in administration pages, some will probably be used in every [fleXive] application. For a complete reference consult the [fleXive] JavaDoc.
The fxContentViewBean provides a basic wrapper for the EJB content engine.
It is used to create and update instances, usually provided by the
<fx:content>
component.
To save a FxContent instance, you have to set the
content
property
of fxContentViewBean. Usually this is done with a JSF setPropertyActionListener,
take a look at
Example 7.2, “Edit an existing FxContent instance”.
You can also specify an optional success message that will be added
to the current JSF context after the content has been saved, using the
successMessage
property of fxContentViewBean.
When a content instance is created or saved, fxContentViewBean stores the primary key in the request, which can be used for URI rewriting as demonstrated in the section called “URI rewriting for contents”.
You can also add empty values to an existing content instance. This is especially useful for adding empty group elements for generic editors, as demonstrated in Example 7.5, “Add a new empty group to a content instance”.
The fxAuthenticationBean provides a simple wrapper for signing users on and off using [fleXive] security.
To create a login form, you have to
bind form inputs to the
username
and
password
properties
of fxAuthenticationBean,
add a JSF commandButton or commandLink that invokes the
fxAuthenticationBean.login
action, and
add a navigation case for the
loginSuccess
result.
If login fails,
login
is returned instead.
If desired, a checkbox mapped to the
takeover
property can be added to the login form.
It has to be checked, if other clients are logged in using the same credentials
(they will be logged out), otherwise logging in will fail, if the account does not
support
concurrent logins.
A logout link calls the
fxAuthenticationBean.logout
action method. The logout method returns
login
as a result.
Logging out always succeeds (except for internal errors), so no other navigation
case is necessary.
Be sure to perform a redirect on the navigation target since the HTTP session
is invalidated on logout (otherwise you may get
"Session already invalidated"
errors). For example:
<navigation-case> <from-outcome>login</from-outcome> <to-view-id>/pub/login.xhtml</to-view-id> <redirect/> </navigation-case>
The fxMessageBean is an application-scoped bean that provides a localized message lookup service for JSF. Instead of a single resource bundle as provided by JSF's loadBundle, the fxMessageBean provides centralized access to the messages of all [fleXive] applications and plugins available in the current context.
The fxMessageBean implements the map interface and returns localized
messages for resource keys. For example,
#{fxMessageBean['Global.welcome']}
returns the message defined for the key
Global.welcome
for the current user's locale.
Message texts can include positional arguments that will be replaced
when the message is rendered. You can add a comma-separated
list of literal replacements or JSF-EL expressions to the resource key.
For example, the message
Global.welcome=Welcome, {0}
could be formatted using
#{fxMessageBean['Global.welcome,#{myBean.userName}']}.
[fleXive] relies on fixed filenames for application and plugin messages. If you place a file with the given name in your application or plugin package, it will be automatically detected on startup by the fxMessageBean and will be available to all clients.
The [fleXive] resource bundle base names are:
For applications like the backend administration application, for example
ApplicationMessages.properties
or
ApplicationMessages_de.properties.
For plugin packages, for example
PluginMessages.properties
or
PluginMessages_de.properties.
[fleXive] includes a classpath resource resolver for Facelets that allows any Facelets resources (especially XHTML pages) to be included directly from JAR files in the classpath, instead of storing them in a web application archive (WAR).
The classpath resource resolver is declared in the [fleXive] components'
faces-config.xml,
so no further steps are necessary for enabling it. The classpath resolver acts
as a fallback for the common Facelets resource resolver, i.e. if a resource
cannot be found using the default Facelets lookup, it attempts to load the same
path using the context class loader.
For example, if you store
/mywebdir/mypage.xhtml
in one of your JAR files, you can reference it just with this path in any of your
JSF config files, like the
faces-config.xml.
It will then be delivered directly from your JAR file.
While [fleXive] builds on existing JavaEE 5 infrastructure, it integrates additional services that make writing components for [fleXive] applications even easier.
Full EJB3 support in your applications and plugins, you can use existing EJBs or create new ones, and call your EJBs from [fleXive] via scripting.
Easy integration of JSF components and beans through JSF infrastructure, i.e.
using a packaged
faces-config.xml.
Serve Facelets templates directly from the JAR archive or any other location in the classpath.
Weblets can be used for serving content that is not handled by Facelets, like additional JavaScript files or stylesheets.
A compact plugin system allows to extend the backend administration application and make your own application or plugin extensible in a typesafe manner.
Setup your plugin or application using startup scripts that have full access to the [fleXive] API.
JSF components provide a simple and powerful way of providing additional functionality
to existing applications, for example a forum or comment section.
A component packages all Java code and web resources into a single JAR file that can
be dropped into the
lib/
directory of an existing
flexive.ear
file. After the next [fleXive] startup it is available to all applications contained
in the [fleXive] archive.
The [fleXive] distribution contains a build target to create the directory structure of a standalone component, including a build file to compile and package the component.
To create a new component, open the command shell and go to the directory containing
the [fleXive] distribution. Execute
ant. You are greeted by the welcome screen of the flexive
build tool. Enter
component.create
as the target name and supply the name of your component (e.g.
test).
When you confirm the input, a new component directory layout is initialized in
the distribution's parent directory, in this case in
../test. Your screen should look approximately like this:
Buildfile: build.xml
info:
[flexive]
[flexive] Welcome to the flexive build tool. Feel free to use the following build targets:
[flexive]
[flexive] project.create
[flexive] Creates a new flexive project directory.
[flexive]
[flexive] component.create
[flexive] Creates a new flexive UI component directory.
[flexive]
[flexive] db.create
[flexive] Create or reset the database schema of a flexive division.
[flexive] Warning: if the schema already exists, it will be dropped (i.e. you will lose all data
[flexive] stored in the schema).
[flexive]
[flexive] db.config.create
[flexive] Create or reset the global flexive configuration schema.
[flexive]
[input] Please enter a target name, or quit to exit:
component.create
check:
component.create:
[input] Name of the component you want to create:
test
[flexive]
[flexive] Please confirm your input:
[flexive] Component name: test
[flexive] Base directory: ../test
[flexive]
[input] Are these settings correct? ([y], n)
y
[mkdir] Created dir: /tmp/test
[copy] Copying 8 files to /tmp/test
[copy] Copied 13 empty directories to 6 empty directories under /tmp/test
[copy] Copying 1 file to /tmp/test
[echo] Component test created successfully. The component root directory is
[echo] ../test
Change to your component directory in
../test. The directory structure is similar to a [fleXive]
project, the main difference is that there is only one source folder since all
classes will be packaged into one JAR file.
. |-- build.xml |-- resources | |-- META-INF | | |-- faces-config.xml | | |-- flexive-plugins-config.xml | | `-- weblets-config.xml | |-- messages | |-- scripts | | |-- library | | |-- runonce | | `-- startup | |-- templates | `-- weblets |-- src | `-- java `-- web
src/java
contains the Java sources of the component.
web
contains the web documents of the component, e.g. configuration screens for the component.
resources/META-INF
contains the component's configuration files for JSF, the [fleXive] plugin descriptor, and the Weblets configuration.
resources/messages
contains the component's localized message resources that can be accessed with the fxMessageBean.
resources/scripts
contains the run-once, startup and library scripts of the component.
resources/weblets
contains the Weblets that serve resources like images or stylesheets directly from you component JAR file.
resources/templates
is the standard folder for Facelets templates. You can choose any folder of course if you like, but then you'd have to modify the build script.
To check if your build environment is working, execute
ant
in the component's base directory.
An (empty) component JAR file should now be created in
dist/test.jar. You can open the component in your favourite IDE now
and start adding classes, templates and web pages. If you move the component directory
to another directory (e.g. to maintain components in a larger [fleXive] project)
be sure to modify the path to the [fleXive] distribution in
build.xml.
To deploy the component, you have to make it available to the [fleXive] application's classpath.
The easiest way to do this is to add the JAR file to the
lib/
folder of your
flexive.ear
file.
If you want to add it to your own [fleXive] project created by the
build tool, you can copy it to the project's
lib/
directory. When you build the EAR file, it will be added to the archive's
lib/
folder and will be automatically deployed when [fleXive] starts up.
A JSF component is a JAR file that may contain the following elements:
META-INF/faces-config.xml
Registers JSF beans, components, validators, and navigation rules. Note that you can reference XHTML pages from within your JAR file thanks to the classpath resource resolver as described in the section called “Delivering XHTML pages from the classpath”.
META-INF/*.taglib.xml
Register your own Facelets templates. The templates are stored directly in the JAR file and are referenced relative to the META-INF directory. For example, consider the following JAR contents:
./META-INF/mycomponents.taglib.xml ./templates/button.xhtml
To register your button tag, the
mycomponents.taglib.xml
might look like the following:
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://www.mycompany.com/jsf/components</namespace> <tag> <tag-name>button</tag-name> <source>../templates/button.xhtml</source> </tag> </facelet-taglib>
Facelets scans the classpath for
*.taglib.xml
files and will automatically provide the
registered templates and components under the given namespace.
META-INF/flexive-plugins-config.xml
Defines [fleXive] JSF plugins, as described in the section called “The JSF plugin API”.
META-INF/weblets-config.xml
Contains the Weblets configuration if you want to deliver
additional web resources like images or stylesheets. The weblets resources
are stored in the JAR file like Facelets templates, but their base directory
may be any package in the JAR file. For example, the following config file
sets the weblets root directory of the [fleXive] components package to
com/flexive/faces/weblets
and registers the weblet
provider for the URI
/flexive-web-resources/:
<?xml version="1.0" encoding="UTF-8" ?>
<weblets-config xmlns="http://weblets.dev.java.net/config">
<weblet>
<weblet-name>com.flexive.faces.weblets</weblet-name>
<weblet-class>
net.java.dev.weblets.packaged.PackagedWeblet
</weblet-class>
<weblet-version>3.0-SNAPSHOT</weblet-version>
<init-param>
<param-name>package</param-name>
<param-value>
com.flexive.faces.weblets
</param-value>
</init-param>
</weblet>
<weblet-mapping>
<weblet-name>com.flexive.faces.weblets</weblet-name>
<url-pattern>/flexive-web-resources/*</url-pattern>
</weblet-mapping>
</weblets-config>
If you attach
-SNAPSHOT
to the
weblet-version
number, you can disable browser caching for development builds. Otherwise
Weblets will add caching information and you have to change the
version number to ensure that all clients will use the most recent version.
EJB3 components can be packaged in any JAR file and should be deployed automatically
if included in the
flexive.ear
file. At the time of this writing, this mechanism was not working in current versions
of Glassfish and JBoss unless you included an explicit
ejb-ref.xml,
so you have to add an explicit reference to the EJB JAR file in
flexive.ear!/META-INF/application.xml
if you use only EJB3 annotations.
When you develop [fleXive] applications as described in
Chapter 4, Writing [fleXive] applications
,
this is done automatically by the [fleXive] build system.
For further information on EJB packaging rules please refer to [Packaging EJB3 applications], which is also a chapter in the great [EJB3 in Action].
One of the main JSF features is its component-centric design. It allows developers to create component packages as JAR files that can be deployed in existing applications with little or no additional configuration. Prominent examples in the free software world are MyFaces Tomahawk, ICEfaces, or JBoss RichFaces.
[fleXive] encourages the use of all JSF and Facelets features for writing reusable components, and adds its own plugin API to the mix for modifying existing [fleXive] applications, most notably the backend administration application. For example, a plugin developer may extend main menu entries, add new buttons or create whole new administration areas inside the backend administration application.
Currently the plugin API is only available in the JSF layer, although it is not specifically tied to JSF. It is a pure Java solution that may be used for adding plugins to any Java application, it's just that inside [fleXive] most sensible extension points for plugins are in the JSF layer.
The plugin API chapter covers only a small set of features needed for extending [fleXive], for information on writing reusable components see the section called “Writing reusable [fleXive] components ”.
The plugin API is based on three core interfaces and a JSF managed bean:
An extension point defines a "code point" where existing Java code can be extended.
Extension points are parameterized with a concrete sub-interface of a plugin executor . A plugin executor interface exposes the methods which plugins may call to modify application behaviour. For example, a plugin executor for a tree may specify methods for adding or removing tree nodes.
A plugin implements an interface which is parameterized with the type of an extension point, which in turn is parameterized with a plugin executor interface which will be used by the plugin.
The fxPluginRegistryBean acts as a global plugin registry for the application. Plugins can be registered at application startup using the PluginFactory interface.
Extension points and plugin executors are provided by the application developer, the plugin developer only needs to implement a properly parameterized interface.
Example 7.18. Making a [fleXive] application extensible
The intent of the next two examples is to create an extension point which can handle arbitrary plugins attached to it, and then write a plugin for it. The first part is the responsibility of the original application developer, the second one applies to any plugin developer for that application.
The first step for the application developer is to create a
PluginExecutor
interface that defines the methods available to plugins.
In this example, we want to accumulate numbers generated by plugins.
We define the following interface:
public interface TestExecutor extends PluginExecutor { void pushResult(int result); }
The executor takes a result (generated by the plugin), and stores it in an internal data structure. Why do we specify an interface instead of a concrete implementation class? The actual plugin executor implementation depends on the code being extended, and may contain private classes that are not (or should not be) visible to the plugin developer. Also, sometimes it is convenient to implement the executor as an anonymous inner class that has access to the surrounding method's variables, without publishing the actual implementation to somebody else.
Next we declare an extension point, for example in a public shared class:
public static final ExtensionPoint<TestExecutor> EXTENSIONPOINT = new ExtensionPoint<TestExecutor>("ourapp.extensionpoint") { };
We bind the extension point to our executor interface, and specify a unique identifier
(ourapp.extensionpoint).
When a plugin developer does not want to or cannot reference the
EXTENSIONPOINT
constant in his code, he can create a new extension point with the same identifier (and the same
executor interface).
Finally we create the executor implementation:
public class TestExecutorImpl implements TestExecutor { private final List<Integer> results = new ArrayList<Integer>(); public void pushResult(int result) { results.add(result); } public List<Integer> getResults() { return results; } }
The class implements the
TestExecutor
interface, and adds an implementation-specific method to obtain the results. The executor
implementation does not have to be visible to the client, but its interface has to.
Extending our application is the last step. We instantiate a new executor, and ask the fxPluginRegistryBean to execute all registered plugins. Then we collect the result and show it to the user.
final TestExecutorImpl exec = new TestExecutorImpl(); PluginRegistryBean.getInstance().execute(EXTENSIONPOINT, exec); System.out.println("Plugin results: " + exec.getResults());
Take a look at the next example to see how to write a plugin for our application.
Example 7.19. Writing a plugin for a [fleXive] application
Now that our application supports plugins as described in the previous example, we'll show how to write a plugin for it. Remember that the executor interface allows us to send numbers to the application. We'll create a very simple plugin that always sends the number 42.
public class TestPlugin implements Plugin<TestExecutor> { public void apply(TestExecutor executor) { executor.pushResult(42); } }
The executor is passed to the plugin, which then calls the method defined in the executor's interface.
If we're distributing the plugin as (part of) a [fleXive] component,
we can add a
PluginFactory
to register the plugin during [fleXive] startup.
public class TestPluginFactory implements PluginFactory { public void initialize(PluginRegistryBean registry) { registry.registerPlugin(EXTENSIONPOINT, new TestPlugin()); } }
Finally we register the factory with its fully qualified classname as shown in Example 7.20, “Specifying a PluginFactory in flexive-plugins-config.xml ”.
The fxPluginRegistryBean is an application-scoped JSF bean that acts as a central registry for all [fleXive] plugins. It is initialized on application startup and scans the classpath for available plugins. An application can retrieve a list of registered plugins for a extension point or execute a plugin executor on all plugins of an extension point.
The plugin registry has a
registerPlugin
method for registering
plugins at extension points. You can call this method at any time, for example
from the constructor of an application-scoped JSF bean, or use the
PluginFactory
interface to be called by [fleXive] on startup.
To register your plugin automatically during the startup of [fleXive], you have to
implement the
PluginFactory
interface,
set the factory name in
META-INF/flexive-plugins-config.xml
of your package, and
register your plugins in the
PluginFactory's
initialize
method.
The
PluginFactory
specifies a simple callback
interface that lets you register plugins with a
fxPluginRegistryBean. You have to specify the fully
qualified classname of your implementation in a file called
META-INF/flexive-plugins-config.xml,
for example:
Example 7.20. Specifying a
PluginFactory
in
flexive-plugins-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <plugins-config xmlns="http://www.flexive.com/plugins-config"> <plugin-factory> com.flexive.faces.plugin.DemoPluginFactory </plugin-factory> </plugins-config>
For an example of a concrete
PluginFactory
implementation, refer to
Example 7.19, “Writing a plugin for a [fleXive] application
”.
JBoss Seam is a widespread framework for rapid development of applications with JavaEE 5. It provides a neat wrapper around JSF and EJB that removes most of the "glue code" prevalent in many JSF applications that use EJB. Main features include a close integration of EJB persistence (JPA), a conversation scope for JSF, or business process management via jBPM.
This chapter is a quick guide to integrating [fleXive] into an existing Seam application.
To integrate [fleXive] with Seam, you need to
modify your build file to add the [fleXive] libraries to the packages EAR file, and
modify your
web.xml
and
application.xml
deployment descriptors.
Both tasks shouldn't take you more than a few minutes, and both are easy to undo.
First of all, you need unpack a [fleXive] distribution as described in Chapter 4, Writing [fleXive] applications . You don't need to actually start [fleXive], but you need access to the distribution directory.
When you have a local [fleXive] distribution installed, go to you Seam project directory and perform the following tasks:
Modify your
build.xml:
[fleXive] will copy all required libraries to the
exploded-archives
folder every time your application is built.
Include
build.seam-project.xml
at the top of
build.xml.
FLEXIVE-DIST
is the path to your local [fleXive] distribution.
<project name="myseamproject" default="deploy" basedir="."> <import file="FLEXIVE-DIST/build.seam-project.xml"/>
Add a dependency for
copyflexive
to the
archive
target:
<target name="archive" depends="jar,war,ear,copyflexive" description="Package the archives">
To check if everything still works, you should build and deploy your application now.
Add a reference to
flexive-ejb.jar
to
resources/META-INF/application.xml:
<!-- Flexive EJBs --> <module> <ejb>flexive-ejb.jar</ejb> </module>
Add the [fleXive] filter and the Weblets listener to
resources/WEB-INF/web.xml:
<filter> <filter-name>FlexFilter</filter-name> <filter-class>com.flexive.war.filter.FxFilter</filter-class> </filter> <filter-mapping> <filter-name>FlexFilter</filter-name> <url-pattern>*.seam</url-pattern> </filter-mapping> <listener> <listener-class>net.java.dev.weblets.WebletsContextListener</listener-class> </listener>
The
FlexFilter
is needed to provide the [fleXive] context (e.g. the current user ticket)
for every request, and Weblets is used to serve Javascript and CSS files needed by
the JSF components.
Add a suffix pattern for the
Faces Servlet
as the
last
servlet mapping to
resources/WEB-INF/web.xml.
Otherwise all Weblets resources will have a
.seam
suffix instead of the original file extension.
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.seam</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>
To test if the [fleXive] integration was sucessful, add the following code snippet to one of your front-end pages:
#{fxSystemBean.buildInfoVerbose}It should render the [fleXive] version and build number. If no text is rendered, please check the logfile for error messages during the application startup. [fleXive] writes several status log messages on application startup, so you should see at least some messages originating from [fleXive].
When using [fleXive] UI tags on your pages, be sure to declare the
fx
namespace in your XHTML root element:
xmlns:fx="http://www.flexive.com/jsf/core"
You can use all the [fleXive] JSF and EJB components just like in a stand-alone application. There are, however, some issues where extra thought is required for seamless integration between both frameworks.
To use [fleXive] security features, you need to combine the Seam and [fleXive] logins. This also means that any user that should have more than guest user rights must have a valid [fleXive] account.
For example, this code requires a valid [fleXive] account for the authentication to succeed.
You could also ignore the exception, which will leave guest user rights if your own
authentication succeeds. In a default Seam application,
Authenticator#authenticate
might look like this:
public boolean authenticate() { log.info("authenticating #0", identity.getUsername()); //write your authentication logic here, //return true if the authentication was //successful, false otherwise try { FxContext.get().login(identity.getUsername(), identity.getPassword(), true); } catch (LoginException e) { facesMessages.add("Failed to perform [fleXive] login: " + e.getMessage()); return false; } return true; }
To ensure that the user is logged out from [fleXive], you need to extend Seam's
Identity
component. For example:
@Name("org.jboss.seam.security.identity") @Scope(ScopeType.SESSION) @Install(precedence = Install.APPLICATION) @BypassInterceptors @Startup public class FlexiveIdentity extends RuleBasedIdentity { @In FacesMessages facesMessages; @Override public void logout() { super.logout(); try { FxContext.get().logout(); } catch (FxLogoutFailedException e) { facesMessages.add("Failed to perform [fleXive] logout: " + e.getMessage()); } } }
Table of Contents
Provided that you have a working flexive installation, you
start the administration GUI by starting your chosen webserver. You enter
the administration GUI by opening your web browser and entering the address
of the administration GUI startpage. By default the address is
http://localhost:8080/flexive/adm/main.jsf
where
localhost
can to be replaced by your IP address. The port (
8080
in the example) depends on your
Application Servers setting and is the default for e.g. JBoss. After a few moments, you
will reach the login page. If you want to work with the administration
GUI as supervisor (a role that is allowed to do
everything
without any security limitations), the default username to
choose is
supervisor
with its default password
supervisor.
The structure editor allows you to define the structure or blueprints of content instances. In essence, the structure of a content instance is defined by three main building blocks:
A type
The type's assigned groups
The type's assigned properties
The smallest building block is represented by property, which defines what data may be assigned to a content instance and who has access to that data. In order to make life easier, a specific set of properties can be grouped together into a group. A group can then be assigned to a different type later on, therefore saving you the trouble of manually assigning every single property to the type all over again. A more detailed explanation can be found in the chapter about structures.

The structure editor GUI is reachable by clicking the tab (1) in the administration backend. The GUI is separated into two parts: the structure tree (2) on the left half of the screen and the edit view (3) on the right half.
The structure tree consists of the following items:
Content types: types with their mode set to "Content". Types are always displayed
at the root level, possible children are
group and property assignments. All children are assigned to this specific type.
Relation types: types with their mode set to "Relation". Types are always displayed at
the root level, possible children
are group and property assignments. All children are assigned to this specific type.
Group assignments: possible children are group and property assignments. All children
are
assigned to this specific group.
Property assignments: property assignments are assigned to groups or types and act as
the
smallest building blocks and therefore without any children.
System internal property assignments: assignments of special system internal properties
that are common to all types and are intentionally not editable. For clarity reasons,
instead of displaying
those non editable properties for every type, they are only displayed once at the root
level.
Left click: left clicking on an element in the structure tree opens the element in view mode on the right half of the screen. Left clicking on the [+] graphic icon to the left of a specific element expands the tree and shows the children of the specific element. Left clicking on the [-] graphic icon to the left of a specific element collapses the tree and hides the element's children.
Left clicking on the caption of the structure tab opens a drop down menu. The following options are available:
Create a new type.
Right click: right clicking on an element in the structure tree opens a context menu with possible actions. Depending on which element was right clicked different actions are possible:
For types it is possible to:
Open the type in view mode.
Open the type in edit mode.
Create a new property that will be automatically assigned to this type.
Create a new group that will be automatically assigned to this type.
To create content for the selected type (this aciton will open the content editor).
To search for content instances of this specific type (this aciton will open the query editor).
To delete the selected type.

For group assignments it is possible to:
Open the group assignemnt in view mode.
Open the group assignment in edit mode.
Create a new property or group that will automatically be assigned to the selected group assignment's group.
To copy the group assignment, so it can be pasted into another group or type.
To cut the group assignment, so it can be pasted into the same group or type at a different position.
To delete the selected group assignment.
For property assignments it is possible to:
Open the property assignment in view mode.
Open the assignment in edit mode.
To copy the group assignment, so it can be pasted into another group or type.
To cut the group assignment, so it can be pasted into the same group or type at a different position.
To delete the selected group assignment.
Cut: the cut operation is used to change the position of an assignment at its current hierarchy level. To change the position of an assignment, you have to right-click on the assignment you want to move and select "Cut" from the context menu. Then you are able to select another assignment on the same hierarical level, perform a right-click and select either "Paste above" or "Paste below". The assignment which you have cut will then be inserted either above or below the selected assignment. Note that you cannot change the position of system internal assignments at the root level of the structure tree.
Copy: the copy operation is used to create derived assignments and paste them into the structure tree at a chosen position. To facilitate insertion various paste operations are provided:
Paste into: This is the default pasting operation for copied assignments. A derived assignment will be created and pasted into the chosen group or into the chosen type at the last position.
Paste into as: Works like "Paste into" but additionally prompts you specify a new alias so you can create a derived assignment and paste it into the same group with a different alias.
Paste above and paste below: pastes an assignment directly above or below another assignment.
Paste above and paste below as: Works like "Paste above" and "Paste below" but additionally prompts you specify a new alias so you can create a derived assignment and paste it into the same group with a different alias.
By default, left clicking on a structure element in the structure tree opens an editor in view mode on the right half of the screen. In view mode it is not possible to edit the structure element, you can merely view its current settings.
The edit mode on the other hand enables you to edit structure elements and to save changes to the data base. You can switch to edit mode by choosing "Edit" from the context menu or by clicking the "Edit" button in the editor on the right half of the screen. Both view and edit mode provide three different sub editors:
The type editor for creating and editing types and scripts assigned to that type.
The group editor for editing group assignments, editing and creating groups and their options.
The property editor for editing property assignments, editing and creating properties and their options.
Depending on which item is left-clicked in the structure tree, or which item is selected for creation in the context menu of the structure tree, one of the three sub editors is opened in either view mode or edit mode.

When editing an existing type, you will notice that the type's and select boxes are disabled and grayed out. This is due to the fact that the setting of a type's mode and language mode is only allowed when a new type is created. For types that are already stored in the database, content may also already exist which would become invalid if those fields change.
Types with their mode set to are called relation types. For relation types a table is provided to add and remove relations. For adding a relation the user needs to set the source and destination type, together with the maximum multiplicities and click the button in the column. In order to set unlimited multiplicities simply enable or disable the according checkbox in a table row. For specifying the multiplicites manually, first disable the checkbox and use the input field below the source or destination type which appears after disabling the checkbox. Relations can be removed again by clicking on the button in the column.

Also notice that by enabling the checkbox, by disabling the checkbox and for type relations by disabling the and "maxRelDest" checkboxes, input fields for setting the, , and the maximum number of relation sources and destinations become available that are hidden otherwise.


When editing an existing group assignment, you will notice three tabs:
The tab, which gives you access to the group assignment that is selected in the structure tree.
The tab, which gives you access to the actual group that is assigned by the selected group assignment.
The tab, which gives you access to the options of the group assignment and its group as well.
When creating a new group, the tab will be hidden, as well as the options of the group assignment in the tab. The according assignment will then be automatically generated after the group has been created and automatically inserted into the structure tree accordingly.
Notice that assignments may override specific settings of their groups. For group assignments, if the checkbox in the tab, or the checkbox is enabled, the multiplicity defined in the group assignment overrides the multiplicity of its group.
Also notice that the group mode of a group assignment may not be changed if content instances for the group assignment already exist.
Generally speaking an option is just a pair of a key and a value. The user can access the options by left clicking the tab.

In the image above, the group options table and the group assignment options table are displayed. The group has three options with the keys "MYOPTION", "MYOPTION2" and "MYOPTION3". The checkbox indicates which options may be overwritten by the group assignment (i.e. may have the same key but a different value).
In the example above, only the group option with the key "MYOPTION2" and value "myvalue" is marked as overridable. This option is successfully overwritten by the group assignment option with the same key "MYOPTION2" and the different value "another value".
Options can be added by filling in the empty fields on the bottom of the according table and clicking the button which is located in the column of the table. An option can be deleted again by selecting the according row and pressing the button which is also located in the column of the table.
In order to facilitate the editing of options, visual helpers are provided:
Option displayed in green: This is a valid option, for example "MYOPTION" in the group options table.
Option displayed with grey background: The assignment tries to override a group option which is currently not set to "Overridable", for example "MYOPTION3" in the group assignment options table.
Option displayed in red: This is an invalid option. Either the option has no key, or the option has no value, the key already exists or the assignment tries to override an option with the same value. The key "MYOPTION4" exists twice in the group assignment options table, therefore making it invalid.
In order to display the visual helpers, the "Validate" button
on the bottom left has to be clicked.
In order to save changes to existing group assignments, groups and options,
the group editor provides the
button
located at the bottom left of the group editor in every tab. A shortcut to the button
can also be found on top of the content frame.
In order to create a new group, the "Create Group..." option has to be selected from
the context menu of the of the structure tree. Instead of a
button,
the group editor provides the
button
at the bottom left of the group editor view, as well as a shortcut on top of the
content frame. After the group has been successfully created, an according group assignment
is automatically generated as well and automatically inserted into the structure tree.
For deleting an existing group assignment, the group editor provides the
button
at the bottom right of the "Edit Group Assignment" tab. A shortcut to the button can also be
found on top of the content frame.
Note that by deleting a group assignment, the group itsself is not automatically deleted! Only if the group is not assigned to another group or type anymore , the group will be removed from the database.

Similar to the group editor, the property editor provides three tabs:
The tab, which gives you access to the property assignment that is selected in the structure tree.
The tab, which gives you access to the actual property that is assigned by the selected property assignment.
The tab, which gives you access to the options of the property assignment and its property as well.
System internal property assignments
, together with their properties may not be edited. This is why all input elements
are deactivated. It is only allowed to edit their options.
When creating a new property, the tab will be hidden, as well as the options of the property assignment in the tab. The according assignment will then be automatically generated after the property has been created and automatically inserted into the structure tree accordingly.
Note that property assignments may override specific settings of their property. If a specific setting may be overwritten is controlled by the "Override property options" section in the tab and by the "Property override options" section in the tab respectively.

Editing property assignment and property options works exactly the same as editing group assignment and group options. The only difference is that, when creating a new property, a set of standard options is automatically added. If you need more information please refer to the section called “Editing group assignment and group options”.
Also concerning saving and deletion operations, properties and property assignments exactly comply to group and group assignments. The reader is therefore refered to the section called “Saving and deleting group assignments and groups”.
Scripts can be assigned to a type or property assignment by using the script assignment editor, wich is reachable by clicking on the "Scripting" tab. The number in brackets on the "Scripting" tab refers to the number of scripts that have already been assigned. Note that only users that are in the role of "Script Manager" may edit script assignments.
Via the editor it is possible to add, remove and edit existing script assignments. Clicking on a specific script name provides a shortcut to the script editor in the "Admin" section, where the script code can be inspected or changed. A script assignment consists of a specific script and a specific script event, which designates when a script will be run. Naturally, types and assignments provide different subsets of events to choose from.
Furthermore it is possible to activate and deactivate script assignments. For deactivated script assignments, script execution will be prevented. It is also possible to derive script assignments to child types or property assignments. The inherited script assignments of a parent type or property assignment can not be directly in the script assignment editor of the child. Instead a link is provided for derived script assignments, which opens the script assignment editor of the parent type where changes can be made and are in turn propagated to all derived children.
In order for changes to take effect, the script assignment editor -just like the other editors-
provides the
button
at the bottom left of the script assignment editor view. A shortcut to the button can also be
found on top of the content frame.
If you want more information on scripts and their specific settings please refer to the section called “Scripting Engine”.
The instance editor allows you to create and edit content instances.
It can be accessed via the
As you might expect, the content tree and the query results contain already created instances. From the structure tree it is possible to create new content instances which can be attached to the content tree.
By default, the instantiated type's properties are displayed as input fields on the initialization of an instance. Non mandatory properties can be skipped and added again, when needed.
These are the UI elements of the instance editor:
After saving you will get the following screen. You can see that the fields which have not been filled in, disappeared. No matter, you can add them again by clicking on the symbol for the single fields (1) or by clicking on the symbol for the grouped fields (2).
It is possible to create a new version of the instance.
To learn more about versioning in [fleXive] see the Content Engine section.
The content tree contains content instances of any type. It can be used i.e. in a Content Management
System, where the page tree of a website can be administered inside the [fleXive] backend administration.
Mind that when we speak of the content tree, in fact we speak of 2 separate trees. The live and the edit
tree.
This will be explained in detail in this section.
You can find the content tree in the tab
Content Tree.
The content tree GUI allows you to administer the 2 separate trees:
The Live tree holds content instances which have been activated in the edit tree. It is not possible to create content instances directly in the live tree. You can activate tree nodes, representing content instances, or subtrees via the content tree's context menu.
Furthermore it is possible to change the appearence of the node's textual representation. You can choose between the caption and the node path of a tree node.
The UI provides the following elements to handle these different views:
See the core components chapter edit and live modes for additional information.
To activate the context menu rightclick on a tree node. Not all features shown on the following screenshot are always available. For example, attaching a content instance is only possible, if the instance has been loaded into the instance editor before.
The context menu provides the following functionality:
Attach content here
Attach an, into the instance editor loaded, instance to the content tree.
Open
Load an instance into the instance editor in view mode.
Edit
Load an instance into the instance editor in edit mode.
Rename
Rename the instance directly in the content tree's in-place editor.
Create folder
Create a folder on the target node.
Create content ...
Create a content instance on the target node.
Activate
Activate the node for the live tree.
Activate subtree
Activate the subtree for the live tree.
Paste
Paste a formerly copied content into a tree node. The tree node becomes the parent of the pasted content instance.
Cut
Cut a content instance from its position.
Copy
Copy a content instance.
Detach content
Detach a content instance from the content tree. If a node to detach has children they will be shifted onto the detached node's level. See chapter Tree Engine for further details.
Note:
Content instances of type
folder
are deleted permanently if detached.
Delete
Delete a content instance permanently.
The query editor allows you to search for already created content instances. You can formulate complex queries on structures and their properties, it's like building SQL statements visually.
The elements of the query result set can be attached to the content tree or saved into a briefcase, which can be shared among users. A query can also be saved.
To access the query editor go to the
Search
tab of the [fleXive] backend administration. It is also accessible via the
FxSQL / Search
menu item in the
Administration
tab. Open the query editor and rightclick on nodes of the
content tree
and
structure tree
to formulate your query. You can add as many nodes as you want.
You can also search on
structure properties.
Select the chosen query elements with Shift or Ctrl and rightclick on them to use the context menu to create subqueries with con/disjunctions.
The following screen shows a formulated query in the query editor. You can nest your subqueries infinitely.
The following comparison operators are available:
=
Expression is equal
not equal
Expression is not equal
is empty
Expression is empty
is not empty
Expression is not empty
like
Partial match. Use the
%
character, meaning
any character(s),
to build your expression.
Mind that the term
any character(s)
implies
no
character. Expression is case insensitive.
The following examples illustrate the usage of
like
in sql statements and the [fleXive] query editor. Assuming a user with first name
John
exists in the database, you could search for him (and all other matching names) using
the following patterns.
%oh%
Search for
oh
preceded and followed by any character(s), meaning all words containing
oh.
jo%
Search for
jo
followed by any character(s), meaning all words starting with
jo.
If the query with your provided search criteria was successful, the result set will be displayed.
Rightclick on an item of the result set to activate the context menu.
View instance.
Show preview (images).
Edit instance.
Copy instance and
Delete instance permanently.
You can save your query or save the query result to a briefcase by checking the options in the
query editor before executing the query. A briefcase can be public or private, meaning shared or
not shared among other users. Saved queries and briefcases can be found in the
Search
tab of the [fleXive] backend administration.
In addition to the
visual query editor
the [fleXive] administration UI provides an input mask,
shown on the following screen, to
execute
FxSQL
statements directly. It is also possible to create
briefcases
from the result set. The input mask
can be found in the
FxSQL / Search
menu item of the
Administration
tab.
FxSQL is a [fleXive] SQL (structured query language) dialect.
In the following example on the screen the primary keys of all content instances are selected from
the table
content.
The [fleXive] user account administration UI provides an administration interface to administer your user accounts, assign user groups and roles and make other settings like activation and language settings.
The following screen shows the user account editor when creating a new user.
Roles are a key feature of the [fleXive] security concept. There are two ways to assign roles to a user.
After creation, you will be redirected to the user account overview where you can edit and delete users.
The user group editor allows you to create and edit user groups. You can specify a group name, a color and assign roles to the group.
The following screen shows the user account overview. You can see the default system user groups. These groups cannot be deleted.
Every
mandator
gets this group created automatically, in this screen it is the mandator
Default
System user group for all mandators
System user group for all mandators
In this chapter the administration UI for mandators will be explained. Please see section Mandators in chapter Security for an explanation of the concept of mandators used in [fleXive].
The following screen shows the mandator editor. You can specify a name for the mandator and set the activation flag.
The ACL administration UI provides an ACL editor to edit and create ACL's and to set permissions for user groups in the permission matrix. ACL's are a key feature of the [fleXive] security concept.
The following screen shows the ACL editor in create mode.
You can specify
The mandator
The ACL's name
The ACL's label
A color
A category
Please see chapter ACL's - Access Control Lists for information about ACL categories in [fleXive].
The following categories are available:
A description
The permission matrix (1) is available in the ACL editor for already created ACL's. Add rows to add groups and fill in the matrix to grant permissions.
In this example everyone (a user group automatically containing every known account) may read the ACL. Accounts that are a member of the user group Account Managers may read and edit the ACL (please note that Account Managers would already be allowed to read due to their membership to user group Everyone but we added read permission here in case someone decides to remove group Everyone from this ACL at a later date and we still want to keep read permission). Members of the user group Editors may additionally create, delete and export.
On the following screen you can see the default ACL's for each category and a user defined ACL (Public Content Instance ACL).
You can switch between different mandators with the select box on top of the list.
The [fleXive] workflow administration UI allows you to define step definitions and workflows. Step definitions are used as steps in one or more workflows. You can define routes between these steps. See the core component chapter Workflow Engine for additional information about workflows in [fleXive].
The following screen shows the step definition editor. You can define a unique target (1) for a step definition.
The following screen shows the step definition overview.
Create a workflow and provide a name and an optional description.
After the creation of the workflow, the extended workflow editor, shown on the following screen, will be loaded.
The basic elements of a workflow are:
Steps (1)
You have to define a workflow ACL for each step. Steps can have unique targets (2).
Routes (3)
The routes are the transitions between the steps. Content instances can be moved on these routes. Each route must have a user group assigned.
The following diagram illustrates the routes defined in this workflow.
The following screen shows the creation of a contact data verification script. You can see the
select box for the default script event (1) which can be bound to a script. In this case it is
BeforeContentSave.
If the default script event is set to
Manual, the script can be executed via the
administration UI in the
script overview.
| List of available events | ||
|---|---|---|
|
|
|
The following screen shows the script overview. You can see the script
ClearLiveTree.gy
(1), which can be executed manually via the administration UI,
and other scripts, bound to different
events.
The following screen shows the [fleXive] Script console. It allows you to execute scripts directly at the EJB layer.
You can choose between the following languages and file extensions (1):
gy: Groovy v1.5.1 (Bundled GroovyShell v1.5.1)
groovy: Groovy v1.5.1 (Bundled GroovyShell v1.5.1)
js: ECMAScript v1.6 (Mozilla Rhino v1.6 release 2)
If you want to execute the script on the web layer, check the option
Execute at Web layer
(2). Mind that this feature is only available for bundled Groovy!
The Groovy code in this example creates a new tree node in the
Edit tree
of the
Content Tree. The
node's name is
NodeName
and its label
NodeLabel.
You might want to copy paste the example and try it out:
import com.flexive.shared.EJBLookup; import com.flexive.shared.interfaces.TreeEngine; import com.flexive.shared.tree.FxTreeNodeEdit; import com.flexive.shared.value.FxString; //Lookup the tree interface TreeEngine tree = EJBLookup.getTreeEngine(); //create new node FxTreeNodeEdit node = FxTreeNodeEdit.createNew("NodeName"); FxString label = new FxString(false, "NodeLabel"); node.setLabel(label); //create the node long id = tree.save(node);
The
System
menu item of the
Administration
tab contains the
system information
display
and the
language settings.
The system information gives you a brief overview of the system [fleXive] is running on.
In the language settings editor you can manage your system languages. Add a language from the list of disabled languages, delete a language or change the list's order. The language at the first position on the list becomes the default language. The following screen shows the language settings editor.
Check the
Ignore language usage (1)
option to be able to delete languages which are already in use by
content instances, meaning a
property
has been filled in by a user in this language.
The following screen shows the initial step of the select list creation process.
You can specify
Name
The name of the select list
Label
The label of the select list
Description
A description of the select list's context.
Dynamic item creation
The option for dynamic item creation
Create items ACL
The permission matrix of the
Default SelectList Create
ACL
provides only permission settings for
ACL for new items
The permission matrix of the
Default SelectList item
ACL
provides only permission settings for
Mind that you cannot change the ACL's and the
Dynamic item creation
option after creation of the select list.
A select list consists of select list items . The select list editor allows you to add and delete select list items. The following screen shows the select list editor.
For a select list item (1) you can specify
Label
A label for the select list item
Data
Additional data stored in the select list item (i.e. a currency code, a picture ratio)
ACL
The ACL for the select list item
Color
A hex (i.e.
#0033CC) or CSS color code (i.e.
red) for the
select list item
Plugins provide additional functionality for [fleXive] applications. Plugins can provide stand-alone functionality (possibly with web frontends), or be integrated in the backend administration application.
To enable a plugin when compiling a project using the [fleXive] distribution, add it to the
lib
folder of your project. Plugins that are shared by all applications are stored in
flexive-dist/applications.
With Apache Maven, you only need to add the corresponding dependencies to your project.
Core JSF components and managed beans that support working with [fleXive] components (as described in the section called “The [fleXive] component library ”).
Package name: flexive-plugin-jsf-core.jar
Maven package:
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-plugin-jsf-core</artifactId> <version>3.0.2</version> </dependency>
A web-based GUI for the global configuration.
Entry URL: /<context>/globalconfig/index.xhtml
Package name: flexive-plugin-globalconfig.jar
Maven package:
<dependency> <groupId>com.flexive</groupId> <artifactId>flexive-plugin-globalconfig</artifactId> <version>3.0.2</version> </dependency>
When installed, the global configuration plugin provides a web application to manage the global configuration (especially the division table) through the web browser.
Login in your web application context under
globalconfig/index.xhtml.
The default login (which should be changed as soon as you logged in) is
administrator/adminadmin.
You can now define the URL mapping for each division as a regular expression. You can test the mappings by entering example URLs before storing them to the database using the input box after the division table.
More information on using the global configuration plugin can be found in [The Global Configuration Plugin].

identifies a point where an existing [fleXive] application can be extended with a plugin.
See Also plugin, plugin executor.
A concrete implementation of a plugin interface that interacts with a plugin executor interface.
See Also extension point, plugin executor.
An interface that exposes methods available at a given extension point.
See Also extension point, plugin.
A [fleXive] content type definition, for example an article or image type.
An open-source framework for delivering web resources like images or stylesheets stored in the classpath, e.g. in a JAR file, instead of the WAR file system.
A SQL-like query language for [fleXive] contents.
A special property available in search queries. It accumulates miscellaneous information about a result row that may not be available in an actual property, like permission flags or the primary key.
chained hyperlinks that show the tree path of the current object
A script in a supported Java scripting language that will be executed exactly once for a division datasource. A common usecase is to create the data model used by an application or plugin.
A script in a supported Java scripting language that will be executed at application startup. A common usecase is to initialize application-scoped data structures or to validate the installation.
A logical installation of [fleXive]. It defines the JDBC data source to be used, as well as the domains that map to this installation. A [fleXive] server can handle multiple divisions that are isolated from each other.
The [fleXive] superuser role. No permission checks are performed at all if a user has this role.
[Packaging EJB3 applications] Packaging EJB3 applications.
[Getting started with [fleXive] and Maven 2] Getting started with [fleXive] and Maven 2.
[The Global Configuration Plugin] The Global Configuration Plugin (or: Division not defined).