Thursday, March 22, 2012

NextReports : Text Rotation

In version 5.2 of NextReports a new property for cells is added: text rotation. This property has meaning only for PDF or EXCEL exporter formats. It can have only three degrees values :
  • 0  : no rotation 
  • 90  : text is rotated to the left with 90 degrees 
  • -90 : text is rotated to the right with 90 degrees
You can have for 90 degrees value something like:

 And for -90 degrees value:

Monday, March 19, 2012

NextReports Engine : Report Conversion

Version 5.1 of NextReports was already released, but  there is the time to look into next version.

One of the biggest feature, NextReports will resolve in version 5.2 is report conversion. This is not a 'visible' feature to the end user, but a back-end functionality.

NextReports supported from start back-end compatibility with older type of reports. Any new functionality was brought to life without breaking compatibility. For NextReports Engine users (developers) this means that anytime when a ReportUtil.loadReport(String xml) or a ReportUtil.loadReport(InputStream is) was done, no xml exception was raised.

To be more flexible in NextReports evolution, an entirely new convert process was integrated. Using a chain of xml converters, a Next report can be modified to a new structure. Practically if a report with version X is loaded by an Y version of engine, where Y > X, the load method will do the following:
  • get report version (X in this case)
  • apply a converter chain process if needed
  • load the resulted xml
To explain the second step,  for example if we have a Converter_X1 (to version X1) and a Converter_X2 (to version X2) with X < X1 < X2 < Y then conversions X->X1 and X1->X2 are needed,they are applied using a chain of responsibility order and the new version of report is set to Y.

The only differences to NextReports Engine are the signatures of the load methods from ReportUtil class which now throw a LoadReportException:
public static Report loadReport(String xml) throws LoadReportException { ... }

public static Report loadReport(InputStream is) throws LoadReportException { ... }

Friday, March 09, 2012

NextReports : Notifications with Wicket Push

With version 5.1, NextReports Server implements a much-desired feature: message notification.

In previous versions, when a report was manually set to run, user was redirected to Monitor section. Here running processes can be seen and with an auto-refresh at some time the result will appear in history table, containing the link to the document.

In current version, after a manually run, a message in the top right corner will notify that the process started and that a new message will be issued after finish. This first message has a short life of 5 sec and it will disappear after time elapsed. When process is finished, a new message will be shown containing the link to resulted document. This message will live until the user will close it.


If an error occurred  during export process, an error message will be shown.


To see the actual error user has to go to Monitor section and look in history table to "Success" column.


If you schedule a report, you will only be notified about it. No messages when the process is finished will be issued.


To implement this notification feature, NextReports uses wicket-stuff push and jQuery jGrowl.

Using a general Observer pattern, first we need an event ReportResultEvent and a listener interface:
public interface ReportListener {    
    public void onFinishRun(ReportResultEvent result);
} 
Our ReportService will manage report listeners for current logged user:
public void addReportListener(ReportListener reportListener) {
    reportListeners.put(SecurityUtil.getLoggedUsername(), reportListener);        
}

public void removeReportListener() {        
    reportListeners.remove(SecurityUtil.getLoggedUsername());
}

public void notifyReportListener(ReportResultEvent event) {
    ReportListener reportListener = reportListeners.get(event.getCreator());
    if (reportListener != null) {
        reportListener.onFinishRun(event);
    }
}
When the process is finished we will create the event and we will notify all listeners:
ReportResultEvent event = new ReportResultEvent(...);
reportService.notifyReportListener(event);
To use jGrowl in Wicket it's easy with an AjaxBehavior. Messages will be kept in Session FeedbakMessages list.
public class MessageAjaxBehavior extends AbstractDefaultAjaxBehavior {

   public void renderHead(IHeaderResponse response) {

     super.renderHead(response);      
     response.renderJavascriptReference(
         new JavascriptResourceReference(MessageAjaxBehavior.class, "jquery.jgrowl.js"));
     response.renderCSSReference(
         new CompressedResourceReference(JGrowlAjaxBehavior.class, "jquery.jgrowl.css"));
     response.renderCSSReference(
         new CompressedResourceReference(JGrowlAjaxBehavior.class, "jgrowl.css"));

     String feedback = renderFeedback();
     if (!StringUtils.isEmpty(feedback)) {
         response.renderOnDomReadyJavascript(feedback);
     }

   }

   protected void respond(AjaxRequestTarget target) {
     String feedback = renderFeedback();
     if (!StringUtils.isEmpty(feedback)) {
        target.appendJavascript(feedback);
     }
   }
   ....... 
}
Rendered feedback message contains the jgrowl javascript text. Message can be sticky to live until user close it, or it can have a time-elapsed life using life with a millisecond value.
$.jGrowl("message",   
           {
             theme: 'css-class',
             sticky: true        //  life: 5000        
           }
        )
All options of jGrowl can be found here.

Finally our wicket page will contain a message label:
Label messageLabel = new Label("message", "");
messageLabel .setOutputMarkupId(true);
messageLabel .add(new MessageAjaxBehavior());
And we will initialize push service:
protected void onInitialize() {

    super.onInitialize();

    initPush();
           
    reportService.addReportListener(new ReportListener() {            
            
        public void onFinishRun(ReportResultEvent result) {                

            if (pushService.isConnected(pushNode)) {
                // forward the Message event via the push service 
                // to the push event handler
                Message message = createMessage(result);
                pushService.publish(pushNode, message);
            }
        }
    });
}

private void initPush() {

    // instantiate push event handler
    IPushEventHandler handler = new AbstractPushEventHandler() {     
            
        public void onEvent(AjaxRequestTarget target, Message event, 
                            IPushNode node, IPushEventContext context) {
            
            getSession().getFeedbackMessages().add(
                         new FeedbackMessage(null, event.getText(), messageType));
            target.addComponent(messageLabel);
        }       
    };

    // obtain a reference to a Push service implementation
    pushService = TimerPushService.get(); 
        // install push node into this panel
    pushNode = pushService.installNode(this, handler);
}