The project defines a schema for future storage of web application state The schema is located in the folder: src/main/resources.
The project uses JAXB through the Maven plugin cxf-xjc-plugin to generate classes from XML schema.
The standard Java enterprise edition deployment descriptor for web applications is located in src/main/webapp/WEB-INF/web.xml. The descriptor is defined with the version 3.0 schema, i.e. http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd.
The descriptor is defined with metadata-complete="false" to enable scanning of annotations (servlets etc.).
The descriptor defines:
ZK specific deployment descriptor for web applications is located in src/main/webapp/WEB-INF/zk.xml
The descriptor defines:
Pineapple is initialized from the value of the pineappple.home.dir system property which defines the Pineapple Home directory. The directory defines the location where Pineapple will look for its configuration files, modules and write reports to.
Initialization of the home directory is done by PineappleHomeInitializer context listener as part of the initialization of the web application.
Two use cases for distribution of the web application are supported:
When the web application is distributed as a stand alone client, then the starter scripts (e.g. runPineapple.cmd and runPineapple.sh) sets the Pineapple Home directory. In this case the initializer doesn't do any initialization.
When the web application is distributed as a deployable web application, the no assumption regarding the state of the pineappple.home.dir system property should be made since it would require the user to set the system property prior to using the web application. In this case the value is read from the web.xml and used to the system property.
The web application uses Log4j for logging. The Log4j configuration file is located in src/main/webapp/WEB-INF/log4j/log4j.properties.
The Log4j configuration file is read by the com.alpha.pineapple.web.logging.LoggingInitializer class which is configured in the web.xml deployment descriptor.
The file configures logging for ALL the pineapple components / jars which constitutes the web application.
The configuration file configures Log4j to write log files to:
The main application context file for the application is located at src/main/webapp/WEB-INF/spring/webapp-config.xml.
The application context file defines:
The application context file for Spring web MVC is located at src/main/webapp/WEB-INF/spring/mvc-config.xml.
The application context file defines:
<mvc:view-controller path="/index" view-name="workspace"/> <mvc:view-controller path="/workspace" view-name="workspace"/> <mvc:view-controller path="/mvc/workspace" view-name="workspace"/> <mvc:view-controller path="/pineapple" view-name="workspace"/> <mvc:view-controller path="/error" view-name="error"/>
The application context file for services used by the MVC controllers is located at src/main/webapp/WEB-INF/spring/service-config.xml.
The application context file defines:
The GUI widgets are defined by .zul files located in the src/main/webapp/WEB-INF/jsp directory.
For each .zul file either a view model (MVVM)class and/or a ZK controller class exists which implements the logic (e.g. event code) of the widgets. The ZK view model classes are located in the package com.alpha.pineapple.web.zk.viewmodel. The ZK controller classes are located in the package com.alpha.pineapple.web.zk.controller.
The web application is configured with a Maven dependency to the Servlet API version 3.1.0 (see PINEAPPLE-715/6) with the provided scope:
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency>
The version is managed from the main project pom.xml.
The application doesn't use JSTL and contains no dependencies to JSTL (since resolution of PINEAPPLE-145).
The report generator uses internationalization. Messages used by classes in the project are located in the file src/main/resources/com.alpha.pineapple.web-messages.properties.
The module panel implements integration of the ACE editor to support remote editing of model files.
This diagram illustrates the components constitutes the integration between ZK and ACE:
The participating files are:
This diagram illustrates the call sequence when a document is saved at the server side:
The sequence for closing a document (a module model) corresponds to the sequence for openning a document:
The REST API is configured and implemented using Spring Web MVC.
The API is implemented in the package com.alpha.pineapple.web.spring.rest. The classes are annotated as Spring @Controller classes and picked up by component scan of the package during application context initialization. Furthermore, configuration-wise a multipart resolver has been configured to support multipart file upload by the web services.
The REST web services are implemented using the Spring annotations for request mapping etc. The web services are implemented to return the result using the @ResponseBody annotation letting Spring handle the marshalling of the returned model objects by the services.
Currently the default environment is used.
The default environment is configured to use a ThreadPoolExecutorDispatcher which uses a ThreadPoolExecutor with an unbounded queue to dispatch events. Reactor in its default configuration implements asynchronous event processing.
Registration of Reactor consumers is implemented by the WebAppCoreFactory:
public PineappleCore createCore() throws CoreException { webAppReactor.getConsumerRegistry().clear(); webAppReactor.on($(REACTOR_TOPIC_SERVICE_NOTIFICATION), createReport); webAppReactor.on($(REACTOR_TOPIC_SERVICE_NOTIFICATION), resultNotificationNotifier); webAppReactor.on($(REACTOR_TOPIC_SERVICE_CREATED_REPORT), createdReportNotification); webAppReactor.on(T(OpenModuleActivity.class), openModuleActivityInvoker); webAppReactor.on(T(ExecuteOperationActivity.class), executeOperationActivityInvoker); // create core ResultListener[] listeners = { webAppResultListener }; PineappleCore coreComponent = coreFactory.createCore(listeners); return coreComponent; }
These topics are used:
The topic /webapp/service/execution-result-notification is used to stream converted Pineapple events as Reactor events. The events posted on this topic are wrapped ExecutionResultNotification instances. The events are produced by the result listener WebAppResultListenerImpl.
The topic /webapp/service/created-report is used to signal that a report has been generated. The events posted on this topic are wrapped String instances. The events are produced by the Reactor consumer / report generator CreateReportImpl.
The topic OpenModuleActivity.class is used to signal that a open module activity should be performed again. The events posted on this topic are wrapped OpenModuleActivity instances. The events are produced by the ZK view model HomePanel for the home tab.
The topic ExecuteOperationActivty.class is used to signal that a execute operation activity should be performed again. The events posted on this topic are wrapped ExecuteOperationActivity instances. The events are produced by the ZK view model HomePanel for the home tab.
These consumers are registered:
Dispatch of Reactor events as ZK events is implemented to support notification of the GUI of events in the non GUI layer of the web application which should be reflected in the GUI.
A ZK event dispatcher is used by a Reactor consumer who wants to post to a ZK event. A Reactor consumer is by nature non-graphical and has no reference to the GUI. The ZK event dispatcher is a class* which perform a double registration when a ZK desktop is created. The event dispatcher registers itself with the desktop to allow for unregistration when the desktop is destroyed. Furthermore the dispatcher registers the ZK desktop to keep record of all known desktop’s (i.e. live GUI’s). The purpose is to have a defined end point where ZK events can be posted to.
The event dispatcher will dispatch events to all registered desktops. A registered desktop is unregistered when the desktop is destroyed.
The interface EventDispatcher is used to implement an event dispatcher The default implementation of the interface is EventDispatcherImpl.
To enable a Reactor consumer to dispatch a ZK events it must implement two interfaces:
The Reactor interface receives a Reactor event in the accept method.
The received Reactor event is transformed into a ZK event which is dispatched to this class as ZK a event listener. The purpose is to shift from the Reactor thread into ZK controlled threads. The received ZK event is posted as global command with the either the ZK application or session scope. The purpose is to notify the target view model (e.g. the reort panel or the execution panel) that something relevant happened and the view should be updated.
The order of initialization is:
The PineappleHomeInitializer is initialized first since it sets the pineappple.home.dir system property which is used by log configuration initialized by Log4jInitializer and the Pineapple core component defined in the Spring application context initialized by ContextLoaderListener.
The package com.alpha.pineapple.web contains a factory for creation of the core component:
The package com.alpha.pineapple.web.context contains servlet context listeners which is used to initialize the web application:
The purpose of the initializer is to support the two use cases for distribution of the web application mentioned above.
The directory is initialized using the algorithm:
The package com.alpha.pineapple.web.model contains:
The package com.alpha.pineapple.web.account contains interfaces and classes for user account management:
The package com.alpha.pineapple.web.event contains interfaces and classes to support usage of Spring Reactor and mapping to ZK events:
The event dispatcher serves to maintain a set of live ZK desktop's which can be used by the the non GUI parts of the web application (e.g. Reactor consumers) as defined endpoints for posting notifications to the GUI.
The event dispatcher is used by ZK desktop's to register themselves (using the register(Desktop d) method) upon creation as a live GUI which allows the dispatcher to forward ZK events to it. The unregister(Desktop d) method is used upon destruction by the desktop to unregister itself and thus no longer serve as an end point for posting ZK events.
Finally the dispatchZkEvent(Event zkEvent, EventListener<Event> eventListener) method is used by Reactor consumers and other non GUI parts of the application to post ZK events to the GUI.
The class implements the Pineapple EventListener interface. The class is registered as a result listener with the Pineapple core component. The class is registered by the WebAppCoreFactory as part of the core component initialization. The class wraps received ExecutionResultNotification events as Reactor events, an event is posted as ExecutionResultNotification in Reactor. The Reactor events are sent to the web application Reactor instance.
The package com.alpha.pineapple.web.event.consumer contains classes which implements the Reactor Consumer interface:
The purpose of this class is to notify the GUI that an execution result has been produced by Pineapple core component. If the result is part of any user initiated execution then the execution panel view model should be updated.
The class Implements the ZK EventListener and the Reactor Consumer interfaces. The consumer subscribes to events from the /webapp/service/execution-result-notification topic. When a Reactor event is received then the EventDispatcher is invoked with the class as ZK a event listener.
The event dispatcher schedules the event listener as an asynchronous event if any ZK desktop exists. The purpose is to shift from the Reactor thread into ZK controlled threads.
When the event listener method is invoked with a ZK event then a global command is posted with the session scope to notify the execution panel view model that execution result is produced and that the view should be updated if the result is part of any user initiated execution.
The consumer subscribes to events from the /webapp/service/execution-result-notification topic.
If a received ExecutionResultNotification signals the conclusion of an operation then a report is generated using the HTML report generator which is feed the received ExecutionResultNotification as input.
When the report is generated then a created-report event is posted on the /webapp/service/created-report topic.
The purpose of this class is to notify the GUI that a new report has been generated and that all active GUI’s (for all users) should be updated to reflect the change.
The class Implements the ZK EventListener and the Reactor Consumer interfaces. The consumer subscribes to events from the /webapp/service/created-report topic. When a Reactor event is received then the EventDispatcher is invoked with the class as ZK a event listener.
The event dispatcher schedules the event listener as an asynchronous event if any ZK desktop exists. The purpose is to shift from the Reactor thread into ZK controlled threads.
When the event listener method is invoked with a ZK event then a global command is posted with the application scope to notify the report panel view model that report creation is completed and that the view should be updated.
The purpose of this class is to notify the GUI that an user activity should be performed again and that the GUI for the current user should be updated to reflect the change.
The class Implements the ZK EventListener and the Reactor Consumer interfaces. The consumer subscribes to events from the OpenModuleActivity.class topic. When a Reactor event is received then the EventDispatcher is invoked with the class as ZK a event listener.
The event dispatcher schedules the event listener as an asynchronous event if any ZK desktop exists. The purpose is to shift from the Reactor thread into ZK controlled threads.
When the event listener method is invoked with a ZK event then a global command is posted with the session scope to notify the module panel view model that it should load the selected module and that the view should be updated.
The purpose of this class is to notify the GUI that an user activity should be performed again and that the GUI for the current user should be updated to reflect the change.
The class Implements the ZK EventListener and the Reactor Consumer interfaces. The consumer subscribes to events from the ExecuteOperationActivity.class topic. When a Reactor event is received then the EventDispatcher is invoked with the class as ZK a event listener.
The event dispatcher schedules the event listener as an asynchronous event if any ZK desktop exists. The purpose is to shift from the Reactor thread into ZK controlled threads.
When the event listener method is invoked with a ZK event then a global command is posted with the session scope to notify the execute panel view model that it should execute the targeted module and environment with an operation and that the view should be updated.
The package com.alpha.pineapple.web.zk.viewmodel contains ZK view model classes which backs views implemented in .zul files:
The .zul file for the report panel, i.e. report-panel.zul is configured with a ZK Binder with application scope:
<window apply="org.zkoss.bind.BindComposer" binder="@init(queueName='pineapple-queue',queueScope='application')" viewModel="@id('vm') @init('com.alpha.pineapple.web.zk.viewmodel.ReportPanel')" hflex="1" vflex="1" border="none" >
The application scope is use to update all desktops when a report is created and the "completedReportCreation" global command is posted.
The .zul file for the activity panel, i.e. activities-panel-popup is configured with a ZK Binder with application scope:
<window apply="org.zkoss.bind.BindComposer" binder="@init(queueName='pineapple-queue',queueScope='application')" viewModel="@id('vm') @init('com.alpha.pineapple.web.zk.viewmodel.ActivityPanelPopup')" hflex="1" vflex="1" border="none" >
The application scope is use to update all desktops when an activity is created and the "completedActivityCreation" global command is posted.
The package com.alpha.pineapple.web.zk.controller contains ZK composer classes for views which requires additional functionality that can be provided by a view model. If a view requires data then a view model is implemented. As result the composer shouldn't store any data in them:
The package com.alpha.pineapple.web.zk.asynctask contains asynchronous tasks which supports execution of long running processes while updating the ZK GUI by scheduling ZK events.
The package com.alpha.pineapple.web.zk.asynctask.event contains ZK event classes which is used by asynchronous tasks to communicate updates to the ZK controllers.
The package com.alpha.pineapple.web.zk.asynctask.event.visitor contains interfaces for visitors which can receive events from asynchronous tasks:
The package com.alpha.pineapple.web.zk.lifecycle contains classes which implements the ZK lifecycle event listener interfaces:
The class is registered as ZK event listener in zk.xml. The class implements a single method cleanup(Desktop desktop) which is invoked when a ZK desktop is destroyed. The listener performs the actions:
The package com.alpha.pineapple.web.zk.converter contains ZK converters which can convert data object into formats which can be rendered by views.
The package com.alpha.pineapple.web.spring.rest implements the REST web services:
The project is set up to use the Jetty-Maven-plugin for rapid development and testing. To run the application in Jetty invoke, the Maven command:
mvn jetty:run
The plugin will start the Jetty server with the application deployed with these parameters:
The application URL is: http://localhost:8080
For more info about the Jetty Maven Plugin, visit: http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin
The Pineapple Home directory will be set to the value of the pineapple.home.dir parameter in the web.xml whose default value is ${user.home}/.pineapple.
The plugin is configured to use Log4j for logging. The Log4j configuration file is located in src/test/resources/log4j.properties.
The Log4j configuration file is read by the Commons Logging bridge in Jetty which is configured by the plugin to use Log4j as logging implementation.
The file configures logging for ALL the pineapple components / jars which constitutes the web application, including log output from Jetty.
The configuration file configures Log4j to write log files to:
The plugin is configured to set the System properties:
slf4j = false log4j.configuration= file:./target/classes/log4j.properties
..which disables logging to slf4j and configures where the plugin will look for a Log4j configuration. Furthermore, the plugin requires the dependencies to commons-logging, slf4j-jcl and log4j for the Log4j based logging to work.