DeveloperGuide

ProjectForge® 2013

Version: 5.4
Date: 2014-05-15
Project: ProjectForge® 2013
URL: www.projectforge.org
Author: Kai Reinhard
mailtok.reinhard@me.com

downloadPrint-friendly version: DeveloperGuide.pdf

Contents

1Introduction
2Setting up the environment
2.1 Quickstart
2.2 Memory and perm gen space
2.3 Preparation of Eclipse
2.3.1 Installation of Maven plugin (optional)
2.3.2 Installation of WTP plugin (optional)
2.3.3 Setting up the maven path
2.3.4 Installation of the Groovy plugin
2.3.5 Creation of Eclipse project settings
2.4 Useful eclipse commands
2.5 Coding style and templates
2.6 Develop mode
2.7 More convinient use of PostgreSQL
3Running ProjectForge with Eclipse
3.1 Jetty
3.2 WTP
4Configuration
4.1 WTP plugin (optional)
4.1.1 server.xml (apache tomcat 6.0)
4.1.2 m2eclipse WTP-Plugin
4.1.3 Install hsql data-base
4.1.4 Keystore
5Concepts
5.1 Hibernate history
5.1.1 ShortDisplayNameCapable
5.2 Wicket vs. Stripes
5.3 Wicket pages (context.xml)
5.3.1 Strip Wicket tags in development mode
5.3.2 List, edit and standard-form pages
5.3.3 i18n
5.3.4 Cooking book
5.3.5 SearchPage
5.4 Lucene analyzer PFTokenizer.jflex
5.5 Core DO's and Dao's
5.5.1 Adding a new Dao
5.6 Hibernate search
5.6.1 Dependent DO's
5.6.2 Adding search fields of child DO's
5.7 Web optimizations
5.7.1 Caching of images, css, java scripts and flash files
5.7.2 Image dimensions
5.8 Excel export
5.9 Localization / i18n
5.10 Custom skins for Spaces (not yet available)
5.10.1 Customizing your skin
5.10.2 Building your skin
5.10.3 Installing your skin
5.10.4 Applying your skin
6Plugins
6.1 My first plugin in one hour!
7Rest-API
7.1 Authentication
7.2 Use cases for Rest
7.2.1 AddressDaoRest
7.2.2 Team-calendar
7.2.2.1 Get reminders of events
7.2.2.2 Calendars
7.2.3 Time sheet booking

List of figures

1Overview of all modules of ProjectForge.
2Eclipse XML settings.

1 Introduction

This document contains some useful information for developers of ProjectForge. Issues on how to set up the database etc. you will find in the AdministrationGuide.
Figure 1: Overview of all modules of ProjectForge®. The source code of most modules is hosted on GitHub .

2 Setting up the environment

Hint

You need Maven 3 for building ProjectForge.

2.1 Quickstart

  1. Checkout: svn co https://pforge.svn.sourceforge.net/svnroot/pforge ProjectForge
  2. Set the JVM memory in MAVEN_OPTS or JAVA_OPTS: -Xmx1024m -Xms512m -XX:PermSize=96m -XX:MaxPermSize=192m
  3. Build ProjectForge: mvn -DskipTests=true install
  4. Run ProjectForge: mvn exec:java -Dexec.mainClass="org.projectforge.web.MyStart" -Dexec.classpathScope=test
    Please add run configuration vm parameters (right mouse click) before starting: -Xmx1024m -Xms512m -XX:PermSize=96m -XX:MaxPermSize=192m
  5. Open your browser: http://localhost:8080/ProjectForge

2.2 Memory and perm gen space

Please set -Xmx1024m -Xms512m -XX:MaxPermSize=192m in MAVEN_OPTS, JAVA_OPTS and/or eclipse.ini for avoiding out of memory exceptions.

2.3 Preparation of Eclipse

2.3.1 Installation of Maven plugin (optional)

Add http://m2eclipse.codehaus.org/ as new RemoteSite and install the maven plugins. You can also use maven from your command line, don't forget to refresh ProjectForge in Eclipse after doing mvn eclipse:eclipse.

2.3.2 Installation of WTP plugin (optional)

Add http://download.eclipse.org/webtools/updates/ as new RemoteSite and install the WTP plugins.

2.3.3 Setting up the maven path

Eclipse -> Preferences -> Java -> Build Path -> Classpath:
Adding variable: M2_REPO=/Users/kai/.m2/repository

2.3.4 Installation of the Groovy plugin

Groovy-Plugin Howto

2.3.5 Creation of Eclipse project settings

2.4 Useful eclipse commands

2.5 Coding style and templates

Use the coding style for this project: misc/eclipse/codingstyle.xml (Window->Preferences->Java->Code Style->Formatter import).
Use the coding templates for this project (last update: 2010-01-31): misc/eclipse/codetemplates.xml (Code Style->Code Templates import).
For XML files use the following settings:
Figure 2: Eclipse XML settings.
Useful template (Eclipse -> Preferences -> Java -> Templates:
Name: log4j
Description: Log4j Logger declaration
Pattern: private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(${enclosing_type}.class);

2.6 Develop mode

  1. Please edit META-INF/context.xml and set the property development to true.
    The development parameter can be get over BaseActionBean.isDevelopmentSystem() . It's actually used for system administration menu for dumping data base to xml.
  2. Please edit log4j.properties and set the debug level for all or the special categories you want to debug.

2.7 More convinient use of PostgreSQL

For accessing the database it's useful to create a database user which is same to the unix user:
1
2
3
Archon:~ admin$ createuser -U postgres kai
psql -U postgres
grant projectforge to kai
Afterwards you can use dropdb, createdb and psql directly from the shell (if not, check the AdministrationGuide for configuration of pg_hba.conf .

3 Running ProjectForge with Eclipse

Your can run ProjectForge with the built-in Jetty web server or with WTP (e. g. with Tomcat).

3.1 Jetty

This is the easiest and fastest way. Start org.projectforge.web.MyStart from the test-classes directory. If you want to modify your settings (database, passwords etc.) make a copy of e. g. StartPostgreSQL.java and modify the settings however you want.

Hint

Please add run configuration vm parameters (right mouse click) before starting: -Xmx1024m -Xms512m -XX:PermSize=96m -XX:MaxPermSize=192m

3.2 WTP

Please uncomment the wtp-version in pom.xml and do all the WTP related configuration steps of this document: 2.3.2 , 4.1 .

4 Configuration

4.1 WTP plugin (optional)

4.1.1 server.xml (apache tomcat 6.0)

 File: server.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
  <Listener className="org.apache.catalina.core.JasperListener"/>
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
  <GlobalNamingResources>
    <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
    <Connector SSLEnabled="true" acceptCount="100" clientAuth="false" disableUploadTimeout="true" enableLookups="true" keystoreFile="${user.home}/.keystore" keystorePass="changeit" maxSpareThreads="75" maxThreads="200" minSpareThreads="5" port="8443" scheme="https" secure="true" sslProtocol="TLS"/>
    <Engine debug="0" defaultHost="localhost" name="Standalone">
      <!--Realm className="org.apache.catalina.realm.UserDatabaseRealm"
        resourceName="UserDatabase" /-->

      <Host appBase="webapps" debug="0" name="localhost" unpackWARs="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="common" prefix="localhost_access_log." suffix=".txt"/>
      <Context docBase="ProjectForge" path="/ProjectForge" reloadable="true" source="org.eclipse.jst.jee.server:ProjectForge"/></Host>
    </Engine>
  </Service>
</Server>

4.1.2 m2eclipse WTP-Plugin

Since Eclipse 3.6 you probably need this plugin installed for starting ProjectForge as WTP module.

4.1.3 Install hsql data-base

Copy hsqldb-<version>.jar into your Apache Tomcat lib dir.

4.1.4 Keystore

ProjectForge is configured with SSL connector as default. Please create your keystore-file:
1
2
keytool -genkey -keystore tomcat-localhost.keystore -alias tomcat -keyalg RSA -keysize 1024 -keypass changeit -storepass
            changeit

5 Concepts

5.1 Hibernate history

The Micromata's hibernate history is used for historizing data base objects.

5.1.1 ShortDisplayNameCapable

Mark your data object as ShortDisplayNameCapable for manipulating the history output of a data object (e. g. TaskDO implements this interface). Implement the interface method in your data object:
1
2
3
4
5
6
7
8
9
10
public class TaskDO extends DefaultBaseDO implements ShortDisplayNameCapable
{
  ...
  @Transient
  public String getShortDisplayName()
  {
    return this.getName() + " (#" + this.getId() + ")";
  }
  ...
}

5.2 Wicket vs. Stripes

All Stripes pages are replaced by Wicket pages.

5.3 Wicket pages (context.xml)

5.3.1 Strip Wicket tags in development mode

During the development the output of Wicket tags are most times a little bit annoying. At default, the Wicket tags are stripped also in development mode. If you need the output of the Wicket tags set stripWicketTags to false in context.xml or as parameter in your MyStart. In productive (aka deployment) mode and in mobile pages Wicket tags are always stripped.

5.3.2 List, edit and standard-form pages

ProjectForge uses RepeatingViews for having a floating layout of form elements. Therefore the most standard pages needs no own html page, they use the html code of their super classes.

5.3.3 i18n

5.3.4 Cooking book

5.3.5 SearchPage

The requirements of all objects which should be part of the SearchPage are:

5.4 Lucene analyzer PFTokenizer.jflex

In ProjectForge a customized Analyzer derived from StandardAnalyzer is used. After modifiing the PFTokenizerImpl.jflex file you need to rebuild the PFTokenizerImpl.java:
java -classpath lib/jflex-1.5.0-SNAPSHOT.jar jflex.Main --nobak src/main/java/org/projectforge/lucene/PFTokenizerImpl.jflex
The Snapshot JFlex version 1.5 is needed (by lucene), used from sourceforge.net/projects/jflex/ . Please checkout and compile the svn code.

5.5 Core DO's and Dao's

Please refer the plugins chapter for handling plugins. This chapter does only concern core DO's and Dao's.

5.5.1 Adding a new Dao

  1. Add the Dao class to:
    org.projectforge.registry.DaoRegistry.java
  2. Add the Dao to the spring configuration: applicationContext-business.xml.
  3. Add the DO to hibernate configuration in:
    org.projectforge.database.HibernateCoreEntities.java

5.6 Hibernate search

5.6.1 Dependent DO's

Hibernate does not update the index of dependent objects automatically. In ProjectForge exists an hot fix, which should be implemented in each Dao managing DO's which depends on other DO's.

Example

This code is an example for DO's containing one dependent DO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ProjektDao()
  {
    super(ProjektDO.class);
    baseDaoReindexRegistry.registerDependent(KundeDO.classthis);
  }


  @Override
  public void reindexDependents(BaseDO< ? > obj)
  {
    if (obj instanceof KundeDO) {
      @SuppressWarnings("unchecked")
      List<ProjektDO> list = getHibernateTemplate().find("from ProjektDO p where p.kunde.id=?", ((KundeDO) obj).getId());
      reindex(list, null);
    } else {
      super.reindexDependents(obj);
    }
  }

Example

This code is an example for DO's containing more than one dependent DO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public EmployeeDao()
  {
    super(EmployeeDO.class);
    baseDaoReindexRegistry.registerDependent(PFUserDO.classthis);
    baseDaoReindexRegistry.registerDependent(Kost1DO.classthis);
  }

  @Override
  public void reindexDependents(BaseDO< ? > obj)
  {
    Set<Serializable> alreadyReindexed = new HashSet<Serializable>(); // For avoiding multiple re-indexing.
    if (obj instanceof PFUserDO) {
      @SuppressWarnings("unchecked")
      List<EmployeeDO> list = getHibernateTemplate().find("from EmployeeDO e where e.user.id=?", ((PFUserDO)obj).getId());
      reindex(list, alreadyReindexed);
    } else if (obj instanceof Kost1DO) {
      @SuppressWarnings("unchecked")
      List<EmployeeDO> list = getHibernateTemplate().find("from EmployeeDO e where e.kost.id=?", ((Kost1DO)obj).getId());
      reindex(list, alreadyReindexed);
    } else {
      super.reindexDependents(obj);
    }
  }

5.6.2 Adding search fields of child DO's

5.7 Web optimizations

5.7.1 Caching of images, css, java scripts and flash files

Https normally does not allow to cache any content in the client's browser. It's annoying, that for every request all java script, css, image and flash files will be loaded from the server. ProjectForge uses the ResponseFilter of Micromata WebUtils. This filter modifies the response header of all in web.xml registered files for caching that resource files by the client's browser. This speed-ups ProjectForge!

5.7.2 Image dimensions

Setting all image dimensions in the html markup results in faster rendering processes of the client's browser. In ProjectForge is a test case defined which runs automatically every time during building a new release:
 File: GetImageDimensionsTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
  private static final String PATH = "src/main/webapp/images";

  private static final String DIMENSION_FILE = "src/main/resources/" + WebConstants.FILE_IMAGE_DIMENSIONS;

  private static final String[] IMAGE_SUFFIXES = new String[] { "png""gif""jpg"};

  @Test
  public void doit() throws IOException
  {
    log.info("Create dimension file of all webapp images.");
    @SuppressWarnings("unchecked")
    final Collection<File> files = FileUtils.listFiles(new File(PATH), IMAGE_SUFFIXEStrue);
    final File absolutePathFile = new File(PATH);
    final String absolutePath = absolutePathFile.getAbsolutePath();
    final List<ImageDimension> dimensions = new ArrayList<ImageDimension>();
    for (final File file : files) {
      final Image image = Toolkit.getDefaultToolkit().getImage(file.getAbsolutePath());
      final ImageIcon icon = new ImageIcon(image);
      final String filename = file.getAbsolutePath().substring(absolutePath.length() + 1);
      final ImageDimension dimension = new ImageDimension(filename, icon.getIconWidth(), icon.getIconHeight());
      dimensions.add(dimension);
    }
    final FileWriter writer = new FileWriter(DIMENSION_FILE);
    final XStream xstream = new XStream();
    xstream.alias("images"List.class);
    xstream.alias("image"ImageDimension.class);
    String xml = xstream.toXML(dimensions);
    writer.append(xml);
    IOUtils.closeQuietly(writer);
    log.info("Creation of dimension file done: " + DIMENSION_FILE);
  }
...
The file uses the Java AWT toolkit for calculating the geometry of all images. The resulting image dimension file is:
 File: src/main/resources/imageDimensions.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<images>
  <image>
    <path>accept.png</path>
    <width>16</width>
    <height>16</height>
  </image>
  <image>
    <path>add.png</path>
    <width>16</width>
    <height>16</height>
  </image>
  ...
</images>
Inside ProjectForge for all images rendered by Wicket the width and height will be set in the markup automatically.

5.8 Excel export

Excel downloads are quite simple:
 File: Kost1ListPage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private enum Col
{
  STATUSKOSTDESCRIPTION;
}

void exportExcel()
{
  ...
  final ExportWorkbook xls = new ExportWorkbook();
  final ContentProvider contentProvider = new XlsContentProvider(xls);
  xls.setContentProvider(contentProvider);
  final ExportSheet sheet = xls.addSheet(sheetName);
  final ExportColumn[] cols = new ExportColumn[] { //
  new I18nExportColumn(Col.KOST"fibu.kost1", 10), // Id, i18n key, length
      new I18nExportColumn(Col.DESCRIPTION"description", 30),
      new I18nExportColumn(Col.STATUS"status", 10)};
  sheet.setColumns(cols);
  // Insert here cell formats if needed.
  final PropertyMapping mapping = new PropertyMapping();
  for (final Kost1DO kost : kost1List) {
    mapping.add(Col.KOST, kost.getFormattedNumber());
    mapping.add(Col.STATUS, kost.getKostentraegerStatus());
    mapping.add(Col.DESCRIPTION, kost.getDescription());
    sheet.addRow(mapping.getMapping(), 0);
  }
  DownloadUtils.setDownloadTarget(xls.getAsByteArray(), filename);
}
If you need your own cell formats, please try something like this:
1
2
3
4
5
6
final ContentProvider sheetProvider = sheet.getContentProvider();
sheetProvider.putFormat(Col.START_TIME"yyyy-MM-dd HH:mm");
sheetProvider.putFormat(Col.STOP_TIME"HH:mm");
sheetProvider.putFormat(Col.DURATION"[h]:mm");
sheetProvider.putFormat(Col.ID"0");
sheetProvider.putFormat(Col.BETRAG"#,##0.00;[Red]-#,##0.00");

5.9 Localization / i18n

Default locale is English. Please refer class org.projectforge.web.I18nCore for administration of existing and new localizations. You can add additional languages and translations by adding the translation files and adding the new locale in the I18nCore class.

5.10 Custom skins for Spaces (not yet available)

Normally all spaces comes with the same skin: ProjectForge If you like to customize the look of a specific Space, you need to build a new GWiki skin. To get a gernerally idea of GWiki skins, have a look to it's documentation at http://labs.micromata.de/gwiki/gwikidocs/howtos/en/CreateStyleandSkins For ProjectForge needs, an example skin already exists (gwiki-style-ProjectForge-example), so you can adopt it's project structure and code to build your own skin. You can download the skin from the ProjectForge svn repository.

5.10.1 Customizing your skin

All relevant customizations can be made in the files standardhead.gspt and standardfoot.gspt

Hint

You should include the original ProjectForge-include files, e.g.: <@include file="inc/ProjectForge/headMenu.gspt" @> Actually there is no reason to try another approach.

5.10.2 Building your skin

After you have customized the files gwikiplugin.xml and pom.xml you can build the skin with the command mvn install

5.10.3 Installing your skin

Installing the skin is very easy:
  1. Navigate your browser to ProjectForge -> Spaces
  2. In the Admin Menu choose Installed Plugin Index . Here you can see and manage all installed Plugins.
  3. Upload your skin / plugin (choose the zip file).
  4. In the Admin Menu choose Wiki Control
  5. Reload the whole GWiki Web
  6. Activate the skin via Plugin Admin , if nesaccery

5.10.4 Applying your skin

GWiki pages inherits their skin property by it's parents. In this case the Spaces skin.
  1. Browse to the space you wish to edit
  2. In the Page menu choose Edit
  3. Choose the Settings tab and click on the Content area
  4. Enter your skin-name as specified Skin (e.g.: ProjectForge-example)

6 Plugins

Extend ProjectForge with your own plugins or third party plugins. This chapter describes how easy it is to write own plugins. Enable the following features inside your plugins with a few lines of code:

6.1 My first plugin in one hour!

Every step should be very easy to understand with an high re-use opportunity inside your own plugins. Gather your experience of the technologies such as Spring, Wicket and Hibernate by developing your plugins step by step.
  1. Download examples.
    Download resources/plugins.zip for getting example sources.
  2. Create your data base object.
    See MemoDO.java for seeing how easy it is to define persistent data models.
  3. Create your data access object.
    Refer MemoDao.java as an example on writing own data access objects. Data access objects are responsible for reading and writing objects from and into the data-base. Your Dao is available by adding a pluginContext.xml in your resource path (see example).
  4. Define the access rights.
    Refer MemoRight.java for defining which user should have access to your data objects.
  5. Define your data-base setup and update scripts.
    See MemoPluginUpdates.java for seeing how easy it is to define your data-base setup and update scripts for any further release of your plugin.
  6. Write list pages with filters.
    See MemoListPage.java and MemoListForm.java for implementing list pages with filters and full text search engine support.
  7. Write edit pages.
    See MemoFormRenderer.java, MemoEditForm.java and MemoEditPage.java for implementing edit formular pages with insert, update, delete functionality.
  8. Putting the stuff together.
    Refer MemoPlugin.java for putting all the stuff together. This class is used to register all your components (data base object, data access object, menu entries and web pages) as well as the i18n resource bundle (e. g. MemoI18nResources*.properties ).
  9. Register your plugin to ProjectForge.
    Edit your config.xml by adding your plugins (coma separated):
     File: config.xml
    1
    2
    3
    4
    5
    6
    7
    8
    <config>
      ...
      <pluginMainClasses>
        org.projectforge.plugins.todo.ToDoPlugin,
        org.projectforge.plugins.memo.MemoPlugin
      </pluginMainClasses>
      ...
    </config>
  10. Ready, run, enjoy it.
    Add your plugin as jar to your WEB-INF/lib directory and restart ProjectForge.
An example plugin with more features is the ToDoPlugin (e-mail templating etc.).

7 Rest-API

Please refer the ProjectForge® repository projectforge-jax-rs on GitHub for getting example code for using ProjectForge® within your clients. Please refer the classes AuthenticationRest for description and the client demo applications RestClientMain, AddressDaoClientMain etc.

7.1 Authentication

It's recommended to avoid storing the user's username and password on the client (e. g. on the mobile phone) due to security reasons. Please store the user's id and authentication-token instead:
  1. On first start of your client (user-token isn't known yet), please call AuthenticationRest.getToken() for getting the user data (id, authentication-token and optional information) by sending the username and password the user typed in.
  2. You may now store the user's id and authentication-token for the user's convenience on your client (e. g. mobile app).
  3. Every time the user starts the client / app you should call AuthenticationRest.initialContact(String) for checking the server version. May-be the server version is too old or your client version is too old. This call is optional but good practice.
  4. Every further rest call is done by authentication via user-id and authentication-token. The user-id is required for logging purposes e. g. for failed logins or brute-force attacks.

7.2 Use cases for Rest

Some clients are already implemented, further Rest calls will be implemented when needed.

7.2.1 AddressDaoRest

Rest-Call for AddressDao#getFavoriteVCards().
If modifiedSince is given then only those addresses will be returned:
  1. The address was changed after the given modifiedSince date, or
  2. the address was added to the user's personal address book after the given modifiedSince date, or
  3. the address was removed from the user's personal address book after the given modifiedSince date.
The use case is to get all v-cards initial and then get only new, modified or deleted entries every time interval (incremental update).

7.2.2 Team-calendar

7.2.2.1 Get reminders of events

Get future reminders for events by calling TeamEventDaoRest.getReminderList(calendarIds, daysInFuture). You will receive a list with all events having reminders (alarm triggers e. g. 15 minutes before the event).

7.2.2.2 Calendars

in progress: get calendar list, get events, add and modify events with your mobile device (Android, iOS, Blackberry etc.) via native app.

7.2.3 Time sheet booking

in progress: get time sheets, add and modify time sheets with your mobile device (Android, iOS, Blackberry etc.) via native app.