Jspresso Wiki

Welcome to the Jspresso Wiki !

* FAQ

Frequently Asked Questions

 

Can I change the GUI (Flex, Qooxdoo, Wings) in runtime?

Short answer: no.
Long one: use two different browser instances (e.g. one IE and one Firefox) or erase the cookies of your current browser.

 

Do I need to package after any GUI change?

Short answer: no.
Long one: as long as you keep your model unchanged, there is no need to launch a maven package task.
Very long one: even some change in the model doesn't need it. It's the case of any change that doesn't imply changes of the entity or component java classes or the linked Hibernate files (see core/target/generated-sources/entitygenerator and core/target/generated-resourcesxdoclet). E.g.: you can change the value of an entry in serviceDelegateClassNames or serviceDelegateBeanNames, but not the key. Same thing with the values in a renderedProperties.

 

How can I work with the snapshot version?

Create a new project with the maven command:

       mvn archetype:generate -DarchetypeCatalog=http://repository.jspresso.org/maven2-snapshots/

 

* Good stuff from the forum

You'll find on this page links towards forum threads about issues which are not thoroughly treated either in the documentation or in the wiki yet.

It's a live page:

- as soon as an issue is integrated in the documentation, the wiki or the FAQ, it should be removed from here;

- new theads can be added if they give either a good explanation or a clear how to;

- some links just go to the main entry of a thread: they could be improved by going directly to the best part of the thread;

- if you think that some good stuff is missing...

And now, the winners are:
    
Miscellaneous
    http://www.jspresso.org/forum/workspace-modules-and-view
    http://www.jspresso.org/forum/properties-top-tabs-feature-request
    http://www.jspresso.org/forum/request-no-label
    http://www.jspresso.org/forum/more-explanation-needed-initializationmapping
    http://www.jspresso.org/forum/selected-items#comment-50

Lifecycle    
    http://www.jspresso.org/forum/selection-and-wizardaction
    http://www.jspresso.org/forum/change-property-collections-item#comment-78

DB legacy
    http://www.jspresso.org/forum/using-jspresso-existing-db-schema#comment-790
    http://www.jspresso.org/forum/using-jspresso-existing-db-schema#comment-780
    http://www.jspresso.org/forum/entity-annotations#comment-498
    http://www.jspresso.org/forum/sql-name-table-columns
    http://www.jspresso.org/forum/existing-domain-model
    http://www.jspresso.org/forum/entity-annotations
   
Mail
    http://www.jspresso.org/forum/action-sending-mail
         
Data exportation
   http://www.jspresso.org/forum/help-how-export-data

Reporting
    http://www.jspresso.org/forum/how-integrate-itext-jspresso

Extension
    http://www.jspresso.org/forum/widget-set
    http://www.jspresso.org/forum/how-integrate-itext-jspresso

Login
    http://www.jspresso.org/forum/jaas-jdbc-login
    http://www.jspresso.org/forum/help-multiple-users-development
    http://www.jspresso.org/forum/solved-logincontext-problem
    http://www.jspresso.org/forum/help-ldap-and-custom-properties
   
Wizard  
    http://www.jspresso.org/forum/help-how-initialize-value-wizard-model
    http://www.jspresso.org/forum/front-action-get-value
    http://www.jspresso.org/forum/question-staticwizardstepdescriptor
    http://www.jspresso.org/forum/help-choice-wizard

Card view
     http://www.jspresso.org/forum/dynamic-selection-out-multiple-views-featu...
     http://www.jspresso.org/forum/dynamic-panel

Gates
    http://www.jspresso.org/forum/dynamic-selection-out-multiple-views-featu...
    http://www.jspresso.org/forum/request-writabilitygate-based-enumerated-type
    http://www.jspresso.org/forum/bug-readabilitygate-does-not-seem-work
        with the question of its usability...
    http://www.jspresso.org/forum/problem-gates-advanced
    http://www.jspresso.org/forum/help-actions-entity-selections-and-entity-...
    http://www.jspresso.org/forum/help-property-descriptor-and-readibility-g...
    http://www.jspresso.org/forum/bug-strange-model-collection-gate
    http://www.jspresso.org/forum/help-writabilitygate-table
    http://www.jspresso.org/forum/bug-readabilitygate-does-not-seem-work
   
Show/Edit detail view
    http://www.jspresso.org/forum/avidavid-edit-button-problem
    http://www.jspresso.org/forum/maxime-edit-button-problem
    http://www.jspresso.org/forum/edit-button-master-detail-action-map-and-d...
    http://www.jspresso.org/forum/add-button-open-entity
    http://www.jspresso.org/forum/adding-masterdetail-actionmap
    http://www.jspresso.org/forum/double-click-collection-view-based-relatio...

 

Add a new model type: Joda time

The idea is to add some new model types in a jspresso project with a minimal effort. Also:

- the types are simple ones, i.e. with only one value to deal with,

- and there would be no need to work at the UI level.

Luckily I only need to use the joda types LocalDate and LocalDateTime in a jspresso project. This use case shows how to implement such simple types. With more complex type like Joda Interval, it'd have been an other story!

To do such a thing we need to deal with five issues:

  1. To create new model descripors, one for each new type, here ILocalDatePropertyDescriptor and ILocalDateTimePropertyDescriptor.
  2. To create specific connectors:
    •     for Swing and Wings, with dedicated ones: JJodaTimeFieldConnector and XJodaTimeConnector,
    •     for others, with a common factory: JodaRemoteConnectorFactory, which generates JodaRemoteValueConnector instances.
  3. To manage these connectors in a new view factory for each UI. These factories will use a new format adapter for each model type.
    And they must be specified in the frontend configuration.
  4. To make  the model configuration "joda aware" by changing the template HibernateXdocletEntity.ftl.
  5. To configure hibernate to manage the joda types.

Now the new types can be used in place of standard date, with the same syntax and the same attributes. To do it, just specify the new interfaces in the model configuration file, e.g.:

  <bean 
    id="dateType"
    class="org.popsuite.framework.model.descriptor.basic.BasicLocalDatePropertyDescriptor"
    abstract="true" />
[...]
  <bean parent="dateType">
     <property name="name"
                    value="paymentDay" />
  </bean>

To be used without limitation!

The dedicated model descriptors

A model descriptor is agnostic in regard of the UI technology. Each new model type needs only one descriptor. At the moment, only Joda Date and DateTime are implemented.

For each type we need an interface and at least one implementation. There is no need to deal with UI (until we want to enhance the current presentation of a date...). So the new interfaces and implementations are simply derived from the current date ones.

First the two interfaces:

package org.popsuite.framework.model.descriptor;
import org.jspresso.framework.model.descriptor.IDatePropertyDescriptor;

public interface ILocalDateTimePropertyDescriptor extends IDatePropertyDescriptor {}

and

package org.popsuite.framework.model.descriptor;

public interface ILocalDatePropertyDescriptor extends ILocalDateTimePropertyDescriptor {}

Then an implementation:

package org.popsuite.framework.model.descriptor.basic;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.model.descriptor.EDateType;
import org.jspresso.framework.model.descriptor.basic.BasicDatePropertyDescriptor;
import org.popsuite.framework.model.descriptor.ILocalDateTimePropertyDescriptor;

public class BasicLocalDateTimePropertyDescriptor extends BasicDatePropertyDescriptor
implements ILocalDateTimePropertyDescriptor {

    public BasicLocalDateTimePropertyDescriptor() {
        super();
    }

    @Override  
    public Class<?> getModelType() {
        return LocalDateTime.class;
    }

    @Override
    public EDateType getType() {
        if (super.getType() != null) {
            return super.getType();
        }
        return null;
    }

}

and

package org.popsuite.framework.model.descriptor.basic;
import org.joda.time.LocalDate;
import org.jspresso.framework.model.descriptor.EDateType;
import org.popsuite.framework.model.descriptor.ILocalDatePropertyDescriptor;

public class BasicLocalDatePropertyDescriptor extends BasicLocalDateTimePropertyDescriptor
implements ILocalDatePropertyDescriptor {

    public BasicLocalDatePropertyDescriptor() {}

    @Override  
    public Class<?> getModelType() {
        return LocalDate.class;
    }

    @Override
    public EDateType getType() {
        return EDateType.DATE;
    }

}

The connectors

Only the Swing and Wings versions of jspresso need the creation of new dedicated connectors. For the others remote UI, Flex and Qooxdoo, a connector factory is used.

Swing

package org.popsuite.framework.binding.swing;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.ConnectorBindingException;
import org.jspresso.framework.binding.swing.JDateFieldConnector;
import org.jspresso.framework.gui.swing.components.JDateField;

/**
 * JJodaTimeField connector.
 *
 * Manages LocalDate and LocalDateTime values.
 *
 */
public class JJodaTimeFieldConnector extends JDateFieldConnector {

    /**
     * Constructs a new <code>JDateField</code> instance.
     *
     * @param id
     *            the connector identifier.
     * @param dateField
     *            the connected JDateField.
     */
    public JJodaTimeFieldConnector(String id, JDateField dateField) {
        super(id, dateField);
    }

    /**
     * Will be called by <code>setConnecteeValue</code>.
     *
     * {@inheritDoc}
     */
    @Override
    protected void protectedSetConnecteeValue(Object aValue) {
        Object result = aValue;
        if (aValue instanceof LocalDateTime) {
            result = isUnknown(aValue) ? null : ((LocalDateTime) aValue).toDateTime().toDate();       
        } else if (aValue instanceof LocalDate) {
            result = isUnknown(aValue) ? null : ((LocalDate) aValue).toDateTimeAtStartOfDay().toDate();
        }
        super.protectedSetConnecteeValue(result);
    }

    /**
     * Gets the value out of the connector text after parsing the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    protected Object getConnecteeValue() {
        // don't call getValue() due to bad focus event delivery order of
        // JFormattedTextField.
        // return getConnectedJComponent().getValue();
        try {
            if (getConnectedJComponent().getFormattedTextField().getText() = = null
                    || getConnectedJComponent().getFormattedTextField().getText().length() = = 0) {
                return null;
            }

            Object result = getConnectedJComponent().getFormattedTextField().getFormatter()
                .stringToValue(getConnectedJComponent().getFormattedTextField().getText());
            Class<?> expectedType = null;
            if (getModelConnector().getModelDescriptor() != null) {
                expectedType = getModelConnector().getModelDescriptor().getModelType();
            } else {
                expectedType = getModelDescriptor().getModelType();
            }
            return (LocalDateTime.class.equals(expectedType))
            ? LocalDateTime.fromDateFields((Date) result)
                    : LocalDate.fromDateFields((Date) result);
        } catch (ParseException ex) {
            throw new ConnectorBindingException(ex);
        }
    }

}

Wings

package org.popsuite.framework.binding.wings;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.wings.XCalendarConnector;
import org.wingx.XCalendar;

public class XJodaTimeConnector extends XCalendarConnector{

    public XJodaTimeConnector(String id, XCalendar dateField) {
        super(id, dateField);
    }

    /**
     * Sets the value to the connector text after formatting the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public void setConnecteeValue(Object aValue) {
        Date result = null;
        if (aValue instanceof LocalDate) {
            result = isUnknown(aValue) ? null : ((LocalDate) aValue).toDateTimeAtStartOfDay().toDate();
        }
        else if(aValue instanceof LocalDateTime) {
            result = isUnknown(aValue) ? null : ((LocalDateTime) aValue).toDateTime().toDate();
        }
        else {
            result = (Date) aValue;
        }
        getConnectedSComponent().setDate(result);
    }

    /**
     * Gets the value out of the connector text after parsing the string
     * representation.
     * <p>
     * {@inheritDoc}
     */
    @Override
    protected Object getConnecteeValue() {
        Object result =  getConnectedSComponent().getDate();
        if (null = = result) return null;

        Class<?> expectedType = null;
        if (getModelConnector().getModelDescriptor() != null) {
            expectedType = getModelConnector().getModelDescriptor().getModelType();
        } else {
            expectedType = getModelDescriptor().getModelType();
        }
        result = (LocalDateTime.class.equals(expectedType)) 
        ? LocalDateTime.fromDateFields((Date) result)
                : LocalDate.fromDateFields((Date) result);
        return result;
    }

}

Flex and Qooxdoo

A connector factory is used with the two other remote UI, Flex and Qooxdoo. This factory generates JodaValueConnectors.

The factory:

package org.popsuite.framework.binding.remote;

import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.binding.remote.RemoteConnectorFactory;
import org.jspresso.framework.binding.remote.RemoteValueConnector;

public class JodaRemoteConnectorFactory extends RemoteConnectorFactory{

    @Override
    public IValueConnector createValueConnector(String id) {
        RemoteValueConnector connector = new JodaRemoteValueConnector(id, this);
        attachListeners(connector);
        return connector;
    }

}

And the value connector:

package org.popsuite.framework.binding.remote;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.binding.remote.RemoteConnectorFactory;
import org.jspresso.framework.binding.remote.RemoteValueConnector;

public class JodaRemoteValueConnector extends RemoteValueConnector{

    public JodaRemoteValueConnector(String id,
            RemoteConnectorFactory connectorFactory) {
        super(id, connectorFactory);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Object getValueForState() {
        Object result = super.getValueForState();
        if (result instanceof LocalDate) {
            result = isUnknown(result) ? null : ((LocalDate) result).toDateTimeAtStartOfDay().toDate();
        }
        else if(result instanceof LocalDateTime) {
            result = isUnknown(result) ? null : ((LocalDateTime) result).toDateTime().toDate();
        }
        return result;
    }   

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setConnecteeValue(Object connecteeValue) {
        Object result = connecteeValue;
        if (result instanceof Date)
        {
            Class<?> expectedType = getModelConnector().getModelDescriptor().getModelType();
            if (LocalDateTime.class.equals(expectedType)) {
                result = LocalDateTime.fromDateFields((Date) result);
            } else if (LocalDate.class.equals(expectedType)) {
                result = LocalDate.fromDateFields((Date) result);
            }
        }
        super.setConnecteeValue(result);
    }

}

Special case: unknow dates

The framework popsuite defined unknown values for the dates. Such values must be intercepted by the connectors (see above).

Note: a use case of how to implement unknown values with jspresso will be the topic of a future wiki entry. Cool

package org.popsuite.framework.model.specialcase;

import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;

public class SpecialDates {
   
    public static final LocalDate UNKNOWN_DATE  =
        new LocalDate().year().setCopy(new LocalDate().year().getMinimumValueOverall())
                       .dayOfYear().withMaximumValue();
    public static final LocalDate UNKNOWN_BEGIN = UNKNOWN_DATE;
    public static final LocalDate UNKNOWN_END   =
        new LocalDate().year().setCopy(new LocalDate().year().getMaximumValueOverall())
                       .dayOfYear().withMinimumValue();
   
    public static boolean isUnknown(Object date)
    {
        if (null = = date) return true;
        if (date instanceof Date) return false;
       
        if(date instanceof LocalDate) {
            return ! UNKNOWN_BEGIN.isBefore( (LocalDate) date) || ! UNKNOWN_END.isAfter( (LocalDate) date) ;
        }
        else if (date instanceof LocalDateTime) {
            return ! (new LocalDateTime(UNKNOWN_BEGIN)).isBefore( (LocalDateTime) date)
                   || ! (new LocalDateTime(UNKNOWN_END)).isAfter( (LocalDateTime) date) ;
        }
        return true;
    }
}

The View factories

Each UI needs a specific view factory, but Flex and Qooxdoo share the same one. These factories are derived from the standard ones. The former must be used in placed of the latter in the frontend configuration file.

Swing

package org.popsuite.framework.view.swing;
[...]
/**
 * Enhanced view factory:
 * - manages the joda's dates
 *
 */
public class PopsuiteSwingViewFactory extends DefaultSwingViewFactory {

    private static final Date                  TEMPLATE_DATE               = new Date(27166271000L);
         
    @Override
    protected IView<JComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<JComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            view = createLocalDateTimePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        return view;
    }

    protected TableCellRenderer createTableCellRenderer(IPropertyDescriptor propertyDescriptor, Locale locale) {

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            return createDateTableCellRenderer(
                    (ILocalDatePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            return createDateTableCellRenderer(
                    (ILocalDateTimePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        return super.createTableCellRenderer(propertyDescriptor, locale);
    }

    protected IView<JComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        JDateField viewComponent = createJDateField(locale);
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.getFormattedTextField().setFormatterFactory(
                new DefaultFormatterFactory(new DateFormatter(format)));
        JJodaTimeFieldConnector connector = new JJodaTimeFieldConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                Toolkit.getDefaultToolkit().getScreenResolution() / 3);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    protected IView<JComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        JDateField viewComponent = createJDateField(locale);
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.getFormattedTextField().setFormatterFactory(
                new DefaultFormatterFactory(new DateFormatter(format)));
        JJodaTimeFieldConnector connector = new JJodaTimeFieldConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                Toolkit.getDefaultToolkit().getScreenResolution() / 3);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    private TableCellRenderer createDateTableCellRenderer(ILocalDatePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private TableCellRenderer createDateTableCellRenderer(ILocalDateTimePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

    private LocalDate getDateTemplateValue(ILocalDatePropertyDescriptor marker) {
        return LocalDate.fromDateFields(TEMPLATE_DATE);
    }

    private LocalDateTime getDateTemplateValue(ILocalDateTimePropertyDescriptor marker) {
        return  LocalDateTime.fromDateFields(TEMPLATE_DATE);
    }
}

Wings

package org.popsuite.framework.view.wings;

[...]
/**
 * Manages the joda's dates
 */
public class PopsuiteWingsViewFactory
extends DefaultWingsViewFactory{

    private static final Date                  TEMPLATE_DATE               = new Date(27166271000L);
   
    @Override
    protected IView<SComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<SComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) { 
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        return view;
    }

    @Override
    protected STableCellRenderer createTableCellRenderer(
            IPropertyDescriptor propertyDescriptor,
            Locale locale) {
        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) { // must come before ILocalDateTimePropertyDescriptor
            return createDateTableCellRenderer(
                    (ILocalDatePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            return createDateTableCellRenderer(
                    (ILocalDateTimePropertyDescriptor) propertyDescriptor,
                    locale);
        }
        return super.createTableCellRenderer(propertyDescriptor, locale);
    }

    protected IView<SComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        XCalendar viewComponent = createDateField();
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.setFormatter(new SDateFormatter(format));
        XCalendarConnector connector = new XJodaTimeConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                64);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    protected IView<SComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        XCalendar viewComponent = createDateField();
        DateFormat format = createDateFormat(propertyDescriptor, locale);
        viewComponent.setFormatter(new SDateFormatter(format));
        XCalendarConnector connector = new XJodaTimeConnector(
                propertyDescriptor.getName(),
                viewComponent);
        connector.setExceptionHandler(actionHandler);
        adjustSizes(propertyViewDescriptor,
                viewComponent,
                createFormatter(format, propertyDescriptor),
                getDateTemplateValue(propertyDescriptor),
                64);
        return constructView(viewComponent, propertyViewDescriptor, connector);
    }

    private STableCellRenderer createDateTableCellRenderer(ILocalDatePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private STableCellRenderer createDateTableCellRenderer(ILocalDateTimePropertyDescriptor propertyDescriptor, Locale locale) {
        return new FormattedTableCellRenderer(createDateFormatter(propertyDescriptor, locale));
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }
   
    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

    private LocalDate getDateTemplateValue(ILocalDatePropertyDescriptor marker) {
        return LocalDate.fromDateFields(TEMPLATE_DATE);
    }

    private LocalDateTime getDateTemplateValue(ILocalDateTimePropertyDescriptor marker) {
        return  LocalDateTime.fromDateFields(TEMPLATE_DATE);
    }
   
}

Flex and Qooxdoo

package org.popsuite.framework.view.remote;
[...]
/**
 * Manages the joda's dates
 */
public class PopsuiteRemoteViewFactory
extends DefaultRemoteViewFactory {
   
    /**
     * {@inheritDoc}
     */
    @Override
    protected IView<RComponent> createPropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IView<RComponent> view = null;
        IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) propertyViewDescriptor.getModelDescriptor();

        if (propertyDescriptor instanceof ILocalDatePropertyDescriptor) {  // must come before ILocalDateTimePropertyDescriptor
            view = createLocalDatePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        else if (propertyDescriptor instanceof ILocalDateTimePropertyDescriptor) {
            view = createLocalDateTimePropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        if (view = = null) {
            return super.createPropertyView(
                    propertyViewDescriptor,
                    actionHandler,
                    locale);
        }
        decorateWithDescription(propertyDescriptor, locale, view); // see AbstractViewFactory.createPropertyView
        if ((view != null) && (propertyDescriptor.getName() != null)) { // see DefaultRemoteViewFactory.createPropertyView
            view.getPeer().setLabel(
                    propertyDescriptor.getI18nName(getTranslationProvider(), locale));
        }
        return view;
    }

    protected IView<RComponent> createLocalDatePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        ILocalDatePropertyDescriptor propertyDescriptor = (ILocalDatePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, propertyViewDescriptor, connector);
        return view;
    }

    protected IView<RComponent> createLocalDateTimePropertyView(
            IPropertyViewDescriptor propertyViewDescriptor,
            IActionHandler actionHandler,
            Locale locale) {

        IValueConnector connector;
        ILocalDateTimePropertyDescriptor propertyDescriptor = (ILocalDateTimePropertyDescriptor) propertyViewDescriptor.getModelDescriptor();
        if (isDateServerParse()) {
            connector = getConnectorFactory().createFormattedValueConnector(
                    propertyDescriptor.getName(),
                    createDateFormatter(propertyDescriptor, locale));
        } else {
            connector = getConnectorFactory().createValueConnector(
                    propertyDescriptor.getName());
        }

        connector.setExceptionHandler(actionHandler);
        RDateField viewComponent = createRDateField(connector);
        viewComponent.setType(propertyDescriptor.getType().toString());
        IView<RComponent> view = constructView(viewComponent, propertyViewDescriptor, connector);
        return view;
    }

    private IFormatter createDateFormatter(
            ILocalDatePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createDateFormatter(
            ILocalDateTimePropertyDescriptor propertyDescriptor,
            Locale locale) {
        return createFormatter(createDateFormat(propertyDescriptor, locale), propertyDescriptor);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDatePropertyDescriptor marker) {
        return new LocalDateFormatAdapter(format);
    }

    private IFormatter createFormatter(DateFormat format, ILocalDateTimePropertyDescriptor marker) {
        return new LocalDateTimeFormatAdapter(format);
    }

}

Frontend configuration file

In the project frontend file, in the bean "applicationFrontController", for the attribute "viewFactory", the standard value "viewFactory" must be replaced by a specific one, for example "popsuiteViewFactory".

Then the bean "popsuiteViewFactory" has to be defined in new specific frontend files, one for each of the view factories.

In frontend-swing.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.swing.PopsuiteSwingViewFactory"
    parent="viewFactory"
    scope="prototype">
  </bean>

In frontend-wings.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.wings.PopsuiteWingsViewFactory"
    parent="viewFactory"
    scope="prototype">
  </bean>

In frontend-remote.xml:

  <bean
    id="popsuiteViewFactory"
    class="org.popsuite.framework.view.remote.PopsuiteRemoteViewFactory"
    parent="viewFactory"
    scope="prototype">
    <property name="connectorFactory"  ref="popsuiteConnectorFactory" />
  </bean>

Spring location file

As new frontend configuration files have been created, the project file "beanRefFactoy.xml" must know where to find them. It will be done for each UI context bean, e.g. for Swing (in bold):

  <bean
    id="popsuite9-swing-context"
    class="org.springframework.context.support.ClassPathXmlApplicationContext"
    lazy-init="true">
    <constructor-arg>
      <list>
        [...]
        <value>org/jspresso/framework/application/frontend/commons-frontend.xml</value>
        <value>org/jspresso/framework/application/frontend/swing/commons-swing.xml</value>
        [...]
        <value>org/popsuite/hr/frontend/frontend.xml</value>
        <value>org/popsuite/framework/application/frontend/frontend-swing.xml</value>
        [...]
      </list>
    </constructor-arg>
  </bean>

FormatAdapter

Each new model type needs a new format adapter. But each adaptor is used by all the view factories.

For Joda LocalDateTime:

package org.popsuite.framework.util.format;

import static org.popsuite.framework.model.specialcase.SpecialDates.isUnknown;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.jspresso.framework.util.format.FormatAdapter;

/**
 * An adapter for the <code>Format</code> joda objects.
 *
 * Uses the jdk's Format with joda's objects
 *
 *
 */
public class LocalDateTimeFormatAdapter extends FormatAdapter {

    public LocalDateTimeFormatAdapter(DateFormat format) {
        super(format);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String format(Object value) {
        if (isUnknown(value)) return null;

        Date date;
        if (value instanceof LocalDateTime) {
            date = ((LocalDateTime) value).toDateTime().toDate();       
        } else if (value instanceof LocalDate) {
            date = ((LocalDate) value).toDateTimeAtStartOfDay().toDate();
        } else if (value instanceof Date) {
            date = (Date) value; // should never be used but...
        } else {
            return null;
        }
        String result = getFormat().format(date);
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object parse(String dateSource) throws ParseException {
        if (null = = dateSource || dateSource.isEmpty()) return null;

        Date date = ((DateFormat) getFormat()).parse(dateSource);
        return LocalDateTime.fromDateFields(date);
    }

}

And for Joda LocalDate:

package org.popsuite.framework.util.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;

import org.joda.time.LocalDate;

/**
 * An adapter for the <code>Format</code> joda objects.
 *
 * Uses the jdk's Format with joda's objects
 *
 */
public class LocalDateFormatAdapter extends LocalDateTimeFormatAdapter {

    public LocalDateFormatAdapter(DateFormat format) {
        super(format);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object parse(String dateSource) throws ParseException {
        if (null = = dateSource || dateSource.isEmpty()) return null;
       
        Date date = ((DateFormat) getFormat()).parse(dateSource);
        return LocalDate.fromDateFields(date);
    }

}

The Model template

Not the funiest part of the job, as at the moment there is no way to hook such an extension without changing the template "HibernateXdocletEntity.ftl" itself (°).

So:

- create a copy of the original in your project

- in {project_home}/pom.xml, add:

  <properties>
    <generator.templateResourcePath>{template_package_path}</generator.templateResourcePath>
  </properties>

- add the specific code to the template (see below).

In the template, there is only one place to change. The test on the new date types must be inserted before the test on IDatePropertyDescriptor. The new code is:

    <#if !componentDescriptor.computed && !propertyDescriptor.delegateClassName?exists>
      * @hibernate.property <#-- Order LocalDate > LocalDateTime > Date is mandatory -->
      <#if instanceof(propertyDescriptor, "org.popsuite.framework.model.descriptor.ILocalDatePropertyDescriptor")>
      *           type = "org.joda.time.contrib.hibernate.PersistentLocalDate"
      <#elseif instanceof(propertyDescriptor, "org.popsuite.framework.model.descriptor.ILocalDateTimePropertyDescriptor")>
      *           type = "org.joda.time.contrib.hibernate.PersistentLocalDateTime"
      <#elseif instanceof(propertyDescriptor, "org.jspresso.framework.model.descriptor.IDatePropertyDescriptor")>
      [...]

That's it.

Indeed, when using a snapshot version of jspresso, you need to check from time to time if there is some change in the original file and to patch them in the project copy.

Note (°) : the FreeMarker template can manage insertion of files. With such a mean, it should be possible to create hooks.

Hibernate configuration

It's the simplest part of the work. In your project:

- check the version of joda library used by default by jspresso, i.e. joda time 1.6+

- add the joda-time-hibernate library, at the moment version 1.1

- change the pom files in the project, i.e.:

  * in {project_home}/pom.xml, add:

      <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time-hibernate</artifactId>
        <version>1.1</version>
      </dependency>

  * in {project_home}/core/pom.xml, add (in bold):

      <dependencies>
      [...]
        <dependency>
          <groupId>joda-time</groupId>
          <artifactId>joda-time-hibernate</artifactId>
          <exclusions>
            <exclusion>
              <groupId>joda-time</groupId>
              <artifactId>joda-time</artifactId>
            </exclusion>
            <exclusion>
              <groupId>hibernate</groupId>
              <artifactId>hibernate</artifactId>
            </exclusion>
            <exclusion>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging-api</artifactId>
            </exclusion>
            <exclusion>
              <groupId>commons-collections</groupId>
              <artifactId>commons-collections</artifactId>
            </exclusion>
<exclusion>
<groupId>ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
            <exclusion>
              <groupId>dom4j</groupId>
              <artifactId>dom4j</artifactId>
            </exclusion>
            <exclusion>
              <groupId>antlr</groupId>
              <artifactId>antlr</artifactId>
            </exclusion>
          </exclusions>
        </dependency>
      [...]
      </dependencies>
      [...]
      <profiles>
        <profile>
          <id>ulc</id>
          [...]
          <build>
            <plugins>
            [...]
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                  <execution>
                  [...]
                  <configuration>
                    <outputDirectory>${project.build.directory}/${project.artifactId}/lib</outputDirectory>
                    <includeArtifactIds>ulc-base-trusted,(...],joda-time,joda-time-hibernate,substance</includeArtifactIds>
                    <stripVersion>true</stripVersion>
                  </configuration>
                  [...]

 

 

Authentication using database

1) Defining the Domain

(model.groovy)

Entity ('User') {

      string_32  'login', mandatory:true, unicityScope:'scope_login'

      password 'password'     

      enumeration 'language', enumName:'language', values:['fr', 'en']

      set 'roles', ref:'Role', reverse:'Role-users'

 

Entity ('Role') {

      string_32 'roleId'

      set 'users', ref:'User', composition:false, reverse:'User-roles'

}

2) Update Jaas.config to use the new org.jspresso.framework.security.auth.spi.DatabaseLoginModule (3.5 +). This login module extends the Jboss security DatabaseServerLoginModule, so configuration apply.
(take care to setup java.security.auth.login.config parameter into your tomcat launcher as described in the startup tutorial)

myproject {

   org.jspresso.framework.security.auth.spi.DatabaseLoginModule required

       dsJndiName="java:comp/env/jdbc/MyProjectDS"

       principalsQuery="SELECT PASSWORD, LANGUAGE language FROM USER WHERE LOGIN=?"

       rolesQuery="SELECT ROLE.ROLE_ID, 'Roles' FROM ROLE, USER, USER_ROLES \

                   WHERE USER.LOGIN=? AND USER_ROLES.ROLE_ID=ROLE.ID AND USER_ROLES.USER_ID=USER.ID"

       principalClass=org.jspresso.framework.security.UserPrincipal

       suspendResume=false;  

};

3) For TOMCAT :

Define a jndi name for your database context.xml into tomcat conf directory

<Context>

    <Resource name="jdbc/MyProjectDS" auth="Container" type="javax.sql.DataSource"

           maxActive="100" maxIdle="30" maxWait="10000"

           username="root" password="pwd" driverClassName="com.mysql.jdbc.Driver"

           url="jdbc:mysql://10.61.168.88:3306/myproject"/>
</
Context>

NB : Take care that tomcat lib contains the jdbc lib library (for example mysql-connector-java-x.x.x.jar )

Setup your datasource project : use the jndi name above
(myproject/webapp/src/main/dev/resources/fr/gefco/myproject/config.xml)

<beans

  xmlns="http://www.springframework.org/schema/beans"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://www.springframework.org/schema/beans

                      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"

  default-lazy-init="true">

 

  <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

   <property name="jndiName" value="java:comp/env/jdbc/MyProjectDS"/>

 </bean>


</beans>

4) For SWING:

4.a) Define the datasource directly into /myproject/core/src/main/resources/fr/gefco/weather/config.xml

  <bean

    id="dataSource"

    class="org.apache.commons.dbcp.BasicDataSource">

    <property

      name="driverClassName"

      value="com.mysql.jdbc.Driver" />

    <property

      name="url"

      value="jdbc:mysql://localhost:3306/myproject" />

    <property

      name="username"

      value="root" />

    <property

      name="password"

      value="pwd" />

  </bean>

 

4.b) Update your SwingApplicationStartup class to setup the jndi name for your database :

  @Override

  public void start() {

    DataSource dataSource = (DataSource) getApplicationContext().getBean("dataSource", DataSource.class);

    try {

      InitialContext ic = new InitialContext();

      Context compSubContext = ic.createSubcontext("java:comp");

      Context envSubContext = compSubContext.createSubcontext("env");

      Context jdbcSubContext = envSubContext.createSubcontext("jdbc");

      jdbcSubContext.rebind("MyProjectDS", dataSource);

    } catch (NamingException ex) {

      throw new NestedRuntimeException(ex);

    }

    super.start();

  }

 

4.c) Add a JNDI library to your Jspresso application Launcher.

For my own i'm using Simple-JNDI : you have to add following VM arguments :

-Djava.naming.factory.initial=org.osjava.sj.SimpleContextFactory
-Dorg.osjava.sj.delimiter=/
-Dorg.osjava.sj.jndi.shared=true
-Dorg.osjava.sj.root=conf


5) This step is optional. The Jspresso database login module will store all extra column you put into the principals query (e.g. language in our case) into the session user principal custom properties. However, you might want to keep the user entity instance itself into the application session (also as a custom property). Here is how you would do it using a simple startup action.

a. Add a startup application
(frontend.groovy)

     controller (myproject.name', startup:'startupBackAction')

(backend.groovy)

action ('startupBackAction',    class:'org.mycompany.myproject.backend.StartupBackAction')

b.  Code your startup action

 

public class StartupBackAction extends AbstractHibernateAction {

 

  public final static String USER_ENTITY_KEY = "session.userEntity";

 

  @SuppressWarnings("unchecked")

  @Override

  public boolean execute(IActionHandler actionHandler, final Map<String, Object> context) {

    final UserPrincipal userPrincipal = getBackendController(context).getApplicationSession().getPrincipal();

 

    User user = (User)getTransactionTemplate(context).execute(new TransactionCallback() {

     

      public Object doInTransaction(TransactionStatus txStatus) {

        User user = null;

       

        DetachedCriteria userCriteria = DetachedCriteria.forClass(User.class);

        userCriteria.add(Restrictions.eq("login", userPrincipal.getName()));

           

        List<User> users = getHibernateTemplate(context).findByCriteria(userCriteria);

        if (users!=null && !users.isEmpty()) {

          user = users.get(0);

        }

       

        txStatus.setRollbackOnly();

        return user;

      }

    });

   

    // merge hibernate instance into session 

    user = (User)getController(context).merge(user, EMergeMode.MERGE_KEEP);

   

    // set custom property

    userPrincipal.putCustomProperty(USER_ENTITY_KEY, user);

    return super.execute(actionHandler, context);

  }

 

}

6) Code an administration workspace for User and Role entity
That will not take a while using Jspresso ;)

 

You should be done !

Bip Bip's jspresso

The model generation is quite long. It's possible to shorten it. On this page, only the quickest options are kept.

So, two things are possible:
- Generate only one GUI, e.g. the Swing one. Even if it's not the final GUI of your project, it's the fastest way to check a prototype.
- Under Eclipse(1): gererate only the java and hbm files.

Disabling a GUI generation can be achieved by commenting modules sections. To keep only the Swing GUI(2), the changes are:

- in file {project}/pom.xml, comment:

         <module>webapp</module>

- in file {project}/startup/pom.xml, comment:

    <module>wings</module>
    <module>remote</module>
    <module>flex</module>
    <module>qooxdoo</module>
 
Then launching "mvn package" will generate only the files for Swing(3).

Whenever you would like to disable a UI generation that i based on the webapp (all UIs but swing), you also have to comment out all related dependencies in the webapp module. So, for instance, if you want to disable Qooxdoo UI generation, edit {project}/webapp/pom.xml and comment out all qooxdoo related dependencies.

When working under Eclipse, it's possible to go further. After a first "mvn package", there is no need to do it again, as fas as the changes made to the project concern only the entities and components. Just launch:
- either "mvn generate-resources"(4) if the persistence layer is concerned: java class files and hbm files will be generated;
- otherwise "mvn generate-sources"(5): only the java class files will be generated.

Notes:
(1) the installation of jspresso under Eclipse is detailled here http://www.jspresso.org/page/importing-hr-sample-application-eclipse.

(2) If you are working under Eclipse, to avoid error messages about missing class dependancies, the Web App Libraries must be filled. So, at least one run of "mvn package" with all the web modules is needed.

Besides, when working with a snapshot version of jspresso, the archive files of jspresso duplicated in the project are updated only if the web modules are uncommented.

(3) By the way, you can also disable ddl script generation (that will speed up things a little bit) by disabling the "generate-ddl-script" profile: launch maven with "-P!generate-ddl-scripts" (don't forget the !).

(4) It's possible to launch it from Eclipse. Create a launch configuration for Maven with:
- Base directory: ${project_loc}
- Goals: generate-resources
- Maven Runtime: external, with version 2.2.1
On the "Refresh" tab, check the "Refresh resources upon completion." box with the option "The project containing the selected resource", and the "Recursively include sub-folders" box.
When the configuration is created, you can add it to the Run button menu: click on the Run button and go to "Organize Favorites..."

(5) If the plugin Maven Integration for Eclipse is installed, then it's available from the context menu: Run As/Maven generate-sources. Otherwise, you can proceed as above in note (4).

Dynamic authorization with gates

 Jspresso allows you to limit the access to parts of your application to users adhering to a specific role. Jspresso foresees to apply this type of authorization on about all elements of your application: properties, entities, (sub)views, actions,… The Jspresso Reference Guide fully explains this type of authorization.

 

We could call this type of authorization rules “static authorization”: A user is authorized to a part of the application depending on his role.

 

In some cases you might need a more dynamic type of authorization. Imagine for instance:

 

Employees can create and submit holiday requests. Once submitted for approval, the time sheet can no longer be altered.

 

Clearly here a more dynamic way of authorization is needed: Being authorized to edit an holiday request entity not only depends on the user’s role, but also on the state of the entity. Jspresso supports this kind of dynamic authorization with so-called gates. A gate is open or closed and can be associated to

Every gate refers to a so-called model that determines if the gate is open or closed. Jspresso comes with 2 predefined types of models:

We give here an example of a writabilityGate that is open if the enumeration property state has the value "created" and hence will be closed for other state values (f.i. "submitted"):

<property name="writabilityGates">

  <list>

    <bean class="org.jspresso.framework.security.GrantedRolesGate">

        <property name="grantedRoles">

            <list>

                  <value>Employee</value>

            </list>

        </property>

    </bean>

     <bean class="org.jspresso.framework.binding.model.EnumerationPropertyModelGate">

          <property ref="accessorFactory" name="accessorFactory" />

          <property name="propertyName" value="state" />

          <property value="true" name="openOnTrue" />

          <property name="openingValues">

               <list>

                     <value>created</value>

              </list>

         </property>

    </bean>

  </list>

</property>

 

 

For instance, the above writabiligate could be included in an entity holidayRequest provided with an action “submit” that sets its property “state” from "created" to "submitted". Conclusion: The above writabilitygate specifies that only Employees are allowed to edit an holiday request, and only for requests in the state "created".

 

We invite you to do the test:

1)include the above snippet in model.xml in an entity that has an enumeration property called “state”. Alter the value of "state" and observe the impact. All the views backed by this entity will be impacted.

2/ transfer the above snippet on an entity property descriptor instead of the whole entity. Now only the field (and table cells) backed by this property will have the behaviour (editable <-> read-only) as described above.

3/ now transfer the snippet from the model.xml to the view.xml. Include it in a view on the entity. Only this view will be impacted.

 

As you could observe, the effect of the writabilityGate will automatically propagate: For instance, if a property is closed for editing, then this will be the case in all views displaying this property. As usal in Jspresso, you must specify things only once!

Entity filters: an example

There are 3 mechanisms to filter the result set of a query on an entity:

  1. Initialization mapping of search properties: This pre-fills properties in the search criterion. Initialization mapping is simply done in the domain model specification (DSL or Spring XML). The Jspresso+reference+manual contains several examples of initialization mapping.
  2. Specialising the QueryEntities action using the hook IqueryComponentRefiner. You can program this in Java. It allows for instance to force query values.
  3. Specialising the QueryEntities action using the hook IcreteriaRefiner. This allows to complement the criteria that will be selected by the user, with arbitrary complex clauses.

This page contains an example of the last variant: IcriteriaRefiner.
The use case goes as follows:

We have an entity holidayRequest. Employees are only allowed to see their own holiday requests. All other user profiles are managers and may see all holiday request.
Furthermore we have other entities that refer to a holidayRequest. We also want that, while selecting the referred holidayRequest, i.e. while picking a holidayRequest via a LOV, the same visibility constraints apply as for querying holidayRequest from the module query panel.

In order to achieve this we just construct two assets:

  1. A specialised queryEntitiesBackAction: Since all entity query actions (LOV, filter module, ...) use the same Jspresso action definition, i.e. queryEntitiesBackAction, we can overwrite this action in order to achieve that our criteria refiner is applied as a general visibility constraint throughout the application.
    Since this will install our criteria refiner code into ALL QBE queries, we have to verify, in our refiner code, on which type of entity the user is working and thus only apply the extra the Hibernate criteria in case of a holidayRequest being queried.
    The attached file “MyEntityFilter.java” contains this criteria refiner. Comments have been added to explain the code.
  2. Injection into the application: Inject your queryEntitiesBackAction into the system using Spring by including the bean below into your model.xml and you’re done:
    <bean id="queryEntitiesBackAction" class="org.jspresso.framework.application.backend.action.persistence.hibernate.QueryEntitiesAction">
      <property name="criteriaFactory" ref="criteriaFactory" />
       <property name="criteriaRefiner">
        <bean class="com.visual.engine.MyEntityFilter" />
     </property>
    </bean>
AttachmentSize
MyEntitytFilter.java_.txt1.91 KB

How to introduce build profiles

Objectives

A common need  in projects id to use Maven build profiles in order to customize the produced application for different environments. For instance, you might want to use an in-memory database with schema created and test data inserted at webapp startup in your development environment whereas you will use a "normal" database in production.

This article is about introducing different build profiles to tackle down this problem. Using a standard Jspresso project structure, we will define 2 Maven build profiles, dev an prod. The dev build profile should be the default one, whereas the prod profile should be activated (using -Pprod on the command line) to produce a production-specific, deployable webapp.

 

Introduce an environment variable

The first step is to introduce an environment variable that will have its value assigned depending on the activated profile. Edit your project root pom.xml and add the following section (before the <build> tag for instance) :

  <properties>
<environment>dev</environment>
</properties>
<profiles>
<profile>
<id>prod</id>
<properties>
<environment>prod</environment>
</properties>
</profile>
</profiles>

This asssigns "dev" as the value of the environment property, unless the prod profile is activated in the Maven build, in which case the environment property is valued with "prod".

 

Create profile-specific webapp resource directories

In order to adapt the produced webapp to the targeted platform, we will create 2 extra webapp resource directories :

All other common webapp resources will go in the standard webapp/src/main/resources directory.

 

Distribute the resources among the different resources directories

For the sake of our example, let's say that we want to :

  1. use a different config.xml for both profiles (to change the targeted database)
  2. package a log4j.properties only for the dev profile (production logging relies on a server-wide logging configuration)
  3. use the same ehcache.xml Hibernate cache configuration for both profiles

So we will :

  1. copy and customize the existing config.xml in both webapp/src/main/dev/resources and webapp/src/main/prod/resources directories. Delete the original one from the common webapp/src/main/resources directory.
  2. copy the existing log4j.properties only in webapp/src/main/dev/resources. Delete the original one from the common webapp/src/main/resources directory.
  3. leave the existing ehcache.xml Hibernate cache configuration in the common webapp/src/main/resources directory.

 

Include the activated, profile-specific, webapp resource directory in the build

We now have to tell Maven to include the different webapp resources directories based on the activated profile. This is done by adding the following xml fragment in the <build> section of the webapp/pom.xml :

  <build>
    ...
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>false</filtering>
</resource>
<resource>
<directory>${basedir}/src/main/${environment}/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
...
  </build>

 

Customize the dependencies included in the different environments

One common use case consists in customizing the built package depending on the target environement. For instance, you can run your development environment using Tomcat and your pre-production / production environment using JBoss. In that case, there are some libraries that have to be packaged in your development webapp war but not in your production war. Most of the time this is because the target environment provides them as part of their runtime infrastructure and packaging them in the application itself can lead to unexpected results (mainly linked to the classloading architecture). In the example above, JBoss will provide several jars that have to be excluded from the war. This is achieved by marking those dependencies as provided in the prod profile, e.g. :

  <build>
    ...
</build>
<profiles>
<profile>
<id>prod</id>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jbosssx</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-common</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>
<profiles>

 

Create customized web.xml descriptors

One last thing we would like to variabilize depending on the targeted environment is the packaged webapp descriptor. For instance, we might want to use different session timeouts for both profiles, disable the insertion of test data on webapp startup for the production war, ...

The standard Jspresso project already contains 2 different web.xml :

In order to further variabilize the used web.xml based on the targeted environment, we will :

 

Include the right web.xml in your maven build

We will now tell Maven to package the right web.xml depending on the environment build profile. Edit webapp/pom.xml and look for the 2 <webXml> sections. Replace :

This way, depending on the environment property value, the right web.xml will be picked up for packaging.

 

Conclusion

Of course, this profile-base build customization can be extended to include different customizations for other domains (e.g. a platform-specific build).

Following our example, everytime you need to create a production artifact, just activate the prod build profile, i.e. :

mvn clean package -Pprod

How-to : Secure your Flex frontend with HTTPS

Securing your Flex/BlazeDS communication requires some configuration.

First of all, a classic architecture :

Flex UI <- HTTPS -> Apache <- HTTP or AJP -> Application Server

Usually, an Apache HTTP server lives in the DMZ and receives HTTPS requests. HTTPS is only necessary over the internet, and the HTTPS payload should be removed from the Apache server before forwarding the (de-crypted) request to the application server servlet engine.

Here are the configuration changes to apply in order to obtain the architecture described above :

Please note that starting from the 3.5.9 version, the archetype already provides the 2 last modifications.

As a side note, there is a common problem you might face if removing the HTTPS payload on the Apache front and forwarding HTTP (not AJP) to the application server using apache mmod_proxy for instance. The Jspresso backend sometimes analyses the incoming request in order to build a new one and send it back to the Flex frontend. This allows for the same archive to be deployed on different infrastructure without being tied to a particular hostname, port or protocol. So far so good. But now, Flex sends HTTPS and the Jspresso backend receives HTTP, so it builds and sends back an HTTP based request to the Flex frontend, which, in turn will then use an un-crypted request. For instance, this is true for dynamically downloaded resources (e.g. images, documents built on the fly, and so on).

In order to fix this problem, the origin protocol (HTTPS) must be restored in the Java object representing the request before being passed to the Jspresso code. This common problem can be solved in Tomcat (and thus JBoss) by installing a custom Tomvcat Valve - org.apache.catalina.connector.RemoteIpValve - that will do the job. For the french speaking readers, there is a very good article from Xebia here that details the whole story. Here is the Tomcat documentation of the valve.

Import CSV files to create new entities

There is a new cute native Jspresso feature : IMPORT CSV FILE TO ANY TABLE

Import file menu

Import dialog

Instructions above are available in french and english depending on the user locale.
Unfortunately it is necessary to feed in the encoding at least once. It is saved in user preferences store and auto filled next time the user imports a new file. 
Please note that the European Microsoft Excel uses "CP1252" for Windows and "MacRoman" for Mac OSX.
From Jspresso developer perspective :
All you need is to use these generic action bellow :
action ref:'importBoxAction'

It is also possible to customize it deeply : for example to manage some additional columns, changing the CSV separator, forcing the encoding, etc. :

action ('importLineOfProductAction',
   
parent:'importBoxAction',
 
custom:[ excludedFields:['createdTimestamp''updatedTimestamp''createdBy''updatedBy'],
            additionalFields:[
'owner.lastname''owner.firstname''owner.userGroup.groupId']
]
)

The source code is available in the following class : org.jspresso.contrib.frontend.ImportEntitiesFactoryBean

This new import fetaure is available today in Jspresso-extension 1.0-SNAPSHOT and will be released in Jspresso-extension 1.0.16.

Enjoy !

The Jspresso Team

Importing a Jspresso module

This article will explain how to import a Jspresso module.

Step 1 : Declare the maven dependencies and setup xdoclet to generate also entity descriptors for module's components :

    pom.xml :

<properties>
  <mymodule.version>1.0-SNAPSHOT</mymodule.version>
</properties>

<dependency>
  <groupId>com.example.mymodule</groupId>
  <artifactId>mymodule-core</artifactId>
  <version>${mymodule.version}</version>
</dependency>

    core/pom.xml :

<dependency>
  <groupId>com.example.mymodule</groupId>
  <artifactId>mymodule-core</artifactId>
  <version>${mymodule.version}</version>
</dependency>
[...]
<plugin>
 <groupId>org.codehaus.xdoclet</groupId>
 <artifactId>maven2-xdoclet2-plugin</artifactId>
 <executions>
  <execution>
    <id>xdoclet-hibernate</id>
    <phase>generate-resources</phase>
    <goals>
      <goal>xdoclet</goal>
    </goals>
    <configuration>
      <configs>
        <config>
          <components>
            <component>
              <classname>org.xdoclet.plugin.hibernate.HibernateMappingPlugin</classname>
              <params>
                <version>3.0</version>
                <destdir>${project.build.directory}/generated-resources/xdoclet</destdir>
                <validate>false</validate>
              </params>
            </component>
          </components>
          <includes>**/model/**/*.java,**/model/*.java</includes>
        </config>
      </configs>
      <sourceArtifacts>com.example.mymodule:mymodule-core</sourceArtifacts>
    </configuration>
  </execution>
 </executions>
</plugin>    

Step 2 : Complete the Spring configuration to include module's spring configurations files :

    For each configuration add the corresponding modules :
   
    beanRefFactory.xml :

<bean
    id="myapp-remote-context"
    class="org.springframework.context.support.ClassPathXmlApplicationContext"
    lazy-init="true">
    <constructor-arg>
      <list>
        <value>org/jspresso/framework/application/commons.xml</value>
        <value>org/jspresso/framework/application/backend/persistence/hibernate/commons-hibernate.xml</value>
        <value>org/jspresso/framework/application/frontend/commons-frontend.xml</value>
        <value>org/jspresso/framework/application/frontend/remote/commons-remote.xml</value>
        <!-- my module extensions -->
        <value>com/example/mymodule/model/dsl-model.xml</value>
        <value>com/example/mymodule/model/model.xml</value>
        <value>com/example/mymodule/backend/dsl-backend.xml</value>
        <value>com/example/mymodule/backend/backend.xml</value>
        <value>com/example/mymodule/view/dsl-view.xml</value>
        <value>com/example/mymodule/view/view.xml</value>
        <value>com/example/mymodule/frontend/dsl-frontend.xml</value>
        <value>com/example/mymodule/frontend/frontend.xml</value>

        <!-- my app -->
        <value>com/example/myapp/model/dsl-model.xml</value>
        <value>com/example/myapp/model/model.xml</value>
        <value>com/example/myapp/backend/dsl-backend.xml</value>
        <value>com/example/myapp/backend/backend.xml</value>
        <value>com/example/myapp/view/dsl-view.xml</value>
        <value>com/example/myapp/view/view.xml</value>
        <value>com/example/myapp/frontend/dsl-frontend.xml</value>
        <value>com/example/myapp/frontend/frontend.xml</value>
        <value>com/example/myapp/config.xml</value>
      </list>
    </constructor-arg>
  </bean>

Step 3 : Complete the Hibernate mapping

    core-config.xml :

<bean id="hibernateSessionFactory" parent="abstractHibernateSessionFactory">
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.query.substitutions">true 1, false 0, yes 'Y', no 'N'</prop>
      <prop key="hibernate.dialect">${hibernate.dialect}</prop>
      <prop key="hibernate.order_updates">true</prop>
      <prop key="hibernate.max_fetch_depth">1</prop>
      <prop key="hibernate.default_batch_fetch_size">8</prop>
      <prop key="hibernate.jdbc.batch_versioned_data">false</prop>
      <prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
      <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
      <prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
    </props>
  </property>
  <property name="mappingLocations">
    <list>
      <value>classpath*:com/example/myapp/**/*.hbm.xml</value>
      <value>classpath*:com/example/mymodule/**/*.hbm.xml</value>
    </list>
  </property>
</bean>

Step 4 : Import jspresso modules into SJS

    application.groovy :

ManageModule libraries = new ManageModule()
libraries.importModuleAsResource('mymodule')

def domainBuilder = new Domain(libraries)

Step 5 : Including jspresso module's internationalization keys

    frontend.groovy :

messageSource(
  basenames:['com.example.myapp', 'com.example.mymodule'])

NB : A future article will explain how to move a standard Jspresso projet to a Jspresso module

Maxime

LDAP and custom properties

Jspresso provides all the necessary plumbing to seamlessly integrate any JAAS login module and as of this writing, there are 2 login modules that come with the framework :

• The development login module which does not require any backend and is perfectly suited for development.

• The ldap login module which authenticates the user against an LDAP directory [1].

You may find many other, freely available, JAAS login modules to meet your needs (JDBC, ascii encrypted file, ...).

Here, we will discuss the LDAP login module and its extension to deal with custom properties.

The Jspresso LdapLoginModule extends the JBoss 4.2.2 LdapExtLoginModule so its documentation [2] apply for the basic configuration. It's an enhanced module with support for custom properties to be extracted from he LDAP directory and stored in the Jspresso org.jspresso.framework.security.UserPrincipal (put/getCustomProperty).

The syntax for custom properties configuration is the following :

custom.propertyName="expression"

where expression is in the form :

recordDN[sliceStart, sliceEnd].attribute

some examples :

with slicing :

The same as above, but extracting an attribute instead of the DN :

Notes:

[1] As LDAP tools, see Apache Directory Studio. It works under Eclipse and contains anything you need. You can start with the embedded server and the browser. Even for a newbie like me, with it, LDAP is a breeze.

[2] See also http://www.jboss.org/community/wiki/LdapExtLoginModule

 

Manage hibernate dependencies when deploying under jboss 4.x with jspresso 3.5.5+

Starting from version 3.5.5, Jspresso uses Hibernate 3.6. When deploying your project under JBoss 4.x you hit Hibernate version conflict errors since JBoss provides older Hibernate jars under the server lib/ directory.

You have two options to resolve this conflict :

  1. Remove the Hibernate libraries from your JBoss lib/ directory. Although it might work for simple webapps, this option is quite dangerous because JBoss uses Hibernate internally and you might get unexpected and obscure errors.
  2. Keep the old Hibernate libs in JBoss but configure your application deployment so that it exclusively uses the Hibernate libraries you've packaged in your deployable unit(s), i.e. WAR or EAR.

Let's dive into the second option. But before going forward, you might fing useful to learn about Java and JBoss classloading. For those of you who read french, you can go through this post.

 


The problem :

Let's describe the problem more accurately :
  - Hibernate libraries are defined as framework/application dependencies, but also defined as JBoss libraries.
  - According to the default JBoss classloading your application try to find libraries first in the JBoss libraries and then, in the application libraries.
  - So the old, outdated Hibernate libraries get loaded from the JBoss libs, hiding you nice up-to-date ones.
  - You get all sort of exceptions...

 


The solution :   
  
So what to do ? We are going to deploy the application in its own classloader, so that :

  1. It's isolated from the other applications deployed in the same server.
  2. We can revert the order classes are looked for by the application, i.e. first in the application and then in the JBoss libs.

How ? The solution is nicely described in this blog post : http://jaitechwriteups.blogspot.com/2008/08/how-to-upgrade-hibernate-in-JBoss.html?m=1

1 - Isolate the application WAR classloader :
Create a file webapp/src/main/webapp/WEB-INF/jboss-web.xml that contains the definition of the classloader :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.4//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_4_0.dtd">
<jboss-web>
<loader-repository>
com.example:loader=yourapp.war
<class-loading>
<loader-repository-config>
java2ParentDelegation=false
</loader-repository-config>
</loader-repository>
</class-loading>
</jboss-web>

See the java2ParentDelegation=false ? This is what makes the classloader look first into the application libraries before going to the server libraries. Of course, com.example and yourapp can be any string as long as they are unique.

 

2 - Make sure that all Hibernate dependencies are actually present in the application libraries. If not, the application classloader is going to look first in its libraries (which is fine) and then in the server libraries. This will be especially problematic for Hibernate, as this is very well explained in the blog post mentioned above.

Here is what we found out to work well for declaring the needed extra Hibernate libraries.

In your root pom.xml in dependencyManagement section:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>3.1.0.GA</version>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.2.0.Final</version>
</dependency>

and in your webapp/pom.xml :

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
</dependency>

 

This step will bring a compatible hibernate-validator library to the aplication that is only here to hide the application server incompatible one. The tricky part is the hibernate-commons-annotation exclusion. It is there to prevent hibernate-validator to tansitively bring an old incompatible version of hibernate-commons-annotation, thus keeping the one that is compatible with our Hibernate 3.6 version, i.e. the 3.2.0.Final.

  
With these few updates, your application should run without problem in an unmodified JBoss 4.x server instance.

 


Whenever you have to package and deploy an EAR to the server, you have to apply almost the same modifications :
 1 - Create a file earmodule/src/main/application/META-INF/jboss-app.xml to isolate the EAR classloader.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-app PUBLIC "-//JBoss//DTD J2EE Application 1.4//EN"
"http://www.jboss.org/j2ee/dtd/jboss-app_4_0.dtd">
<jboss-app>
<loader-repository>
com.example:loader=yourapp.ear
<loader-repository-config>
java2ParentDelegation=false
</loader-repository-config>
</loader-repository>
</jboss-app>

 2 - Modify earmodule/pom.xml to add hibernate dependencies :

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
</dependency>

Merging entities from different realms : the different merge modes

Jspresso is able to deal with different entity realms in order to cleanly synchronize with transactions. An entity, identified by its id, may have at most 1 instance in 1 realm. Each entity realm keeps track of the entity version and dirty state (what are the properties that have been changed on the entity and have not been flushed to the DB). 2 realms cannot share the same object instance for the same entity instance.

For one user application session, there are usually at most 2 entity realms :

  1. The application session entity realm : this is where live the entity instances that are bound to the Jspresso UI. These instances are long-lived instances and are not tied to a specific hibernate session (consider them as detached instances). The big job for Jspresso is to maintain and synchronize their states without changing their references accross the various transactions, DB queries, and so on...
  2. The current transaction "Unit Of Work" (UOW) realm. In this realm, entities (that are different object instances than the session ones) are in a transitional state that will be either committed or rolled-back depending on the actual transaction outcome. Whenever the transaction commits, the UOW entities states must be merged back to the session entities states (state merged and dirty state cleared). Whenever the transaction rolls back, the session realm entities remain untouched (state and dirty state preserved). All the entities living in the UOW realm are attached to the underlying Hibernate session that backs the transaction.

When performing DB queries using the Hibernate backend controller method findXXXByCriteria(...), there is, behind the scene a UOW realm. Whenever the call is performed from a pre-existing UOW, then the same realm is used, and no merge is done since it will be performed depending on the outer TX outcome. But whenever ther is no UOW that has already been started, the method starts one, performs the query, and merges the UOW realm entities in the session realm.

When you start a transaction, clone an entity from the session realm to the UOW realm, update the UOW entity and commit the transaction, you intuitively expect the session entity to receive the updated state, its dirty state to be cleared, and its version to be incremented. This is what we call the CLEAN_LAZY merge mode.

Now, what happens when you retrieve fresh entitities from the DB and want to import (merge) them in the session realm ? Do you want to replace the session entity state with the DB state in any case (like a reload operation) ? Do you want to replace the state if and only if the entity version is newer in th DB (i.e. another user committed a new state) and the session entity state is obsolete ? We have to instruct Jspresso what to when merging an entity from one realm to another. Moreover, this merging process is recursive along the entity reachable graph. When merging an entity between 2 realms, you are not only merging the entity itself, but also all the entities that are related to it through the association relationships and recursively.

Jspresso resolves this problem by introducing merge modes that (hopefully) cover the various merging strategies applied for merging one entity instance and its reachable graph. There are 2 big categories of merge modes :

The different supported CLEAN merge modes :

The other merge modes that do not clean traversed entities dirty states :

 

Don't forget that merging entities serve 2 purposes :

  1. Merging entity graph states.
  2. Retrieving target entity realms instance references as the return value of the merge (or find) methods.

Moving from HSQL to persistent DB

See documentation ref. "I.6.1.2. The backend configuration"

1) Create your database (bellow are sample file for MYSQL)
    Take care to disable hibernate schema generation (parameter hibernate.hbm2ddl.auto)

2) Init your database schema :
   - launch mvn package
   - extract "myproject-schema-mysql-innodb.sql" file from myproject/core/target/weather-core-1.0-SNAPSHOT-ddl-scripts.zip
   - execute script on your new DB

3) Change your swing application launcher to avoid TestDataPersister execution :
    Replace
        -applicationClass fr.gefco.weather.startup.swing.development.SwingDevApplicationStartup
    By
        -applicationClass fr.gefco.weather.startup.swing.SwingApplicationStartup

That's all !

----------------------

Here is a sample config.xml file for MySQL :

<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
  default-lazy-init="true">

  <!-- dataSource MySql localhost -->
  <bean
    id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource">
    <property
      name="driverClassName"
      value="com.mysql.jdbc.Driver" />
    <property
      name="url"
      value="jdbc:mysql://localhost:3306/myproject" />
    <property
      name="username"
      value="root" />
    <property
      name="password"
      value="rootpwd" />
  </bean>

  <!-- HIBERNATE MYSQL -->
  <bean
    id="hibernateSessionFactory"
    parent="abstractHibernateSessionFactory">
    <property
      name="hibernateProperties">
      <props>
        <prop
          key="hibernate.query.substitutions">true 1, false 0, yes 'Y', no 'N'</prop>
        <prop
          key="hibernate.dialect">org.jspresso.framework.model.persistence.hibernate.dialect.MySQL5InnoDBDialect</prop>           
         <prop
          key="hibernate.order_updates">true</prop>
        <prop
          key="hibernate.max_fetch_depth">1</prop>
        <prop
          key="hibernate.default_batch_fetch_size">8</prop>
        <prop
          key="hibernate.jdbc.batch_versioned_data">true</prop>
        <prop
          key="hibernate.jdbc.use_streams_for_binary">true</prop>
        <prop
          key="hibernate.cache.region_prefix">hibernate.test</prop>
        <prop
          key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
     <!--   <prop
          key="hibernate.hbm2ddl.auto">update</prop> -->
        <prop
          key="hibernate.jdbc.batch_size">0</prop>
      </props>
    </property>
   
    <property
      name="mappingLocations">
      <list>
        <value>classpath*:fr/gefco/myproject/**/*.hbm.xml</value>
      </list>
    </property>
   
  </bean>
</beans>

Query by example (QBE) filter views

Jspresso heavilly relies on QBE screens for retrieving entities from the data store. This is certainly the first kind of module you've actually coded if you've followed the tutorials. You mainly use QBE for CRUD (filter modules) UIs, but also for LOV (List of values) type of fields where the user can select and attach (or auto-complete if the search is narrow enough) a related entity following an association path.

QBE can be very powerful in Jspresso, especially if you know some special tricks for refining your search. Each text based query field support some special characters that allows you to express more complex filters than only a "like"-based filter :

The nice thing is that they can all be combined together. Below are a few examples of such combinations :

Using those simple combinations (and teaching them to your users) can prevent you from writing specific query actions in most cases.

 

Single Sign-on integration

Preamble

Jspresso EE supports "Single Sign-on" (SSO) through SPNEGO Integrated Windows Authentication (IWA). This means that you can very easily deploy a Jspresso EE based application and make it participate to your Windows Active Directory SSO architecture using the Kerberos protocol and service tickets exchange.

The main principles of this SSO negociation is described in this excellent OCTO blog post.

Jspresso EE SSO integration relies on the Sourceforge SPNEGO HTTP filter, so all documentation regarding the configuration of this filter apply. Take some time to perform their pre-flight checklist.

 

The application side

Starting from Jspresso EE 3.5.11, the archetype will generate all the necessary stuff for you when creating a new application. However, if you would like to integrate an existing application into your SSO architecture, here are the steps to perform :

Edit core/src/main/resources/your/application/package/frontend/frontend.xml and add the following controller declaration:

<bean id="remoteFrontController" abstract="true" parent="abstractFrontController"
class="org.jspresso.framework.application.frontend.controller.remote.EnhancedRemoteController" />

Edit webapp/pom.xml and add the following dependency :

  <dependencies>
...
<dependency>
<groupId>org.jspresso.framework</groupId>
<artifactId>jspresso-ext-security</artifactId>
</dependency>
...
</dependencies>

Edit webapp/src/main/webapp/WEB-INF/web.xml and add the following servlet filter declaration :

<web-app>
...
  <filter>
<filter-name>SsoHttpFilter</filter-name>
<filter-class>org.jspresso.framework.security.sso.SsoHttpFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SsoHttpFilter</filter-name>
<url-pattern>/flex/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>SsoHttpFilter</filter-name>
<url-pattern>/qooxdoo/*</url-pattern>
</filter-mapping>
...
</web-app>

The application server side

In this section we describe the configuration of JBoss 4. Configuring another application server should be as straightforward.

Copy the SPNEGO filter jars and dependencies in your application server instance lib directory, i.e. ${JBOSS_HOME}/server/[your server instance, e.g. default]/lib/ :

All these jars are already present in your webapp but have to be copied in order to be available to the server class-loader. You can retrieve them in webapp/target/[your application]-webapp/WEB-INF/lib :

Create and edit krb5.conf kerberos configuration file :

We suggest to create it under the conf directory of your application server instance, i.e. ${JBOSS_HOME}/server/[your server instance, e.g. default]/conf/. Here is a sample krb5.conf file as directed by the Sourceforge SPNEGO documentation.

[libdefaults]
default_realm = YOUR.AD.DOMAIN
default_tkt_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
permitted_enctypes = aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc

[realms]
YOUR.AD.DOMAIN = {
kdc = yourkdc.your.ad.domain
default_domain = YOUR.AD.DOMAIN }

[domain_realm]
.YOUR.AD.DOMAIN = YOUR.AD.DOMAIN

Edit the default application server web.xml and add the SPNEGO filter, i.e. ${JBOSS_HOME}/server/[your server instance, e.g. default]/deploy/jboss-web.deployer/conf/web.xml.

This is as directed by the Sourceforge SPNEGO documentation. Note however that the filter type changes since there was a need to make it lenient in order to allow a "classic" login fallback :

<filter>
<filter-name>LenientSpnegoHttpFilter</filter-name>
<filter-class>org.jspresso.framework.security.sso.LenientSpnegoHttpFilter</filter-class>
<init-param>
<param-name>spnego.allow.basic</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>spnego.allow.localhost</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>spnego.allow.unsecure.basic</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>spnego.login.client.module</param-name>
<param-value>spnego-client</param-value>
</init-param>
<init-param>
<param-name>spnego.krb5.conf</param-name>
<param-value>../server/[your server instance, e.g. default]/conf/krb5.conf</param-value>
</init-param>
<init-param>
<param-name>spnego.login.conf</param-name>
<param-value>login.conf</param-value>
</init-param>
<init-param>
<param-name>spnego.preauth.username</param-name>
<param-value>[preauth username]</param-value>
</init-param>
<init-param>
<param-name>spnego.preauth.password</param-name>
<param-value>[preauth password]</param-value>
</init-param>
<init-param>
<param-name>spnego.login.server.module</param-name>
<param-value>spnego-server</param-value>
</init-param>
<init-param>
<param-name>spnego.prompt.ntlm</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>spnego.logger.level</param-name>
<param-value>1</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LenientSpnegoHttpFilter</filter-name>
<url-pattern>/flex/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LenientSpnegoHttpFilter</filter-name>
<url-pattern>/qooxdoo/*</url-pattern>
</filter-mapping>

 

 

Transaction handling

Developping a RIA that involves long-lived entities introduces a big challenge regarding transaction handling. In traditional webapps, the transaction is mapped to the ORM session (e.g. JPA or Hibernate) wich, in turn, is mapped to simple and well-defined application lifecycle phases (be it the HTTP request itself with the "open session in view" pattern, or a more extended scope like JBoss Seam or CDI "conversation").

Jspresso offers a more flexible application worlkflow where the user is not driven by pre-defined pages chainings. Unlike in traditional webapps, the transient model state can evolve independently of the persistent state in an arbitrary way until the user decides to save (or rollback) the changes. Therefore, there is a subtle coordination achieved by the framework to that the model in-memory state remains coherent with the persistent one. Jspresso cannot solely rely on the ORM session to handle the session model state since it is bad practice to keep the ORM session open indefinitely and span multiple requests/transactions. Moreover, any runtime exception can make the ORM session unusable, thus invalidating all the transient state.

In order to achieve this, Jspresso implements automatic tracking of session model changes, and seamlessly deals with the ORM to merge those changes back to the persistent store. All this work nicely integrates with transaction management through the concept of "Unit of Work" (referred to as UOW). A Jspresso UOW can be seen as an in-memory draft zone where model parts can be duplicated and modified without impacting the actual model session state. A UOW can be started, committed, rolled-back, very much like transactions indeed. But you will rarely need to manage the UOW yourself since Jspresso automatically maps its lifecycle to the actual underlying transaction platform, e.g. the Hibernate session, the JTA implementor, ... This means that whenever a transaction is actually started, committed or rolled-back, thhe corresponding operations are performed on the UOW.

Jspresso offers built-in standard actions as well as a comprehensive API to handle domain model persistence along with transactions. Once the user as updated parts of the model using the application views and services here is a synopsis of the operations involved in persisting the changes :

  1. A transaction is started through an action triggered by the user (either the built-in standard save action or a custom one).
  2. All or a subset of the session-modified model is imported (carbon-copied) into the unit of work.
  3. All sorts of operations are performed on UOW entity clones like further modifications, ORM updates and deletes, ... all this in complete isolation from the outer application session state.
  4. Depending on the transaction outcome, the UOW content is merged back to the application session or simply forgotten, in which case, the session state remains untouched.

 

 

Starting a transaction

There are 2 different ways to start a transaction in Jspresso. The first one is to annotate the backend action class with org.jspresso.framework.application.backend.action.Transactional (beware to use the Jspresso one, not the spring one). It forces the action to execute inside a transaction with a REQUIRED propagation scheme (use the existing transaction if any or start a new one).

@Transactional
public class MyCustomAction extends AbstractHibernateAction {
  ...
}

The previous method is just a shortcut to using the Spring transaction template that is configured in the application backend controller. This is the second, more versatile way of using transactions in Jspresso :

public class MyCustomAction extends AbstractHibernateAction {
  @Override
  public boolean execute(IActionHandler actionHandler, final Map<String, Object> context) {
    getBackendController(context).getTransactionTemplate().execute(new TransactionCallback() {       
      public Object doInTransaction(TransactionStatus txStatus) {
        ...
      }
    });
  }
}

The second form allows for more control over the transaction(s) demarcation, e.g. chain 2 or more different transactions in the same action.

 

Cloning working entities into the UOW

Before working on an entity transactionally you have to make a carbon copy of it into the UOW and do all the work on this copy. The backend controller provides the following methods to perform the carbon copy :

  IEntity cloneInUnitOfWork(IEntity entity);

  List<IEntity> cloneInUnitOfWork(List<IEntity> entities);

Several characteristics of the cloning process :

 

Note :
You should always make sure that you work on UOW entity clones inside a transaction. This is the only way that Jspresso can garantee the complete isolation of the transaction space. Obscure Hibernate exceptions like "entity with the same identifier was already registered in the session" are generally the consequence of working on entities that have not been cloned into the UOW.

 

Perform operations on UOW entity clones

Now that you are in an isolated space, you can do whatever you need without having the risk to polute the application session in case of a failure (either due to a functional violation or a technical error). Note that all operations made on the UOW entities are local to the unit of work, e.g. :

 

Merge UOW entity clones back into the normal application session

It is time to commit or rollback the transaction. This is automatically done by the Spring transaction template or indirectly at the end of the "execute" method of a Transactional annotated backend action.
Whenever the transaction is rolled back, the UOW is simply discarded and the session state remains untouched.
Whenever the transaction is committed, we have to merge back the UOW state that needs to be propagated to the application session current state.

This last step is automatically performed for created/updated entities whose state has been synchronized (flushed) to the store on transaction commit. During this phase, the entity state is merged back to the correponding session entity (if any) and its dirty state is reset to unmodified.
This is all perfect for save operations; however, there are situations when you need to apply another type of merge from the UOW entities. These other merge modes cannot be automatically inferred by the framework, and thus, must be explicitely coded through the use of the following backend controller methods :

  IEntity merge(IEntity entity, EMergeMode mergeMode);

  List<IEntity> merge(List<IEntity> entities, EMergeMode mergeMode);

Several characteristics of the merging process :

Jspresso implements 4 different merge modes :

 

Translatable properties

Internationalizing a business application is not only a matter of translating the static labels in the UI. There are times when it is mandatory to be able to translate the data itself. For example, take a business application with geographic master data. A country has a code and a label. Although you could argue that country ISO code is universal and should be understood by everybody, put a chinese user in front of your application. Even if you enforced chinese translation of your UI (through resource bundles), your chinese user may have a hard time figuring out that BE/Belgique is actually Belgium. Of course, the opposite is true ;-)

Translating data is especially important for languages that use different alphabets, where you can't even guess or try to pronounce what's written on your screen. However, data translation should be as transparent as possible for the developer as well as for the user.

Starting from 3.7, Jspresso introduces translatable properties. A string property the is declared translatable:true will automagically instruct Jspresso to create a translation store for property value translation management. In fact an extra table will be created per translatable entity, i.e. entities that have at least one translatable property. This translation store will be keyed by the tuple (entityId, propertyName, languageCode).

At runtime each translatable property will actually be split in 3. Lets take an example. We have a City entity with a translatable name property. The entity will actually have 3 properties added (1 persistent and 2 derived) :

  1. nameRaw : is the un-translated, language independent, value. You could agree on a master language for your application but it's just a convention and Jspresso does not enforce any special meaning for this nameRaw value. An other option could be that the nameRaw contains the city name expressed in the official language of the country the city belongs to.
  2. nameNls : is the user session translated value of the name property. It means that a french user will get "Paris" for Paris, whereas an italian user will get "Parigi". Whenever there is no translation available in the City translation store, this property returns null.
  3. name : is the most versatile property. If a translation exists, return it; if not return nameRaw.

We expect that most UI will be built using the "name" Property, which is why this is the one retaining the original name given by the developer.

The good thing is that all 3 properties are writable and can be used for sorting (even pageable views), auto-completing, ... They all behave as a normal, persistent property for the developer as well as for the user. So, what happens when setting these properties ?

  1. setting nameRaw will simply set the un-translated property in the entity itself.
  2. setting nameNls will exclusively work on the translation. If a translation exists for the name property and the connected user language in the translation store, then update it; if not then create a new one and save it in the store.
  3. setting name is like setting nameNls, except that it also sets the nameRaw if the entity has just been created and has not been saved yet.

As we said before, every translatable entity will have its own translation store. In the database, it will be materialized by an extra table (e.g. CITY_TRANSLATIONS).

The last feature (but not least) is that you can very easily access the different translations of a given entity instance as well as you can make a view out of it for allowing an administrator / translator to massively translate some piece of data. The set of property translations is named propertyTranslations. It holds instances of classes implementing org.jspresso.framework.model.component.IPropertyTranslation. Each entity has it's own property translation class, defined as an inner class of the entity interface. For instance property translations of the City entity will be instances of City.Translation.

Whenever you want to display a master-detail view for the property translations, you can simply use the general purpose one : translations.table.

Keep in mind all the details, but also the fact that making a property translatable for a legacy Jspresso application should not be more work than setting translatable:true for this property. The existing code should continue to work without any other change.

Hope you'll enjoy it.

Using in-memory transactions

Objectives and workflow

There are times when users need to act on the model transactionally but :

  1. the transaction needs to span multiple user interactions
  2. the transaction might be commmitted or rolled-back based on user decision (confirming or cancelling a dialog box for instance)

Jspresso leverages the pattern of "Unit Of Work" (UOW, see Transaction handling) in order to satisfy such situations. The principles are following :

  1. You manually start a UOW using the backend controller
  2. You clone the entities you want to work on in the created UOW
  3. You open a dialog (maybe a wizard), that allows to work on your cloned entities
  4. You either commit or rollback the UOW on dialog closing

Starting the UOW and working on your entity clones

Since you want to be able to rollback the changes you're going to make to your model you will need a UOW that isolates the entities you're working on from the rest of the application state. This is normally what is achieved behind the scene by Jspresso when you start an transaction (see Transaction handling). However, and it's good design, the actual transaction will not span multiple user interactions. This is essential not to lock shared resources indefinitely if anything happens to the client between the begining and the outcome of the transaction.

For this reason, Jspresso allows you to open a UOW that is not actually backed by a transaction, but which will offer the in-memory isolation you need in order to be able to rollback your changes if needed. Once you've started a UOW, Jspresso considers you're in a transactional state and will apply all the corresponding backend mechanics, be it a real transaction behind or not.

In order to start the UOW, simply tell the backend controller to do it somewhere in your action chain :

getBackendController(context).beginUnitOfWork();

From now on, everything happens as if you were in a classic transaction template. So the first things you will want to do is to clone the entities you want to work with and put the clone in the action context for later use.

MyEntity entityClone = getBackendController(context).cloneInUnitOfWork(entity);
setActionParameter(entityClone, context)

Whenever you expect an exception to happen here that would stop th action chain, surround your code with a try/catch block in order to rollback the UOW before rethrowing the exception, e.g. :

try {
...
} catch(RuntimeException ex) {
getBackendController(context).rollbackUnitOfWork();
throw ex;
}

Next step is to open the transactional dialog and bind it to the cloned entities you get back from the action context. Don't forget that all the rules that apply to a normal transaction also apply here. This means that all sanity checks preserving the UOW isolation will be triggered.

Committing ar rollbacking the transaction

Once the user interactions are complete (the user has completed or cancelled the wizard for instance) it's time to transform the UOW into something real.

The easiest operation is to rollback the UOW in case the user does not want to persist the changes he made in the transaction. this is usually bound to the cancel action of the dialog :

getBackendController(context).rollbackUnitOfWork();

 

The other option is to commit the UOW by using a real transaction that will persist all the work that has been done transactionally. This is usually bound to the commit action of the dialog. In that case, you will simply use a normal transaction template. It will detect that a UOW is pre-existing and will bind to it. Once the real transaction commits, the entities will be flushed to the persistent store and the UOW will be committed to the application session.

getBackendController(context).getTransactionTemplate()
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// athough idempotent, cloning the entit will re-attach the entity clone with
// the underlying Hibernate session.
getBackendController(context).cloneInUnitOfWork(entityClone);
}
});