Chapter 12. Understanding the Surf Framework

WHAT'S IN THIS CHAPTER?

  • Introducing Spring Surf

  • Understanding the general concepts for scriptable Web applications

  • Viewing Spring Surf examples

Surf is a scriptable Web framework that powers the presentation-tier rendering capabilities of applications in the Alfresco application suite. These include Alfresco Share, Alfresco Records Management, and both authoring and presentation tools for Alfresco Web Content Management.

This chapter will guide you to an understanding of building Web applications using Spring Surf. It begins by covering some of the essential concepts behind a content-driven design and then moves on to discuss the architecture. It covers Spring Web MVC (Model-View-Controller) and describes how this pattern facilitates the design and delivery of Web pages. The chapter includes code and examples that help to tie all of the concepts together.

The material presented in this chapter is applicable to any Spring Surf application. You could use it to build your own. That said, you're going to use this material starting in Chapter 15 as you extend Alfresco Share.

WHAT IS SURF?

Surf provides a way for you to build user interfaces for your Web applications using server-side scripts and templates. No Java coding, no recompilation, no server restarts, and no heavy lifting.

The result is reduced complexity and lower costs. Surf follows a content-driven approach – everything is content on disk. Scripts and templates are just simple files on disk. You can just open up a text editor and begin making changes to the live site.

Surf is a view composition plug-in for Spring Web MVC. More specifically, Surf is a Spring Framework extension that you can use to build new Spring Framework applications, or you can plug it into existing Spring Web MVC applications in your business. Spring Web MVC provides an elegant separation between the application's model, view, and controller – thus the acronym "MVC." If you wish, you can use Surf side-by-side with other popular Spring Web MVC technologies, including Tiles, Grails, and Web Flow.

Surf provides a simple object model that allows you to define pages, templates, components, themes, and more. It's all just simple XML content on disk. Your Spring application picks up the new files and processes them through your scripts and your templates to produce the view. Scripts are written using server-side JavaScript and Groovy. Templates are written using FreeMarker and PHP.

Using Surf, you can build both page-centric and content-centric Web sites. Surf provides out-of-the-box support for rendering content delivered through content delivery services, such as CMIS, Atom, and RSS.

Surf provides many features for Web site developers. Among these are:

  • Scripts and templates—Everything in Surf consists of scripts, templates, or configuration. This means no server restarts and no compilation. Surf developers benefit by faster iteration.

  • Reusability—Surf's presentation objects, templates, and scripts emphasize reusability. Scoped regions and component bindings allow developers to describe presentation with much less code.

  • Spring Web MVC—Surf plugs in as a view resolver for Spring Web MVC, which enables Spring application developers to use Surf for all or part of their site's view resolution. Surf renders views on top of annotated controllers and is plug-compatible with Spring Web Flow, Spring Security, Spring Roo, and Spring tag libraries.

  • RESTful scripts and templates—All page elements and remote interfaces are delivered through a RESTful API. The full feature set of Web scripts is available to Surf applications. You can write new remote interfaces or new portlets with just a script, a template, and a configuration file.

  • Content management—Surf provides a set of client libraries and out-of-the-box components to streamline interoperability with CMIS content management systems. Enterprise content is easily accessed and presented using Surf components and templates.

  • Two-tier mashup architecture—Surf is designed to work in a decoupled architecture where the presentation tier is separate from the content services tier. Surf provides credential management and mashup mechanics so that the rendered page can surface content from multiple back-end data providers. These might be CMIS, Atom, SOAP, XML, or JSON feeds. Surf builds pages, serves them, and caches for performance optimization.

  • Production, development, and staging/preview—Surf can be configured to work in a number of deployment scenarios, including development, preview, or production environments.

  • Development tools—Surf provides development tools that plug into the SpringSource suite of development tools. These include Eclipse add-ons for SpringSource Tool Suite as well as Spring Roo plug-ins to enable scaffolding and script-driven site generation. Developers can build an entire Web site with just a few commands.

BASIC CONCEPTS

One of the core aspects of Surf is the "no-compile" philosophy. Everything is just content or files on disk. You can change them and they will be picked up by the server right away.

In this section, you'll look more closely at this content and investigate how it is used by Surf to generate user interfaces for your Web site. In doing so, you'll distinguish between semantic and presentation content and describe how Surf's dual-tier architecture provides advantages for both. Finally, you'll look at Spring Web MVC and dig into the details of how Surf plugs in as a view resolver.

Content

Talking about content here refers to dynamic information or data that the Web application looks to at runtime to inform its decision about what to render or how to render. It may change between requests and the Web application is expected to behave accordingly.

Information that comes from a real-time feed (like an RSS feed) would fall into this category. Such content describes what to render into the user interface; for example, news articles. However, things like Java code or Spring context files are not dynamic content since they are resolved once (at compile time for Java code and during application context startup for Spring context files). The server must be restarted for any changes to be picked up.

A Spring Surf Web application architect concerns themselves with two kinds of dynamic content: semantic content and presentation content. Together, these inform Surf of what to render as well as how to render.

Semantic content consists of documents or files that describe business-generated content. This content is authored, approved, and published. It arrives to the Web site, and it is then the duty of the Web site to figure out what to do with it.

The following items are all examples of semantic content:

  • An approved press release for display on the home page

  • A Knowledge Base article that has been tagged and appears on several different pages of the Web site

  • A product inventory descriptor that includes images, thumbnails, and references to product documentation

You can think of semantic content as information that describes what should be rendered. It contains the approved message but it does not contain any formatting information. It is represented in a structured, pure data format such as JSON or XML. Consider the following JSON text for a biography:

{
  "author": "Pablo Neruda",
  "country": "Chile",
  "image": "pablo_neruda.jpg",
  "description": "Pablo Neruda is adored in South America for his romantic prose.",
  "popular_works": ["Cien sonetos de amor", "Confieso que he vivido"]
}

Notice that this doesn't contain any formatting. No HTML tags or other markup. Just simple data. It isn't pretty, to be sure, but it does contain all of the right information – the factual bits that the Web site consumer ultimately wants.

You publish this semantic content to a Web site and then let the Web site managers figure out how they want to display it. You may publish it to more than one Web site. Each Web site manager might choose to display the semantic content differently. You don't really care. All you need to know is that the correct and approved information is being worked with.

Presentation content consists of documents and files that describe presentation configuration for a Web site. Presentation content consists of configuration that informs the Surf rendering engine how the Web page or page component should look and feel.

Presentation content answers questions like:

  • Which theme should be used to render the Web site's home page?

  • How many articles should I display on the front page?

  • Which advertisement should I display in this section of the site for the current user?

You can think of presentation content as information that is specific to the Web site user interface. It answers the question of how things should be rendered. A Web site presentation framework (such as Surf) looks to this content to figure out how to do its thing.

Consider the following XML that configures a Surf Web script responsible for rendering a biography to the end user. It tells the Web script to render a link to the full article as well as to show the image of the author. Don't worry about the XML syntax yet – you'll cover that later!

<component>
   <url>/content/display/biography</url>
   <properties>
<link-title-to-full-article>true</link-title-to-full-article>
      <show-image>true</show-image>
   </properties>
</component>

This XML tells the Web script how to render the biography. The end-to-end rendering flow looks something like what is shown in Figure 12-1.

Figure 12-1 depicts a simple request for a biography of Chile's famous poet. Here is what happens.

  1. The browser request arrives to Spring Surf.

  2. Surf asks the content delivery services for the presentation content that describes what is being requested.

  3. The presentation content is handed back as XML.

  4. Surf determines the Web script to execute and does so using the configuration specified by the XML.

  5. The Web script calls over to the content delivery services and asks for biography data.

  6. The biography is returned as JSON.

  7. The Web script renders HTML markup to the end user. This HTML contains presentation output (formatting) as well as the semantic data (the biography itself).

FIGURE 12-1

Figure 12.1. FIGURE 12-1

The Web site user is then presented with the poetic expressions of Pablo Neruda. In the sections that follow, you'll take a closer look at the content delivery services and investigate how the Alfresco Content Application Server can be used to deliver these services for your Spring applications.

Content Delivery Services

Surf connects to content delivery services to provide content retrieval and query for both presentation and semantic content. This means that Surf applications can consist of either a single-tier or dual-tier application, as shown in Figure 12-2.

Figure 12-2 shows three valid configurations for Surf. In the standalone configuration on the lefthand side, all of the presentation and semantic content is stored as part of the Surf Web application. It is self-contained and has everything that it needs so that requests can be services entirely from the Web application in the presentation tier. This is a perfectly acceptable configuration. Surf imposes no requirements here; it does not require a database, any local persistence, or even a user session.

FIGURE 12-2

Figure 12.2. FIGURE 12-2

On the other hand, a far more interesting scenario is shown in the full content services configuration on the right-hand side. Here, the Surf application lives in the presentation tier but relies upon delivery services in the content services tier to hand it data so that it can respond to incoming This data consists of things like the biography or Web script configuration information.

In this case, Surf provides developers with connector and credential management so that interactions with the content delivery services can be performed on behalf of the end user. Surf provisions connectors to Web script developers so that they can retrieve feeds of data. This data is often represented in either JSON or XML, but it could also be in any number of other formats. Developers work with these feeds and render them back through view templates. Surf focuses on view caching and render performance to minimize the number of remote calls needed on each request.

Surf also has native support for the CMIS standard (Content Management Interoperability Services). CMIS is an industry-adopted API for talking to ECM systems. Alfresco ECM provides the leading open source CMIS implementation, as well as an entire suite of tools around CMIS authoring and delivery. Surf is an ideal presentation technology for CMIS content delivery.

Surf developers benefit from having the option to independently scale out the presentation tier from the content services tier. The presentation tier is primarily developed with the intention of scaling to user load (while providing quick end-user responses), whereas the content services tier scales to content retrieval and query. A single page hit to the Surf application could result in several content services hits, a single hit, or no hits at all. It depends on how you design your application.

Content Applications

In the previous section, you looked at Surf's split-tier architecture and explored some of the advantages of having a separate content services tier built around content standards, such as the Content Management Interoperability Services (CMIS). In this section, you'll explore the big picture about how Surf fits into Alfresco's Content Application vision for Web Content Management.

Surf's intended design pattern is such that it maintains a content-driven and scriptable approach to Web application delivery. As a result of being so content-centric, Surf interoperates very well with Alfresco Web Content Management. Alfresco provides facilities to define, contribute, and manage lifecycle for both semantic and presentation content. It provides a number of very robust WCM capabilities around your content, such as:

  • Managed lifecycle from origination to disposition

  • Object- or attribute-level authority for users, groups, or roles

  • Deployment of content from an approved environment out to all of your Surf applications in the delivery tier

  • Rendering of content into alternative delivery formats

Alfresco Web Content Management allows you to define content lifecycle for your Surf application's semantic and presentation content. It allows you to author content in an authoring environment and publish that content out for consumption to a delivery environment. This is shown in Figure 12-3.

FIGURE 12-3

Figure 12.3. FIGURE 12-3

In the previous section, you looked at a full content services configuration for Surf. This is what you see in Figure 12-3 on the right-hand side. This is your live Web site. All of the content for your site (both semantic and presentation) is retrieved from the live Web site's content delivery services. This Web site runs in the production environment. The users of this Web site are actual live Web traffic users. The production environment is therefore sized and configured to scale for your live Web traffic needs.

Figure 12-3 also shows the authoring environment. The authoring environment consists of a preview Web site and Alfresco Share. Alfresco Share is Alfresco's Web interface to the Content Application Server. Alfresco Share is used to author content and save it into the Content Application Server.

The gray arrows along the bottom indicate deployment. Alfresco Web Content Management provides deployment for your Surf presentation objects as well as your semantic content. Content contributors can create content using Alfresco Share and the Alfresco Content Server. They can then instantly preview their content so as to get an idea of how it might look if it were part of the actual Web site.

In Figure 12-3, a contributor has written the biography for Pablo Neruda and then elected to preview it. The content has been published to the content delivery services for the preview Web site. Surf picks up these changes automatically and shows the contributor what the biography looks like in context with the rest of the page. It only shows this on the preview Web site. The live Web site is not affected by the preview.

If the contributor enabled Surf's in-context editing features, they will be able to make changes to the content right on the Web site. If not, they can always make changes to the content within the Content Application Server using Alfresco Share. When they are happy with the content, they can deploy it to the production environment.

You can configure this any number of ways. Figure 12-3 simply serves to illustrate the concept. Deployment transports the content from the Content Application Server to the content delivery services for a Web site. This is usually done for integration testing, quality assurance, preview, and ultimately for delivery to a live Web site.

The Alfresco authoring facilities are ideal for working with both semantic and presentation content. They include Alfresco Share, the Alfresco Forms Service for content contribution, CIFS for desktop and file system interaction, and Microsoft Office/SharePoint integration. Alfresco lets you manage semantic and presentation content object models, permissions, and behaviors, and gives you a rich set of services around them, including workflow and auditing.

While the Alfresco content delivery services are excellent for Spring Surf, they're also not proprietary to Spring Surf. In fact, they are Web framework-agnostic. They provide a CMIS interface to the outside world. As an open standard, CMIS should work equally well across many platforms. You can use CMIS to support Web applications that are built in Java, PHP, .NET, Python, and more.

Spring Surf gives Java developers a head start by working very nicely in this kind of content delivery architecture. Surf provides a strong open source option for getting started today. The Alfresco Content Application Server works hand-in-hand with Spring Surf to manage and deploy both presentation and semantic content to Spring Framework Web sites.

PUTTING IT INTO ACTION

With all of that theoretical stuff behind you, now you can get down into the details of how Surf works. In this section, you'll review the basic ideas behind MVC and then take a closer look at Spring Web MVC. This will set the stage for understanding how the Surf page dispatcher works.

You don't need to type in any code in this section. Just follow along and enjoy. You'll get hands-on experience with the examples in Chapter 14.

Plug-in for the Spring Framework

Surf snaps into the Spring Framework as a view composition plug-in for Spring Web MVC. Spring Web MVC allows developers to build Web applications in a highly configurable way where the view rendering technology can be plugged in and out. The Spring Framework has many plug-in view resolver technologies available for it, including JSP, Tiles, Grails and, of course, Spring Surf. Spring Surf is a formal Spring Extension Project that was developed as a collaboration between Alfresco and SpringSource.

Spring application architects can mix and match these technologies as they see fit. Surf provides a scriptable, no-compile alternative that quickens the pace of building Web applications by allowing for faster iteration and fewer server restarts. Surf provides several tools for developers to help ease this process:

  • The Spring Roo Add-on for Surf gives developers additional commands that they can use to quickly install, configure, and scaffold Surf applications. With a few simple commands, developers can create new pages and Web scripts for their applications.

  • The SpringSource Tool Suite Plug-in for Surf gives developers additional wizards and templates for the SpringSource Eclipse-based IDE environment. These wizards let developers configure and build new Surf applications from the ground up.

  • Spring Travel and Spring Petclinic Sample Sites provide developers with quick-start reference applications that serve as a guide for building Surf applications.

Model-View-Controller

If you are a bit rusty on the Model-View-Controller pattern, this is your chance to brush up. Don't worry; extensive theses have been written on MVC, and you can refer to them if you really need details. This is a high-level refresher so that you can dig in a little deeper.

Imagine an application that receives requests from the outside world. In an MVC application, a dispatcher handles these requests for your application and then figures out what to do with them. The dispatcher is responsible for looking at the URL to determine the following:

  • Which controller to invoke (to set up a model)

  • Which view to invoke (to render the model)

It does so in that order. The dispatcher uses mappings (usually URL mappings) to figure out which controllers should be invoked for the incoming URL. It also uses mappings to figure out which view should be invoked to render the response back. This is illustrated in Figure 12-4.

Here is what happens.

  1. The request comes in and is handled by the dispatcher. Imagine that the incoming URI is /hotels.

  2. The dispatcher tries to find a matching controller for this URI.

  3. If a controller is found, it is invoked. The controller calls out to some services to retrieve a list of hotels. It creates a model and then places this list of hotels into the model.

  4. The dispatcher tries to find a matching view for this URI.

  5. If a view is found, it is invoked. The view receives the model and uses it to render HTML markup that displays the list of hotels.

  6. The response is sent back to the end user.

FIGURE 12-4

Figure 12.4. FIGURE 12-4

A controller contains business logic that should run before the response is generated. It can do things like query a database or call out to a service. Its job is to place this data into the model. A view contains rendering logic responsible for building the response that the end user receives. It looks to the data in the model to inform its rendering process.

The main benefit of the MVC pattern is that it makes a nice clear separation between the business logic and the rendering logic. This modularizes your application architecture. It allows you to plug in new views and new controllers. It gains you a lot in terms of reuse; many views can share a single controller, or many controllers can share a single view.

Spring Web MVC

Spring Web MVC is the Model-View-Controller implementation for Spring Framework Web applications. It is based on Spring configuration, where Java beans are used to implement controllers, views, the model, and even the mappings between URIs and handlers. One of the really nice things about Spring Web MVC is that it is very extensible. You can write your own Java code, plug in new beans, or rewire things through configuration.

The dispatcher for a Spring Web MVC application is the dispatcher servlet. The dispatcher servlet is responsible for handling the request and executing the MVC pattern. It tries to identify a controller that will handle the incoming request. A controller is a simple Plain Old Java Object (POJO) registered with the Spring Framework.

In the Spring Framework, the model is just a simple map of named properties. The controller may do some heavy lifting to compute these. Once the controller finishes, it hands the model back to the dispatcher servlet. So far, nothing has been rendered to the end user.

The dispatcher servlet then tries to determine a view that will be used to render the model to the end user. It consults a registry of view resolvers and asks each of them if they can handle the incoming request URI. If a matching view resolver is found, it is used to produce a view object. The view object renders the model to the end user.

Typically, Spring Web MVC application developers focus the majority of their effort writing controllers and views in Java. They then wire them together with Spring configuration XML. This is often a very code-intensive and inflexible effort.

Surf View Resolvers

Surf is a view resolver for your Spring Web MVC application. When you add it into your Spring Web MVC application, the dispatcher servlet will consider Surf's view resolvers just like any other view resolvers. It will ask whether Surf can provide a view object for the incoming request URI.

Unlike the code-intensive approaches, Surf's configuration is driven from presentation content. The Surf view resolver will look to the presentation content to figure out whether a page has been defined for the incoming URI. In fact, Surf knows how to render many types of views. These views are listed in the following table along with the names of the view resolver beans that produced each.

VIEW

VIEW RESOLVER BEAN

Page

PageViewResolver

Region of a Page

RegionViewResolver

Component on a Page

ComponentViewResolver

Content

ContentViewResolver

Web Script

WebScriptViewResolver

By default, Surf uses a simple URL file name controller to look up and discover what kind of thing is being requested. If the URI describes an object of one of the types listed, it will discover this, look up the object, and then inform the Spring MVC dispatcher that it can produce the view object.

Surf can also be used with custom controllers. Spring developers will frequently write custom controllers to back incoming requests. These controllers are either simple controllers or annotated controllers.

  • Simple controllers are Java beans that are wired to the incoming URI via a mapping declaration in a Spring configuration file. The Spring MVC framework looks to its configuration files to figure out which controller bean should handle the incoming URL.

  • Annotated controllers are Java beans that use Java Annotations to bind specific methods on the bean to the incoming URL.

Either one is fine. If the controller hands back an explicit ID, the dispatcher servlet will consult Surf's view resolvers to see if they can produce an appropriate view object.

With Surf, you are free to dig in and customize the wiring of the Spring Web MVC configuration. However, in most cases, the out-of-the-box configuration will suit your application just fine.

Examples

You'll walk through a few quick examples now. These are merely simple examples intended to illustrate some of the dispatching mechanics through Spring Web MVC. A little bit of pseudo-code is introduced to help show how things fit together.

You don't have to type anything in. Just see if you can follow along for the basic concepts.

A Surf Page

Imagine that a request arrives to a simple Spring Surf application. For the purposes of this example, imagine that the request takes on the following form:

http://localhost:8080/hotels

The Spring MVC dispatcher receives this request and tries to find a controller that matches the URI /hotels. In this case, it does not find a match. Thus, a controller is not found, nothing is invoked, and a model is not set up. No big deal.

The Spring MVC dispatcher then moves on to the next step. It tries to find a view resolver that can resolve views for the URI /hotels. It will walk through the available view resolvers and ask each one if it can handle this URI.

Since this is a very simple Surf application, each of the Surf view resolvers will be interrogated and asked whether they can resolve /hotels. The two most interesting resolvers are these:

  • PageViewResolver checks to see if there is a Surf Page object defined that maps to the URL /hotels. If so, it produces a PageView view object to render the response.

  • WebScriptViewResolver checks to see if there is a Web script defined that maps to the URL /hotels. If so, it produces a WebScriptView view object to render the response.

If a PageView is produced, Surf will render back the Hotels page. If a WebScriptView is produced, Surf will ask the Web script engine to render back the Web script matching the URI /hotels.

Suppose you want to have Surf render back a Hotels page. This page will list all of the hotels available on your Web site. You just have to define a page in Surf that maps to the /hotels URI. You can do this with a little bit of XML—something like this:

<?xml version='1.0' encoding='UTF-8'?>
<page>
   <id>hotels</id>
</page>

Easy enough. You'll go into where you place this XML fragment a bit later. However, for now, assume that Surf picks this up. It knows that there is a page object with the URI /hotels.

The only other thing you have to do is provide the template to use to render markup to the response. You can do this with a bit of FreeMarker. You can hard-code this for the moment, as shown here:

<html>
   <body>
      <table>
         <tr>
            <td>Walton Cottage</td>
            <td>Maidenhead, UK</td>
         </tr>
         <tr>
            <td>Victorian Treasure</td>
            <td>Lodi, WI</td>
         </tr>
      </table>
   </body>
</html>

Now when you hit the /hotels URI, the dispatcher walks through the view resolvers and settles on the PageViewResolver to work its magic. Your page will be rendered to the browser.

Using an Annotated Controller

As previously mentioned, Spring developers write controllers to build the model that your page ultimately uses. Take a look at how this is done.

Imagine that you want the page to display the hotels received from a query to a Hotels service. In other words, you do not want to just hard-code it as you did before. You would like to pull in content from a service that is an expert in all things hotel.

A Spring developer would typically approach this challenge by defining a new controller. With Spring 3.0, you can use annotated controllers to define a RESTful binding for the incoming request. You declare the annotated controller in its own Java bean. The code might look something like this:

@Controller
public class HotelsController
{
   privateTravelServicetravelService;
   @Autowired
   publicHotelsController(TravelServicetravelService) {
     this.travelService = travelService;
}
   @RequestMapping("/hotels")
   public void getHotels(SearchCriteria criteria, Model model) {
      List<Hotel> hotels = travelService.findHotels(criteria);
      model.addAttribute("hotelList", hotels);
   }
}

This Java bean is annotated to inform Spring that it should bind to the incoming URL of /hotels. Simple enough. When the Spring MVC dispatcher servlet needs to figure out which controller can handle the incoming URL, it will consult these annotations to find your bean.

As you can see by the code, all of the "heavy lifting" is done inside of the bean. It calls out to the travelService to retrieve a list of hotel properties. It then places these into the model and returns it. Thus, unlike in the last example, you're populating the model in the controller.

So far, this is all just Spring Web MVC. How would your Surf template use this model data? You can adjust your Surf template as follows:

<html>
   <body>
      <table>
      <#if hotelList?size == 0>
         <tr>
            <td colspan="2">No hotels found</td>
         </tr>
      <#else>
         <#list hotelList as hotel>
         <tr>
            <td>${hotel.name}</td>
            <td>${hotel.address}</td>
         </tr>
         </#list>
      </#if>
      </table>
   </body>
</html>

That's all there is to it. You used Spring Web MVC to define your controller. The model generated by the controller is available to your Surf template. You mapped your controller and page to the same /hotels URI and let the framework figure stuff out for you.

VIEW COMPOSITION

You just finished looking at a very simple example of a Surf page. It consisted of a little bit of XML configuration and a FreeMarker template. The XML identifies the URI where the page lives. This helps the Surf page view resolver find the page and ultimately find the FreeMarker template to use.

Surf provides a view composition framework that enables much richer user interface definition. In this section, you'll look at how this works. You'll dig into user interface concepts like pages, templates, regions, and components.

Pages

Surf pages are defined using Surf page XML. A page binds to a URI. Surf allows you to render pages by simply passing the URI in the request. You can request a page by using a URL like the ones shown here:

http://localhost:8080/surf/<page-id>

http://localhost:8080/surf?p=<page-id>

If Surf finds this page, it will look at the page XML configuration to figure out which template to use to render the output. Each page can have multiple templates keyed by format. A page might have a default template that it uses for HTML output to a browser, but it may have another template configured that it uses for output to a wireless device.

You can request a particular format for a page using URLs like these:

http://localhost:8080/surf/<page-id>?f=<format-id>

http://localhost:8080/surf?p=<page-id>&f=<format-id>

This allows you to have different markup for different intended formats (such as for small-display devices or integration purposes).

Surf pages are also locale-aware. This lets you finely adjust your site's pages for different languages and localization needs. When you make a request for a page, Surf will do its best to find a match for your browser's locale. If a locale match cannot be made, Surf will fall back to a specified default locale.

Surf lets you group pages into page types. By requesting a page type, Surf will determine which page to use to satisfy your request. It determines this by looking to your currently configured theme. Themes can override default pages for a given page type. You can request a page type like this:

http://localhost:8080/surf/pt/<page-type-id>

http://localhost:8080/surf?pt=<page-type-id>

For example, Surf defines a login page type. Your site might have two themes, such as a normal theme and a holiday theme. You may also have two distinct login pages, such as a normal login page and a holiday login page.

When the holiday theme is active, you would like Surf to resort to using the holiday login page. All you have to do is switch the theme for the site. None of the links or URLs change at all. The following URLs, for example, will always take you to the theme's designated login page.

http://localhost:8080/surf/pt/login

http://localhost:8080/surf?pt=login

As before, you can request a particular format of the login page type by using a format parameter. Here are two URLs that request the wireless format of the login page type.

http://localhost:8080/surf/pt/login?f=wireless

http://localhost:8080/surf?pt=login&f=wireless

Templates and Regions

Once Surf looks at your URI and figures out what you are requesting, it has to go about the process of handling the view. The request may be for a specific page. Or it may be for a content item of type "article." Or it may be for a specific region of the current page (for example, in an AJAX request).

No matter what is being requested, the objective is eventually going to be to produce markup and send it out to the response. The key to making this happen is the template. The template file provides the basic layout of the response to the browser.

The idea for a template is that it is a reusable layout. You can build it once and then apply it to many pages. Each page can then benefit from a common look and feel that was prescribed ahead of time. By later changing the template, a Web designer can affect many pages all at the same time. This lets you achieve a common look and feel and lowers the costs of maintenance.

This is especially meaningful for large Web sites where you may need to manage hundreds, if not thousands, of pages. Or imagine a catalog site with tens of thousands of products. If you had to code each of these pages by hand one at a time, you'd find it very frustrating regardless of whether you used Java, Surf, .NET, or 6510 assembler. Don't try that.

Figure 12-5 shows three pages for a sample Web site. The Web site isn't all that exciting but that is not the point. Take a look at the pages. You'll notice that two of the pages are quite similar. In fact, they have exactly the same page layout. They have four regions on the page, whereas the first page has only three regions. Therefore, you can describe these three pages with two templates. The templates are shown below the pages.

FIGURE 12-5

Figure 12.5. FIGURE 12-5

Notice that the first template defines three regions and gives them some placeholder names (HEADER, BODY, FOOTER). The second template defines four regions and also gives them names (HEADER, MENU, CONTENT, FOOTER). Take a look at the pages again. Notice that the HEADER and FOOTER regions are common across all three pages.

You can define the two templates in Surf and define regions along with region scope to allow reuse across templates. This is illustrated in Figure 12-6.

FIGURE 12-6

Figure 12.6. FIGURE 12-6

In Figure 12-6, the notion of region scope is employed so as to define the entire Web site with only two templates and five scoped regions. There are three scopes: global, template and page.

Regions in the global scope only need to be configured one time. Their configuration is then reused by any templates or pages that include them. In this case, the HEADER and FOOTER regions are defined once in the global scope. Their content appears exactly the same on all of the pages of the site.

Regions in the template scope need to be configured once per template. Their configuration is then reused by any pages that use the template. In this case, the MENU region is defined in the template scope for one of the templates (but not the other). Thus, the two pages on the right-hand side that use this template will have the MENU region in common.

Regions in the page scope need to be configured once per page. Their configurations are not reused. Each page needs to configure the region anew. In this case, the BODY and CONTENT regions are in the page scope. As you can see from Figure 12-5, this allows the two right-hand pages to be slightly different (but only in the CONTENT region).

In the previous section you saw how to write templates using FreeMarker. Now you will learn about the region tag. You define regions on a template using the region tag. When you place the region tag onto the template, you have to inform it of what scope it is in (either page, template, or global). The following examples are indicative of how this is done in FreeMarker:

Globally scoped header region:
<@region id="header" scope="global" />
Template scoped navigation region:
<@region id="navigation" scope="template" />
Page scoped content region:
<@region id="content" scope="page" />

A template defines the basic structure of the rendered view. It then defines regions into which additional presentation should be included.

Figure 12-7 shows an example of the right-hand template that defines four regions: HEADER, MENU, CONTENT, and FOOTER. Some sample code below suggests how you could weave this into a FreeMarker template. It is up to Surf to resolve exactly what should be placed in each of these regions when the template is actually rendered.

FIGURE 12-7

Figure 12.7. FIGURE 12-7

<html>
      <head>
      ${head}
      </head>
      <body>
      <div class="header">
         <@region id="header" scope="global" />
      </div>
      <div class="menu">
         <@region id="menu" scope="template" />
      </div>
      <div class="content">
         <@region id="content" scope="page" />
      </div>
      <div class="footer">
         <@region id="footer" scope="global" />
      </div>
      </body>
</html>

When the template is processed, each of its region tags will execute and attempt to look up something that should be included in that location in the template. In other words, the region tag will be replaced by the output of something that is bound into that place in the template.

Components

Surf allows you to bind components to the regions (as shown in Figure 12-8). A component usually associates a region with a Web script. Templates and scoped regions make it possible to reuse Web scripts quite heavily. You can have as many Web scripts as you like, each encapsulating a unique bit of application functionality.

FIGURE 12-8

Figure 12.8. FIGURE 12-8

In Figure 12-8, a template is used to bring several Web scripts together into an overall markup structure. Here you are rendering a page. However, the same concepts apply for any kind of view rendered from Surf using a template.

Surf lets you define regions in various scopes and then resolves these upon request. This makes your site definition efficient and easier to manage by promoting reuse.

Alfresco Share is an example of a Surf application whose pages are constructed through elegant reuse of templates. All three scopes are used. Figure 12-9 provides an example of how this fits together.

You'll explore Alfresco Share in Chapter 13 and then customize it in Chapter 15 by making adjustments to Surf. For now, you should pick up on the general idea. You can make changes to Alfresco Share pages by tweaking FreeMarker templates and Web scripts.

FIGURE 12-9

Figure 12.9. FIGURE 12-9

In Surf, Web scripts are elevated to the role of not only provisioning remote interfaces to your applications, but also of providing your application's presentation logic. These are presentation-tier Web scripts. Surf orchestrates them so that they can all live together on a single view and interoperate against a common request context.

Web script and template developers have many more capabilities at their disposal. They can do things like pre-process controllers to generate markup that should be injected into different parts of a page (for example, the <head> of an HTML page). Surf also provides additional Web script and template API root-scoped objects and methods for developers.

The API references for both the Web script and template processors are provided in Online Appendix E.

PRESENTATION CONTENT

In this section, you'll do a quick review of the presentation content that Surf consults when it renders the user interface. As you recall, presentation content consists of templates, scripts, and XML files that Surf can pick up without a server restart.

Surf's presentation content consists of three kinds of files:

  • Surf objects

  • Templates

  • Web scripts

Surf Objects

Surf objects define parts of the Web site and describe how these parts fit together to build the complete Web application structure. Objects describe things like pages, page hierarchy, chrome, or components that are reused across many pages.

Objects are defined in XML files that are generally short in nature. A single Surf application will have many XML files to define its objects. When Surf starts up, it looks for all of these small XML fragments and gathers them together to form a complete registry of all of the objects.

An example of the XML for a Page object is shown here:

<?xml version='1.0' encoding='UTF-8'?>
<page>
   <id>mypage</id>
   <title>My First Page</title>
   <description>This is an example of the XML for a Page</description>
</page>

A Spring project will generally maintain these XML files as part of its project resources. They can reside either under the WEB-INF directory or inside the classpath. Users of Alfresco have the additional option of managing these files inside the Alfresco Content Application Server.

The Alfresco Content Application Server allows these XML files to be individually managed, authorized, and approved as part of a lifecycle process. Once approved, Alfresco makes these files available to the Surf application.

Online Appendix E provides you with a complete breakdown of the Surf objects and the locations where you should place these files if you wish them to be picked up out of the classpath. The appendix provides sample XML that you can use as a reference when you define your own Surf objects.

Here are a few examples of the various presentation objects that Surf provides:

  • Chrome—Application borders for your regions and components

  • Components—Binds Web scripts into templates and pages

  • Content Instance—Points to pages or templates to use when rendering content types (cm:content or my:article)

  • Page—A navigable location within the site

  • Page Type—Indirection to non-navigable locations (for example, a login page)

  • Template Instance—Configuration for dynamic templates

  • Theme—Settings that define the site experience

Templates

Templates are transformers that generate markup for the browser to render. For a Web site, this markup is generally HTML. Templates are applied to the current request context or model.

Templates are often files that contain a composite of the output markup and processing tags. The tags execute and generate markup that is injected into the template at the location of the tag. This pattern is common for such template types as FreeMarker, PHP, and XSL.

Surf supports several tags out of the box that are useful to template developers. One commonly used tag is the region tag. The region tag tells the template to look up a component and render its output at the location of the tag.

Here is an example of what a FreeMarker template responsible for rendering a page may look like in Surf:

<html>
   <head>
       ${head}
   </head>
   <body>
      <div class="header">
         <@region id="header" />
      </div>
      <div class="body">
         <@region id="body" />
      </div>
   </body>
</html>

A Spring project will generally maintain these template files as part of its project resources. They could reside either under the WEB-INF directory or inside the classpath. Users of Alfresco have the additional option of managing these files inside a Content Application Server.

Web Scripts

You looked at Web scripts in depth starting with Chapter 8. By now, you should be familiar with the way that they work. From Surf's perspective, Web scripts are miniature applications that you use to build part or all of the page experience. Surf lets you reuse Web scripts and bind them together into as many pages as you like. They are lightweight scripts, which makes them easy to assemble and deploy.

As you know, Declarative Web scripts implement a Model-View-Controller pattern. They have a single descriptor XML file that informs the Web script dispatcher how to behave. Declarative Web scripts have their own template views and optional scriptable controllers. You write new views by writing new template files. You write new controllers by writing new script files. Your scriptable controllers populate the model variable (a map). Your view uses the model during render. It's straight up MVC, right?

Yes, but there is one interesting difference. Surf allows you to merge your Web scripts into the rendering of the overall page. In other words, Surf lets your Web script MVC participate in the overall Spring MVC.

Surf makes sure to provide each Web script with the appropriate context and runtime environment so that it renders in the context of the overall request. The output of each Web script is merged together with the output of the template to form the final markup. This markup is returned from the Spring MVC view as shown in Figure 12-10.

FIGURE 12-10

Figure 12.10. FIGURE 12-10

Figure 12-10 is a modified version of Figure 12-4, with one major adjustment. This is step 5. Rather than produce 100% of the output itself, the rendering template occasionally delegates off to the Web script runtime to do some work. This occurs when the region tags execute. The Web script runtime executes miniature, scriptable MVC processes whose output is then merged into the overall rendition. Surf sets everything up so that the Web script runtime can utilize and take advantage of the full request, user, and page context.

Surf developers build Web scripts to define component implementations that can be accessed either standalone or stitched into an overall page presentation. A component is sometimes thought of as a widget or a gadget – something that you can plug into a Web site on one or more pages. It is a reusable bit of application functionality that participates in the overall page experience.

Web scripts can also be invoked standalone. This means that Web scripts can run entirely outside the context of a page. You can surface components in pop-ups or refresh portions of a Web page via AJAX callbacks. Surf also provides portlet capabilities so that your Web scripts and components can be wrapped up as JSR-268 portlets and dropped into portal servers.

A Spring project will generally maintain Web script files as part of its project resources. They could either reside under the WEB-INF directory or inside the classpath. Users of Alfresco have the additional option of managing these files inside a Content Application Server.

Example

Now you'll build on the example you started earlier. Take your hotel listing page and mix in a Web script. In fact, mix in a Java-backed Web script. This builds on some of the advanced stuff you saw in Chapter 11.

You don't have to type anything in. Just enjoy as you shimmy up your example and make it shine.

A Java-Backed Web Script

In the "Putting It into Action" section, you built a page that used an annotated controller to fetch hotel information for the rendering page. The FreeMarker template then consulted the model to build the markup of hotel listings.

This is a good approach if you want to have the hotel information loaded ahead of the page actually rendering. The penalty of retrieving the information is done ahead of anything on the page even rendering. If multiple components on the page need that information, then this is ideal because it avoids the cost of potentially loading it more than one time.

That said, what if you want to move some of this logic into the individual components on the page? In other words, what if you want the individual Web scripts on the page to have their own Java-backed controllers?

You can do this with a Java-backed Web script. A Java-backed Web script has a Java bean that executes ahead of the Web script's view. You can use them as one way to implement Web script–specific Java controllers.

Look at how this is done. You begin by modifying your template to use a region tag. You looked at region tags when you read about Surf view composition. You do this so as to bind a Web script into the template. It looks something like this:

<html>
   <body>
      <@region id="hotels" scope="page" />
   </body>
</html>

This declares a page-scoped region with the name hotels. You can then develop a Java-backed Web script that retrieves the Hotel list for you and places the result into the model. The Java code would look something like this:

public class HotelListingWebScript extends DeclarativeWebScript
{
   privateTravelServicetravelService;
   public void setTravelService(TravelServicetravelService)
   {
      this.travelService = travelService;
   }
   @Override
   protected Map<String, Object>executeImpl(WebScriptRequestreq, Status status)
   {
      Map<String, Object> model = new HashMap<String, Object>(7, 1.0f);
model.put("hotelList",  travelService.findHotels());
      return model;
   }
}

You then wire this into place with a little Spring configuration. All you then have to do is declare two files.

The Web script descriptor:

hotellisting.get.desc.xml
<webscript>
   <shortname>Hotel Listing</shortname>
   <url>/hotel/listing</url>
   <format default="html">argument</format>
   <authentication>none</authentication>
   <transaction>required</transaction>
</webscript>

And the view template (written in FreeMarker):

hotellisting.get.html.ftl
<table>
   <#if hotelList?size == 0>
   <tr>
      <td colspan="2">No hotels found</td>
   </tr>
   <#else>
   <#list hotelList as hotel>
   <tr>
      <td>${hotel.name}</td>
      <td>${hotel.address}</td>
   </tr>
   </#list>
   </#if>
</table>

That's just about it. You now have a reusable Java-backed Web script. All that remains is an empty page-scoped region called hotels on your template. You can plug in your new Web script by adding a component binding. Since the region tag defines a region in the page scope, you add a page-scoped component definition, as shown here:

<?xml version='1.0' encoding='UTF-8'?>
<component>
   <source-id>hotels</source-id>
   <scope>page</scope>
   <region-id>hotels</region-id>
   <url>/hotel/listing</url>
</component>

That's all. By using this approach, you have created a reusable Web script that can be placed into multiple locations in your Web site. It could appear on different pages, in different regions, and in different scopes. The advantage is that the controller is only hit when the Web script is on the page. The disadvantage is that the Java-backed controller isn't as portable as the rest of the Web script files. Moving it from one Surf application to another would require a server restart.

CONNECTORS AND CREDENTIALS

Web script developers often work with remote sources of data. Surf makes it easy for developers to reach out to these information sources and pull together feeds of data.

These data sources might be SOAP or RESTful providers, CMIS repositories, or perhaps completely proprietary in nature. Furthermore, each data source may require a unique set of credentials to be presented in order to work with the data source.

Surf allows you to define connectors that are responsible for communicating with endpoints. An endpoint is a place where a data source lives. It could be a server in your business residing at an HTTP address. Connectors are used to connect to an endpoint and communicate with it.

Connectors are wired together with authenticators so that they can effectively handshake and establish credentials with endpoints. This pattern abstracts away any of the manual management of connection state that developers would otherwise need to perform. Using authenticators, connectors manage user identity and session state to the endpoint. This is automatically managed for the duration of the user session in the Surf application itself.

Connectors and Endpoints

Connectors and endpoints are both defined through simple configuration as part of Surf's remote configuration block. Declaring an endpoint is fairly simple. It may look something like this:

<config evaluator="string-compare" condition="Remote">
   <remote>
      <endpoint>
         <id>springsurf</id>
         <name>Spring Surf</name>
         <connector-id>http</connector-id>
         <endpoint-url>http://www.springsurf.org</endpoint-url>
      </endpoint>
   </remote>
</config>

This defines an endpoint named springsurf. When talking to this endpoint, a connector of type http should be used. The data source lives at www.springsurf.org:8080. Since nothing else is provided, this is assumed to be an unauthenticated endpoint.

Surf provides a number of out-of-the-box connectors. The http connector lets you connect to HTTP or HTTPS endpoints. If you want to assert an identity to the endpoint, you can do that by adjusting the configuration:

<config evaluator="string-compare" condition="Remote">
   <remote>
      <endpoint>
         <id>springsurf</id>
         <name>Spring Surf</name>
         <connector-id>http</connector-id>
         <endpoint-url>http://www.springsurf.org</endpoint-url>
         <identity>declared</identity>
         <username>USERNAME</username>
         <password>PASSWORD</password>
</endpoint>
   </remote>
</config>

For an http connector, the credentials are passed through using Basic authentication. The values USERNAME and PASSWORD are just placeholders – you would want to fill in your own values here.

With an endpoint like this defined, you can now code against the endpoint and use it without having to worry about managing connection state and asserting credentials.

You could use the following Web script controller code to retrieve something from the springsurf endpoint:

// get a connector to the springsurf endpoint
var connector = remote.connect("springsurf");
// place text file into the model
var txt = connector.get("/sample/helloworld.txt");
model.txt = txt;

The remote root-scope variable gives you a variety of methods and accessors for working with connectors. When it is used, the connection mechanics are abstracted away and your Web script code becomes highly portable from one environment to another, as well as reusable across many users.

Credentials

Surf provides credential management on behalf of users who access content using connectors. If a connector needs to know which credentials to attach to a given request during an authentication handshake, it can call upon the credential vault.

Surf's default credential vault is runtime-only, meaning that it is populated and used at runtime. If you restart the server, the credentials are lost and the user will be required to provide their credentials all over again the next time the connector is used.

Surf also lets you override the credential vault implementation. It provides a number of additional credential vaults out of the box that you can use or base your implementations on. These include a file-system–persistent credential vault and an Alfresco credential vault (where your credentials are stored in an Alfresco-managed file).

To use the credential vault, you simply need to inform the endpoint that its identity is driven from the current user. You can make this change to your endpoint definition:

<config evaluator="string-compare" condition="Remote">
   <remote>
      <endpoint>
         <id>springsurf</id>
         <name>Spring Surf</name>
         <connector-id>http</connector-id>
         <endpoint-url>http://www.springsurf.org</endpoint-url>
         <identity>user</identity>
      </endpoint>
   </remote>
</config>

Connectors to this endpoint will now look for the user's credentials in the credential vault. If credentials are not found and the endpoint requires authentication, the connection may fail. However, if credentials are available in the vault, they will be applied and the connector will access the endpoint on behalf of the developer without the need for manual login.

Authenticators

Authenticating connectors are connectors that have authenticators plugged into them. An authenticator is a class that knows how to perform an authentication handshake with a specific kind of service or application.

For example, consider MediaWiki, which provides a REST-based means for authenticating. You pass in your user credentials and it hands back an HTTP cookie. This cookie must be applied to every subsequent request, as MediaWiki looks to it to inform the application of who is making the request.

Alfresco has a similar REST-based means for authenticating. It is slightly different in that the RESTful parameters are not the same as those of MediaWiki. Furthermore, Alfresco hands back a ticket in an XML return payload. This ticket must be applied to the HTTP headers of every subsequent call so that Alfresco knows who is making the request.

In fact, every application has a slightly different way of handling its authentication. For this reason, Surf makes it easy to write your own authenticators and plug them into your connectors entirely through configuration.

You define authenticators through configuration as well:

<authenticator>
   <id>alfresco-ticket</id>
   <name>Alfresco Authenticator</name>
   <description>Alfresco Authenticator</description>
   <class>org.alfresco.connector.AlfrescoAuthenticator</class>
</authenticator>

You can then bind them to connectors using configuration. Additionally, you can write your own connectors if you so choose:

<connector>
  <id>alfresco</id>
  <name>Alfresco Connector</name>
  <description>Connects to Alfresco using ticket-based authentication</description>
  <class>org.alfresco.connector.AlfrescoConnector</class>
  <authenticator-id>alfresco-ticket</authenticator-id>
</connector>

The alfresco-ticket authenticator and the alfresco connector are both available to Surf developers out of the box. You can use them to connect to an Alfresco instance. All you need to do is define an endpoint that points to an Alfresco instance and uses the alfresco connector.

Alfresco connectors use an Alfresco authenticator to perform a handshake ahead of any actual interaction. The handshake establishes who the user is and then sets up the connector session so that subsequent requests contain the appropriate connection information (cookies, request headers, and so forth). The endpoint definition may look like this:

<endpoint>
   <id>alfresco</id>
   <name>Alfresco REST API</name>
   <description>Alfresco REST API</description>
   <connector-id>alfresco</connector-id>
   <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url>
   <identity>user</identity>
</endpoint>

This endpoint is named alfresco. It uses an alfresco connector and it will draw credentials from the user's credential vault. This is all defined in configuration.

You could use the alfresco endpoint to talk to an Alfresco instance and access its remote API. For example, you may wish to interact with the CMIS API on the Alfresco repository. Here is an example of retrieving XML from the Alfresco CMIS API:

// get a connector to the alfresco endpoint
var connector = remote.connect("alfresco");
// place CMIS text onto the model
model.cmis = connector.get("/api/path/workspace/SpacesStore");

Web script developers do not need to worry about how to connect to the endpoint or pass along user state. They simply code to the remote object.

The Remote API

The remote root-scoped object is very useful for connecting to remote services and retrieving data feeds. It abstracts away all the underlying connectivity stuff, and, therefore, remains a fairly easy API to use.

The basic pattern is to use the remote object to get a connector to a specific endpoint. Endpoints are identified by endpoint ID. You essentially need to do something like this:

var connector = remote.connect(ENDPOINT_ID);

You simply fill in ENDPOINT_ID with the correct value. You now have a connector to the remote service. The connector variable is an object with additional methods describing all of the ways you can work with the endpoint.

The following methods are typically what you will use:

  • post(uri, body)—POSTs content to the given URI

  • post(uri, body, contentType)—POSTs content of the specified type to the given URI

  • get(uri)—GETs content from the given URI

  • put(uri, body)—PUTs content to the given URI

  • put(uri, body, contentType)—PUTs content of the specified type to the given URI

  • delete(uri)—Invokes a URI as a DELETE request

These are the basic HTTP method types that are used to support the essential CRUD (create, read, update, delete) operations of most RESTful services. You can use these to work with services right within your Web scripts.

Example

Now you'll use the Surf remote API to further enhance your hotel listing example. You'll adjust the Java-backed Web Script example from the previous section so that it uses a scriptable controller to access the remote service. This removes the need for Java code and makes your code much more portable!

As before, you don't have to type anything in. Just enjoy as you do a little dance with Surf's remote API. You'll get more hands-on in Chapter 14.

Scriptable Controller

You can also solve the challenge of retrieving a list of hotels from the Travel Service using a purely script-based approach. Declarative Web scripts support scriptable controllers. Spring Surf provides out-of-the-box support for server-side JavaScript, but you can also plug in add-on processors for Groovy and PHP.

You can use Surf's remote API from within a scriptable controller to call out to the Travel Service and acquire the very same information as in the previous example.

Look at how you can do this. Begin by removing the Java-backed Web script. It is very cool and exciting; however, for this example, you want to use a purely scriptable approach. So you remove the Java class and remove the registration of the Java bean from the Spring configuration.

All you need to do then is add one additional file to the Web script:

hotellisting.get.js
var connector = remote.connect("travelService");
varjsonString = connector.call("/hotels/find");
model.hotelList = eval(jsonString);

This is a JavaScript controller that uses the remote variable to pull back content from an endpoint named travelService. You assume that the Travel Service lives at this location. You also assume that it speaks JSON. This means that many different Web applications in the business can all talk to this Travel Service and pull back data for use in rendering pages. It is a pure service-oriented architecture.

If you have set up the endpoint as well as any necessary connectors or authenticators, then the remote variable will let you open connections and work with services on the other side. In this case, you pull back a JSON object and then work with it via pure JavaScript. Nothing else changes.

The advantage of this approach over the previous one is that it is entirely script-based. This means that the Web script is much more transportable; you can move it from one environment to the next with very little difficulty. It is as easy as copying and pasting a set of files. It is also easy to change and does not require Java development skills.

GETTING INVOLVED

The Spring Surf Extension project is maintained and developed as a collaborative effort between Alfresco Software and SpringSource. Alfresco contributed Surf to SpringSource so as to enable the project to grow and accelerate in a wider developer community. It is available today as a plug-in for Spring MVC 3.x applications under the Apache 2.0 license.

Alfresco continues this investment into the project with the active participation of its core engineering team. At the same time, Alfresco has opened up the project to the Spring developer community and encourages open participation and contribution.

If you would like to learn more about the Spring Surf Project or become involved, you can visit the Spring Surf Project here:

www.springsurf.org

From this project page, you can download the latest Spring Surf releases, access documentation, and browse through project information, including build details, technical documentation, and tutorials. It also provides information about how you can check out the code and contribute.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.219