Magnolia blossom-webinar

37
US NAVY INTEGRATES SPRING APPS WITH FLYING COLORS THANKS TO WEB CMS +

description

The US Navy needed a new Content Management System, but had already developed several great applications and had an investment in Spring. Could they bring it all together?During this webinar, you will learn how Campbell Ewald (Navy's digital agency) used Magnolia's Blossom module for straightforward app integration and how Blossom enabled Spring developers to work efficiently with Magnolia CMS right from the start.This Webinar is for Spring Developers of all levels, as well as Developers and IT professionals interested in application integration for business solutions.

Transcript of Magnolia blossom-webinar

Page 1: Magnolia blossom-webinar

US NAVY INTEGRATES SPRING APPSWITH FLYING COLORS THANKS TO WEB CMS

+

Page 2: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

ABOUT THE WEBINARPosting Questions

Viewing the recorded webinar and related materials

Contacting Us

Tweeting - use hash tags: #magnolia_cms #campbellewald #springframework #springmvc #USNavy

2

Page 4: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

ABOUT CAMPBELL EWALD AND MAGNOLIACAMPBELL EWALDDigital AgencyMagnolia PartnerGeneral Motors, Kaiser Permanente, US Postal Service

MagnoliaJava-based Content Management SystemCommunity and Enterprise EditionsSony, Deloitte, Airbus and JBoss

4

Page 5: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

WHAT WE’LL TALK ABOUT

Overview of Magnolia Blossom module

Navy Use Cases

Step-by-Step Walkthrough

Questions

5

Page 6: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

BLOSSOM OVERVIEW

6

Page 7: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

A LITTLE PHILOSOPHY

7

The idea that sparked me writing the Blossom Module for Magnolia CMS was to bring the CMS and especially the content into Spring Web MVC not the other way around. Controllers should be the building blocks when composing pages.

– Tobias Mattsson http://tobias-mattsson-magnolia.blogspot.com/2011/03/spring-web-mvc-with-content.html

Page 8: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

WHAT’S BLOSSOM

Spring integration module for Magnolia CMS

Spring Web MVC with Content

Key features:

Annotation based API

Spring controllers exposed as template components

Pre-execution of template components

8

Page 9: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

WHY USE BLOSSOMLeverages proven Spring Framework

Easily integrate or migrate existing Spring-based applications

Spring developers will feel right at home

Dynamic dialogs, Virtual URI Mappings, Pre-execution

Easily access content in controllers

Best practice web app design patterns (MVC, IoC, AOP)

Open Source and Free to download and use

9

Page 10: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

NAVY USE CASES

10

TM

Page 11: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

ABOUT NAVY.COM

Official recruitment Website for the U.S. Navy

Redesigned in 2010, moved to Magnolia CMS

Application integration for subscription services, CRUD functionality and user-space applications

11

Page 12: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

BLOSSOM + RESTEASY

RESTEasy provided nice JAX-RS client

Aligned with Magnolia CMS roadmap

Allowed business components to be loosely-coupled to CMS

12

Navy Web Service Registry

Business Component 1

Business Component 2

Business Component 3

Business Component n

Navy.comNavy Custom Module

Blossom

Page 14: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

FIND A RECRUITER

Finds the nearest Enlisted and Officer Recruiter Locations based on the postal code entered by the site visitor

Collect and validate input, call Navy Web Service, display results.

14

Page 15: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS

Personality Profile Test to help potential recruits plan their future

Wizard type interface

Collect and validate selected options from site visitor, populate model, then calculate and display results

15

Page 16: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

STANDARD TEMPLATING KITThe STK is a production-

ready website construction framework.

• Best Practices• Rapid Prototyping•Modular Architecture• Extensible• Standards-Compliant

16

Page 17: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

BUILDING LIFE OPS: STEP-BY-STEP

17

Page 18: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

GETTING STARTEDStarting Spring - Add the Blossom Servlet Context Listener to your web.xml<listener> <listener-class>info.magnolia.module.blossom.support.ServletContextExposingContextListener</listener-class></listener>

Create a module for your custom Spring application components, quickest way:$ mvn archetype:generate -DarchetypeCatalog=http://nexus.magnolia-cms.com/content/groups/public/

Have your module class extend BlossomModuleSupport and implement start and stop methods:public class BlossomSampleModule extends BlossomModuleSupport implements ModuleLifecycle { public void start(ModuleLifecycleContext moduleLifecycleContext) { initRootWebApplicationContext("classpath:/applicationContext.xml"); initBlossomDispatcherServlet("blossom", "classpath:/blossom-servlet.xml"); } public void stop(ModuleLifecycleContext moduleLifecycleContext) { destroyDispatcherServlets(); closeRootWebApplicationContext(); }}

18

Page 19: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

SPRING BEAN CONFIGURATION

Create a Spring bean config file called applicationContext.xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:blossom="http://www.magnolia-cms.com/schema/blossom" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.magnolia-cms.com/schema/blossom http://www.magnolia-cms.com/schema/blossom-1.2.xsd"> <blossom:configuration /> </beans>

19

Page 20: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

SPRING BEAN CONFIGURATION

Create a Spring bean config file called blossom-servlet.xml<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="..."> <context:annotation-config /> <context:component-scan base-package="com.c_e.webinar.magnolia.module.blossomsample" use-default-filters="false"> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.Paragraph" /> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.Template" /> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.DialogFactory" /> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.VirtualURIMapper" /> <context:include-filter type="assignable" expression="info.magnolia.cms.beans.config.VirtualURIMapping" /> </context:component-scan> ...</beans>

20

Page 21: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

SPRING BEAN CONFIGURATIONAdd Spring Handler Adapters and Mappings to blossom-servlet.xml<?xml version="1.0" encoding="UTF-8"?><beans ...> ... <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="customArgumentResolver"> <bean class="info.magnolia.module.blossom.web.BlossomWebArgumentResolver" /> </property> </bean> <bean class="info.magnolia.module.blossom.preexecution.BlossomHandlerMapping"> <property name="targetHandlerMappings"> <list> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="useDefaultSuffixPattern" value="false" /> </bean> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> </list> </property> </bean> ...</beans>

21

Page 22: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

SPRING BEAN CONFIGURATIONAdd Spring View Resolvers to blossom-servlet.xml<?xml version="1.0" encoding="UTF-8"?><beans...> ... <bean class="info.magnolia.module.blossom.view.UuidRedirectViewResolver"> <property name="order" name="1" /> </bean> <bean class="info.magnolia.module.blossom.view.TemplateViewResolver"> <property name="order" name="2" /> <property name="prefix" name="/ce-webinar-templating-kit/templates/blossomsamples" /> <property name="suffix" name=".ftl" /> <property name="viewRenderer"> <bean class="info.magnolia.module.blossom.view.FreemarkerTemplateViewRenderer" /> </property> </bean> <bean class="info.magnolia.module.blossom.view.ParagraphViewResolver"> <property name="order" name="3" /> <property name="prefix" name="/ce-webinar-templating-kit/paragraphs/blossomsamples" /> <property name="suffix" name=".ftl" /> <property name="viewRenderer"> <bean class="info.magnolia.module.blossom.view.FreemarkerParagraphViewRenderer" /> </property> </bean></beans>

22

Page 23: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS MODEL

Uses Bean Validation API (JSR 303)

Excerpt:public class LifeOps implements Serializable { ... @NotNull @Min(0) @Max(18) private Integer advisor; ... @NotNull @Min(0) @Max(18) private Integer doer; ...}

23

Page 24: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS MODEL

Add in bean validation support to blossom-servlet.xml<?xml version="1.0" encoding="UTF-8"?><beans ...> ... <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> ... <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="validator" ref="validator" /> </bean> </property> </bean> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> ...</beans>

24

Page 25: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS MODELConstructorpublic LifeOps(Integer advisor, Integer doer, Integer innovator, Integer persuader, Integer planner, Integer problemSolver) { this.advisor = advisor; this.doer = doer; this.innovator = innovator; this.persuader = persuader; this.planner = planner; this.problemSolver = problemSolver;}

Constructor using transients:public LifeOps(String[] activities, String[] interests, String[] careers) { this.activities = activities; this.interests = interests; this.careers = careers; this.resetScores(); this.calculateScores(activities); this.calculateScores(interests); this.calculateScores(careers);}

25

Page 26: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS SPRING VALIDATORpublic class LifeOpsValidator implements Validator {

@Override public boolean supports(Class<?> clazz) { return LifeOps.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { LifeOps lifeOps = (LifeOps) target; if (lifeOps.getActivities() == null || lifeOps.getActivities().length == 0) { errors.rejectValue("activities", "activities.required", "Please choose at least one activity before proceeding."); } if (lifeOps.getInterests() == null || lifeOps.getInterests().length == 0) { errors.rejectValue("interests", "interests.required", "Please choose at least one interest before proceeding."); } if (lifeOps.getCareers() == null || lifeOps.getCareers().length == 0) { errors.rejectValue("careers", "careers.required", "Please choose at least one career before proceeding."); } }}

26

Page 27: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS CONTROLLER@Controller@RequestMapping("/life-ops")@Paragraph("Life Ops form")@ParagraphDescription("Adds a Life Ops form and displays Life Ops Results")@I18nBasename("com.c_e.webinar.magnolia.module.blossomsample.messages")public class LifeOpsController {

private static final Logger log = LoggerFactory.getLogger(LifeOpsController.class); private final String LIFE_OPS_FORM_PATH = "life-ops/form"; private final String LIFE_OPS_SHOW_PATH = "life-ops/show"; @Autowired private Validator validator; @RequestMapping(method = RequestMethod.GET) public String form(LifeOps lifeOps) { return LIFE_OPS_FORM_PATH; } ...}

27

Page 28: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS CONTROLLER@RequestMapping(method = RequestMethod.POST)public String processSubmit(@Valid LifeOps lifeOps, BindingResult result, Model model) { new LifeOpsValidator().validate(lifeOps, result); if (result.hasErrors()) { List<FieldError> fieldErrors = result.getFieldErrors(); Map<String, String> errorsMap = new HashMap<String, String>(); for (int i = 0; i < fieldErrors.size(); i++) { FieldError thisError = fieldErrors.get(i); errorsMap.put(thisError.getField(), thisError.getDefaultMessage() ); } lifeOps.setErrors(errorsMap); model.addAttribute("lifeOps", lifeOps); return LIFE_OPS_FORM_PATH; } else { lifeOps = new LifeOps(lifeOps.getActivities(), lifeOps.getInterests(), lifeOps.getCareers() ); model.addAttribute("lifeOps", lifeOps); return "redirect:/" + STKUtil.getSite().getName() + "/life-ops/results" + "/" + lifeOps.getAdvisor() + "/" + lifeOps.getDoer() + "/" + lifeOps.getInnovator() + "/" + lifeOps.getPersuader() + "/" + lifeOps.getPlanner() + "/" + lifeOps.getProblemSolver(); }}

28

Page 29: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS CONTROLLER@RequestMapping(method = RequestMethod.GET, params = "advisor")public String show( @RequestParam Integer advisor, @RequestParam Integer doer, @RequestParam Integer innovator, @RequestParam Integer persuader, @RequestParam Integer planner, @RequestParam Integer problemSolver, Model model) { try { LifeOps lifeOps = new LifeOps(advisor, doer, innovator, persuader, planner, problemSolver); model.addAttribute("lifeOps", lifeOps); } catch (Exception e) { log.error("LifeOpsController.show() Exception:" + e); } return LIFE_OPS_SHOW_PATH;}

29

Page 30: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS CONTROLLER@TabFactory("Settings")public void addTab(TabBuilder builder) { builder.addEdit("activitiesText", "Activities Text", "A short description for the activities fieldset"); builder.addEdit("interestsText", "Interests Text", "A short description for the interests fieldset"); builder.addEdit("careersText", "Careers Text", "A short description for the interests fieldset"); builder.addFckEditor("resultsText", "Results Text", "A short intro for the results page");}

30

Page 31: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS VIRTUAL URI MAPPING@VirtualURIMapperpublic class LifeOpsURIMapper {

private Pattern pattern; public LifeOpsURIMapper() { this.pattern = Pattern.compile("^/life-ops/results/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/?"); }

public String mapping(String uri, HttpServletRequest request) { this.pattern = Pattern.compile("^/(" + STKUtil.getSite().getName() + "/)?life-ops/results/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/([0-1]?[0-8])/?"); Matcher matcher = pattern.matcher(uri); if (matcher.matches()) { return matcher.replaceAll("forward:/" + STKUtil.getSite().getName() + "/life-ops/results/?advisor=$2&doer=$3&innovator=$4&persuader=$5&planner=$6&problemSolver=$7"); } return null; }}

31

Page 32: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS FORM VIEW[#assign cms=JspTaglibs["cms-taglib"]][#assign form=JspTaglibs["http://www.springframework.org/tags/form"]]

<div> [@cms.editBar /] [@form.form id="life-ops-form" modelAttribute="lifeOps"] ... <fieldset> <legend>Activities</legend> ... <dl class="prop"> <dt><span>What do you like to do?</span></dt> <dd class="value"> <ul> <li> <input id="activities1" name="activities" value="D" type="checkbox" /> <label for="activities1"><strong>Set up a home computer network</strong></label> </li> <li> <input id="activities2" name="activities" value="S" type="checkbox" /> <label for="activities2"><strong>Track the path of a hurricane</strong></label> </li> ... </ul> </dd> </dl> </fieldset>

32

Page 33: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS RESULTS VIEW[#assign results = [ {"name": "problemSolver", "label": "Problem Solver", "score": lifeOps.problemSolver}, {"name": "planner", "label": "Planner", "score": lifeOps.planner}, {"name": "persuader", "label": "Persuader", "score": lifeOps.persuader}, {"name": "innovator", "label": "Innovator", "score": lifeOps.innovator}, {"name": "doer", "label": "Do-er", "score": lifeOps.doer}, {"name": "advisor", "label": "Advisor", "score": lifeOps.advisor}]]...

33

Page 34: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

LIFE OPS RESULTS VIEW<div class="super-list"> [@cms.editBar /] <ul> [#list results?sort_by("score")?reverse as result] [#assign profileDivId = result.label?lower_case?trim?replace(' ', '-')] [#assign scoreWidth = (result.score/2)*11+"%"] [#assign contentCollectionText = result.name + "Text"] [#assign contentCollectionExtras = result.name + "Extras"] <li> <h3> <a rel="bookmark" href="#${profileDivId}" title="${result.label}"> <dfn style="width:${scoreWidth};"><strong>${result.label}</strong></dfn> </a> </h3> ... [#if contentCollectionText?exists] [@cms.contentNodeIterator contentNodeCollectionName=contentCollectionText] [@cms.includeTemplate/] [/@cms.contentNodeIterator] [/#if] [@cms.newBar contentNodeCollectionName=contentCollectionText paragraph="stkTextImage, stkTeaserGroup" /] ... </li> [/#list] </ul></div><!-- end super-list -->

34

Page 35: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

35

QUESTIONS

Page 37: Magnolia blossom-webinar

WEBINAR

CAMPBELL EWALD

FURTHER READINGVisit the Magnolia documentation site to download Magnolia and find tutorials and guideshttp://documentation.magnolia-cms.com/index.html

The Blossom reference documentationhttp://documentation.magnolia-cms.com/modules/blossom.html

Blossom Sample Webapphttp://documentation.magnolia-cms.com/modules/blossom.html#GettingStarted

Tobias Mattsson's bloghttp://tobias-mattsson-magnolia.blogspot.com/

Ask your questions and see what others are doing at the Magnolia community forumhttp://forum.magnolia-cms.com/forum.html

37