The gsf-groovy project is a showcase of how a scripting language can be used to developer websites faster while still allowing for all the features of Sites.
Groovy was chosen because it integrates well with java, and has a java-like syntax, and it can be dynamically reloaded.
The integration with eclipse is explained in the eclipse integration document.
The groovy integration makes use of the Action framework. An Action is a class the implements business logic. The Action is either loaded by the Controller or the <gsf:root> jsp tag. The Action framework is based on the command pattern. It's role is to provide Model data as defined in the MVC design pattern.
The Action interface defines one method: public void handleRequest(ICS ics).
The Action is tightly related to the Controller. If it is invoked by the Controller, it's role is to provide data, and select a View. A View is in most cases a (Layout) Template that will be rendered. If the Action is called from a Template, itś role is only to execute business logic for this specific template, not not to select the View. The Template is the View and needs to render the model data provided by the Action.
Below is a very simple hello-world like Action implementation, it just prints 'groovy was here' to the screen.
import COM.FutureTense.Interfaces.ICS class GTLayout implements Action { public void handleRequest(ICS ics){ ics.StreamText("<h1>groovy was here</h1>") } }
A Content Server web site is build by many different Action classes, as well as Templates and CSElements. The discovery of elements and Template is well know, for Actions a special discovery mechanism is implemented.
The Action is discovered by an ActionLocator class that is responsible for instantiating and configuring up the right Action based on the current request or JSP page. This means that the ActionLocator discovers the ICS object in assigns the correct Action to the current request, based on whatever data is available on that object. This could be a special variable name, the current element name, or a name provided by the jsp tag <jsp:root>. The Action locator is also responsible for injecting the declared dependencies on the Action. This can be done via the spring framework and/or via @InjectForRequest and @Mapping annotations on the class.
A public field annotated with @InjectForRequest on an Action class will be injected by the ActionLocator. For instance a TemplateAssetAccess service can be injected. The developer does not need to know how the TemplateAssetAccess was created, the framework provide it to him.
Here is an example:
@InjectForRequest public TemplateAssetAccess assetDao; @InjectForRequest public Model model; @InjectForRequest public ICS ics;
Depending on how the ActionLocator is configured it might be double dipping; dependency injection can happen first by the spring framework and secondly with InjectForRequest. All the services that are not bound to the request lifecycle could be injected by spring and services that need access to ICS, like asset api calls, are injected by the InjectForRequest annotation.
There are two objects that are special for the @InjectForRequest annotation handling framework; the ICS object and the Model object. The ICS object can be made available as a class field, so it can be used in methods on the Action implementation classes without passing the ICS object explicitly as a method parameter when the method is called from the handleRequest method.
The Model object is an output field for the action. A Model is a name/value pairs data structure. The Action can populate the model data so it can be used by the jsp element, for instance to display the asset data, or write a link to another asset. The Model data is copied by the <gsf:root> tag to the jsp page scope, and by that means made avialable to the jsp developer.
The is a special form of dependency injection, and this is not for services but for CSElement and Template Mapping data. Where in traditional Template code the <render:lookup/> tag is used to retrieve Template and CSElement Mapping values, now the @Mapping annotation can be used. The field object type can be of 3 classes, a String, a AssetId or an AssetName, depending on the Mapping type, or MappingValue independent of the type.
@Mapping("Detail") public String detail @Mapping("StyleSheetReco") public AssetId stylesheetId; @Mapping("Filter") public AssetName filter @Mapping("Foo") public MappingValue bar
Mapping annotation also accepts a match attribute, for instance @Mapping(value="ImageFileAttrName", match=Match.right), analogous to match=":x" or match="x:" in the render:lookup tag. The match=Match.right is to get the right side of the Mapping for ImageFileAttrName, as Asset (Type:Name) mapping with value Media_A:FSII_ImageFile. In this case FSII_ImageFile is injected. An alternative for this construct would be @Mapping("ImageFileAttrName") public AssetName ImageFileAttrName. In this case both the Media_A and the FSII_ImageFile are accessable. The action code would then be ImageFileAttrName.getName(); The latter construct is a more natural style as it is close to the ContentServer mapping styles.
The Model class is the name/value pairs data structure to transport data from the Action to the element, analogous to from Controller to View in MVC frameworks. The names must be Strings and the value can be any object. This can be a string, a collection of strings, or any object that tje JSP expression language can process, like Maps and java beans.
The model has three important methods:
public void add(String key, Object value) {... } public void add(String key, Object... value) {... } public void list(String key, Object value) {... }
The first add method just adds a name/value pair to the model, replacing the current value if it already existed.
The second method, with the value array, is adding all the values as a Collection to the key name.
The third method, list, adds the current key/value pair to the Collection idenfied by key, or creates a new Collection if one did not exists or was not a Collection.
The Asset Api test file shows a jsp file that uses the Groovy Action to load and display an asset.
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld" %><%@ taglib prefix="gsf" uri="http://gst.fatwire.com/foundation/tags/gsf" %><cs:ftcs> <gsf:root action="test/AssetApi"> Hello World. </gsf:root> </cs:ftcs>
The jsp page starts of the the traditional <cs:ftcs> tag, followed by the <gsf:root> tag. Inside the gsf:root tag should the normal jsp code placed. The ftcs tag sets the ics variable on the jsp page, as well as some output stream handling. The gsf:root tags discovers the Action based on the action attribute value via the spring configured ActionLocator. The Action can be implemented in java or as in this example in Groovy.
As you can see, the Groovy files look a lot like a normal java class. It start with package declaration, imports, javadoc and then the class definition. Inside the handleRequest method is the business logic defined. In this example the logic is simple, it finds the current asset based on c/cid ics variables, and prints the AssetData via the DebugHelper class directly to the output stream. This direct writing to the stream is not a textbook example of best practice, as this should be done in the jsp page, but it does show a simple example. Also the error handling is rudimentary and too simple for real applications.
package test; import COM.FutureTense.Interfaces.ICS import com.fatwire.assetapi.common.AssetAccessException import com.fatwire.assetapi.data.AssetData import com.fatwire.assetapi.data.AssetId import com.fatwire.gst.foundation.DebugHelper import com.fatwire.gst.foundation.controller.action.Action import com.fatwire.gst.foundation.facade.assetapi.AssetDataUtils import com.fatwire.gst.foundation.facade.assetapi.AssetIdUtils import com.openmarket.xcelerate.asset.AssetIdImpl /** * Class that implements a call to AssetAPI and prints the AssetData. * */ public class AssetApi implements Action { @Override public void handleRequest(ICS ics) { AssetId id= AssetIdUtils.currentId(ics); long t = java.lang.System.nanoTime() AssetData data =AssetDataUtils.getAssetData(ics,id) long t2 = java.lang.System.nanoTime() ics.StreamText("Reading asset took " +DebugHelper.nanoToHuman(t2-t) + ".<br/>"); String s; try { s = DebugHelper.printAsset(data); ics.StreamText("<pre>"); ics.StreamText(s); ics.StreamText("</pre>"); } catch (AssetAccessException e) { e.printStackTrace(); } } }
The same Action can be coded in Java and compiled and deployed into the webapp. The action can be called by it's classname.
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld" %><%@ taglib prefix="gsf" uri="http://gst.fatwire.com/foundation/tags/gsf" %><cs:ftcs> <gsf:root action="class:com.fatwire.sample.AssetApi"> Hello World. </gsf:root> </cs:ftcs>
To enable this, a ClassActionLocator must be configured. If the default groovy support is enabled (see at the Configuration section), this ClassActionLocator is enabled also.
In case you as a site developer want to access Actions, you will need to do so by name. There are several ways to specify a name, and the methods differ on the place where the action is used.
The first use case is the one where the Action is used in a Template or CSElement. In this case the action parameter in the gsf:root tag is letting the framework know what action to use. There are several ways to map an action name to an actual implementation.
In case the action is called from the Controller, tby default the GST/Dispatcher element, the same lookup mechanisme is used, but this method is topped with a configurable way to extract a action name from the runtime environement, ie the servlet request. By default a request parameter with the name action is used, for instance action=login. If no argument is provided, a fall-back action is used, by default is that the com.fatwire.gst.foundation.controller.action.RenderPage Action.
Below are some more realistic code samples. The following code samples are for a copied version of FirstSiteII. The SiteLauncher tools was used to copy the Template, SiteEntry, CSElement and Page assettypes from the FSII site to a new site called Test, with prefix GT. The other assettypes were shared.
JSP GTLayout template groovy GTLayout
JSP StyleSheetResolver groovy StyleSheetResolver
JSP ExecuteFilter groovy ExecuteFilter
JSP Media_C Detail and JSP Media_C Summary making both use of groovy Media_C Detail
JSP Page Top Nav groovy Page top nav
JSP Product Parent Detail groovy Product Parent Detail
GSF has a way to set up the View layer to make calls to Template, CSElement and SiteEntries from the Action. This is done with help of the IncludeService. This service is a facade over CallTemplate, CallElement, render:satellitepage and render:contentserver. In the Action you set up the various includes and give them a name so they can be identified in the template. The name is similar to the slotname in CallTemplate.
In the jsp this is written to include a block. As you can see the jsp developer does not need to know if this is a CSElement or Template, or if it should be called as a pagelet, embedded or as an element. That is all done in the Action.
<cs:ftcs> <gsf:root action="GTLayout"> <head> <p:include name="Head" /> </head> [ rest of the page truncated for clarity] </gsf:root> </cs:ftcs>
In the groovy Action the code is as follows:
public void handleRequest(ICS ics){ //global call, include the element execution right now, by invoking include(ics) includeService.element ("Filter", filter.getName()).include ics AssetId pageId = assetDao.createAssetId("Page",ics.GetVar("p")) includeService.template("StyleSheetSlot", stylesheetId ,styleSheetResolver).element() includeService.template("Head",assetDao.currentId(),head); // p and locale are copied as part of pagecriteria includeService.template("TopNav", pageId,topNav) includeService.template("BannerSlot", bannerList,bannerTemplate).element() includeService.template("SideNav", assetDao.currentId(),sideNav).element() includeService.template("Detail", assetDao.currentId(),detail).pagelet() includeService.template("BottomNav", pageId,bottomNav).embedded() }
As you can see one CallElement is issued immediately. This is because that element is changing the current state and needs to be called inline. With better java and groovy support we suspect that the use of CSElements to execute business logic is drasticly reduced.
The other include calls are all for CallTemplate. The first argument is always the name of the slot. This name is used to reference the block in the jsp element. As Template are used to render an asset, the second argument is a assetId, and the third the template name. Other arguments are implictly copied if they are part of the page criteria, for instance p and locale. the methods element(), pagelet() and embedded() indicate the call Style.
Important is the remember that all CallTemplate calls are done in the jsp executing and not here. This allows for the jsp page code to move te blocks around without impacting the business logic.
For links and blobs the is also added support. There are two classes that help with populating image and anchors tags in the jsp element. Here is a sample for the FSII Layout and Media_C/Detail template.
Img img = new Img(); img.setSrc (ics.GetProperty("ft.cgipath") + ics.GetVar("site") +"/images/PoweredByFatWire.gif") img.setAlt ("Powered by FatWire Software") model.add("PoweredBy",img) BlobUriBuilder ub = new BlobUriBuilder(asset.asBlob(ImageFileAttrName)); ub.mimeType(asset.asString(ImageMimeTypeAttrName)) Img img = new Img(); img.setSrc(ub.toURI(ics)); img.setWidth asset.asString(ImageWidthAttrName) img.setHeight asset.asString(ImageHeightAttrName) String alt = asset.asString(AltTextAttrName); if(StringUtils.isBlank(alt)){ alt="Content Server Image" } img.setAlt alt model.add("image",img);
and in the jsp element is written:
<div id="PoweredBy"> <img src="${PoweredBy.src}" alt="${PoweredBy.alt}" /> </div> <c:if test="${!empty image.src}"> <img src="<string:stream value="${image.src}" />" class="ImageDetail" width="${image.width}" height="${image.height}" alt="${image.alt}" /> </c:if>
In case as a developer you have the requirement to add additional services to the injection framework, or when you want to change the default service factories you will need to extend the com.fatwire.gst.foundation.controller.support.WebAppContext class on configure.
The Action framework needs a couple of services to work:
If you want to change any of the services you will need to subclass and configure. How configuration is done is shown in the next section.
For implementing your own AppContext it is important to understand that the WebAppContext makes use of reflection to find the service you ask for. If a developer would ask for getBean("foo", com.x.foo.Bar), the WebAppContext looks for a method called createBar() that returns a com.x.foo.Bar class or interface. If such a method does not exist, it will check in the parent AppContext if it can load a bean by calling getBean() on the parent. If the parent does not return a bean, reflection is use to see if a the class can be contructed with a default constructor. This is only possible for concrete classes.
In the create... methods the developer has precise control over how the classes are created and initialized.
This construct is choosen because it gives the exact programmatic control over the class construction when that is needed, and it also provides access to classes that depend on ICS and are bound to the ICS lifecycle. Alternatives as Spring or Guice have been found not to function (easily) in this context, mainly due to scoping issues around the ICS based services. We believe that the direct programmatic control of the factories over an XML based configuration tool is preferred.
If the gst-foundation-all jar is deployed into the WEB-INF/lib directory the default Action support is enabled.
If you want to change the default behaviour you will need to configure the Action framework. This can be done explicitly through a servlet context listener in web.xml or through a file that is loaded from the class path.
To web.xml you will need to add.
<listener> <listener-class>com.fatwire.gst.foundation.controller.support.WebAppContextLoader</listener-class> </listener>
This listener configures the WebAppContext, which is the main class for Dependency Injection of various services, such as the ActionLocator and the services Factory.
The listener will detect if the Groovy library is available, as well as the WEB-INF/gsf-groovy folder. If both preconditions are met, the action framework is configure to use Grrovy Actions.
By default it will configure a com.fatwire.gst.foundation.controller.action.support.IcsBackedObjectFactoryTemplate for a services factory, as well as a ActionNameResolver based on a ics variable called 'action', as well as actions in the form of 'class:class implementing Action'. The default action is a RenderPage Action.
If a servlet context init-paramater with the name gsf-contexts is set with a value of a comma seperated list of classname this list of classes will be used as a tree of AppContexts.
<context-param> <param-name>gsf-contexts</param-name> <param-value>com.foo.x.MyAppContext</param-value> </context-param>
If a file on the classpath at the location META-INF/gsf-contexts is found this will be used to configure the AppContexts. This file shold contain a list of classes. Each of these classes need to implement the AppContext interface and needs to have a constructor with the arguments ServletContext.class, AppContext.class. The last line contains the root, and the first line the youngest generation child.
Alternatively can the action framework and the actions configured via Spring framework.
For this the WebAppContext needs to be configured to use SpringWebAppContext.
In the web.xml file is a spring context file added, Spring context file