Getting Healthy with Magnolia, Blossom and Spring

Preview:

DESCRIPTION

This presentation was given at Amplify Miami 2014 by Jan Haderka, Senior Developer at Magnolia International, and Casey Dement, VP of Architecture at Sharecare, Inc. Casey showed how Sharecare integrated Magnolia and Blossom with Spring MVC and Akamai to build a highly flexible and scalable website. Jan gives an introduction and an update on the new features in Blossom 3.

Citation preview

!1

Blossom in the Real World

Sr. Software Engineer, Magnolia Lead developer of Blossom Module Spring Framework user since 2005

Tobias Mattsson

2@sigget

3

Jan HaderkaHead of Support, Magnolia

@rah003

4

Casey DementVP of Architecture, Sharecare

@casey_dement

#Mplify

5

Magnolia + Spring = Blossom 6

@Template

7

TEMPLATEREQUEST CONTENT

CMS

8

TEMPLATEREQUEST CONTENT

CMS + Blossom

9

CONTROLLER

MODEL

VIEW

Page Template

@Controller @Template(id="myModule:pages/main", title="Main") public class MainTemplate { !

   @RequestMapping("/main")    public String render(ModelMap model) {        return "pages/main";    } }

10

11

PAGE

PAGES CONTAIN 0:n AREAS

12

PAGE

AREA

A R E A

AREA

PAGES CONTAIN 0:n AREAS

13

PAGE

AREA

A R E A

AREA

AREAS HAVE 0:n COMPONENTS

COMPONENT

COMPONENT Etiam porta sem malesuada magna mollis euismod. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

COMPONENT Etiam porta sem malesuada magna mollis euismod. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Aenean lacinia bibendum nulla sed consectetur. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere consectetur est at lobortis.

C O M P O N E N T

Area Template

@Controller @Template(id="myModule:pages/main",title="Main Template") public class MainTemplate {    @Controller   @Area("main")    public static class MainArea {        @RequestMapping("/main/mainArea")        public String render() {            return "areas/main";        }    ...

14

Component Template

@Controller @Template(id="myModule:components/shoppingCart",          title="Shopping Cart") @TemplateDescription("Shopping cart") public class ShoppingCartComponent {    @RequestMapping("/shoppingCart")    public String handleRequest() {        ...        return "components/shoppingCart";    } ...

15

SERVLET CONTAINER

RENDERING FILTER

RENDERING ENGINE

BLOSSOM DISPATCHER SERVLET CONTROLLER

16

How Blossom Works

Blossom 3.0

17

What’s new?

Blossom 3.0

18

Update for Magnolia 5 series Requires Magnolia 5.1 new ways of building dialogs availability annotation

19

Dialogs

Fluent Builder-style API

!

@TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body"), cfg.fields.websiteLink("categoryLink").label("Link"), cfg.fields.basicUpload("image").label("Image"), cfg.fields.checkbox("inlineImage").label("Inline Image")  ); }

20

Dialogs and the Class Hierarchy

public abstract class BasePageTemplate { @TabFactory("Meta") public void metaTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("metaAuthor").label("Author"),     cfg.fields.text("metaKeywords").label("Keywords"),     cfg.fields.text("metaDescription").label("Description")    );  } }

21

Input Validation

... public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("name").label("Name").required(), cfg.fields.text("email").label("Email")⏎ .validator(cfg.validators.email()) ); } ...

22

Dynamic Dialog

!!@Template(id="myModule:components/bookCategory", title="Book category") @TemplateDescription("A list of books in a given category.") @Controller public class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;    @RequestMapping("/bookcategory")    public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    } @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    } }

23

Dynamic Dialog

!@Template(id="myModule:components/bookCategory", title="Book category") @TemplateDescription("A list of books in a given category.") @Controller public class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;

   @RequestMapping("/bookcategory")    public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    } @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    } }

24

@AvailableComponentClasses

25

Components Availability

!    @ComponentCategory @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Banner { } ! @Area("banners") @AvailableComponentClasses({Banner.class}) @Controller    public class BannersArea { … } ! @Banner @Template(id="myModule:components/largePromoBanner"), title="Large banner”) @Controller("banner")    public class LargePromoBannerComponent { …    }

26

@More

27

Content, content, more content

!@Template(id="myModule:components/bookCategory", title="Book category") @TemplateDescription("A list of books in a given category.") @Controller public class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;    @RequestMapping("/bookcategory")    public String render(Node content, ModelMap model) throws RepositoryException {        String category = content.getProperty("category").getString();        model.put("books", service.getBooksInCategory(category));        return "components/bookCategory";    } @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        Collection<String> categories = service.getBookCategories();        tab.fields( cfg.fields.select("category").label("Category").options(categories)        );    } }

28

Now The Real World 29

Thank You!

30

31

magnolia-cms.com/spring

32

How do I get started on my project?

33

mvn archetype:generate -DarchetypeCatalog=http://nexus.magnolia-cms.com/content/groups/public/ !choose magnolia-blossom-module-archetype !Define value for property 'groupId': : com.acme Define value for property 'artifactId': : acme-module Define value for property 'version': 1.0-SNAPSHOT: Define value for property 'package': com.acme: Define value for property 'magnolia-version': : 4.5.11 Define value for property 'module-class-name': : AcmeModule Define value for property 'module-name': acme-module: acmeModule !!!! http://wiki.magnolia-cms.com/display/WIKI/

Creating+a+new+Blossom+project+using+maven+archetypes

How would I build a REST interface to my CMS content using Blossom?

35

REST servlet in module descriptor

<servlets>   <servlet>     <name>rest</name>     <class>org.springframework.web.servlet.DispatcherServlet</class>     <mappings>       <mapping>/rest/*</mapping>     </mappings>     <params>       <param>         <name>contextConfigLocation</name>         <value>classpath:/rest-servlet.xml</value>       </param>     </params>   </servlet> </servlets>

36

REST Controller

@Controller public class ProductsRestController { !

@RequestMapping("/products/{productId}") public Product findProduct(@PathVariable String productId) { // implementation omitted } }

37

Can I leverage Magnolia's caching functionality to improve the performance of my Spring app?

38

Influencing caching 39

@Controller @Template(title="Text", id="myModule:components/text") public class TextComponent { !

@RequestMapping("/text") public String render(HttpResponse response) { response.setHeader("Cache-Control", "no-cache"); return "components/text.jsp"; } }

How can I present dialogs in different languages using Blossom?

40

!@Controller @Template(title = "Text", id = "blossomSampleModule:components/text") @I18nBasename("info.magnolia.blossom.sample.messages") public class TextComponent { !

@TabFactory("textComponent.contentTab.label") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("textComponent.contentTab.heading") ); } }

41I18n Blossom Dialog

!!/info/magnolia/blossom/sample/messages_en.properties textComponent.contentTab.label = Content textComponent.contentTab.heading = Heading !!!/info/magnolia/blossom/sample/messages_sv.properties textComponent.contentTab.label = Innehåll textComponent.contentTab.heading = Rubrik !

42I18n Blossom Dialog

How do I migrate my existing site to Magnolia/Blossom?

43

How can I integrate Spring Security?

44

45

How can I dependency inject spring beans into RenderingModels?

46

Autowired RenderingModel

public class MyRenderingModel extends RenderingModelImpl<TemplateDefinition> { ! @Autowired private MyService service; ! public MyRenderingModel(ServletContext servletContext, Node content, TemplateDefinition definition, RenderingModel<?> parent) { super(content, definition, parent); WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletContext); AutowireCapableBeanFactory beanFactory = wac.getAutowireCapableBeanFactory(); beanFactory.autowireBean(this); }

47

Autowired RenderingModel

!public class MyRenderingModel extends AbstractAutowiredRenderingModel<TemplateDefinition> { ! @Autowired private MyService service; ! public MyRenderingModel(ServletContext servletContext, Node content, TemplateDefinition definition, RenderingModel<?> parent) { super(content, definition, parent, servletContext); } !!!!

48

Views 49

Rendering an Area

FreeMarker [@cms.area name="main" /] !

JSP <cms:area name="main" />

50

Rendering Components in an Area

FreeMarker [#list components as component]    [@cms.component content=component /] [/#list] !

JSP <c:forEach items="${components}" var="component">    <cms:component content="${component}" /> </c:forEach>

51

Page Template Availability

@Controller @Template(title="Article", id="myModule:/pages/article") public class ArticleTemplate {    ...    @Available    public boolean isAvailable(Node node) {        return node.getPath().startsWith("/articles/");    } }

52

Available Components

@Controller @Area("promos") @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public static class PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } }

53

Area Inheritance

@Controller @Area("promos") @Inherits @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public static class PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } }

54

Form Submission

/* Standard annotations omitted */ public class ContactFormComponent { @RequestMapping(value="/contact", method=RequestMethod.GET)  public String viewForm(@ModelAttribute ContactForm contactForm) { return "components/contactForm";

} @RequestMapping(value="/contact", method=RequestMethod.POST) public String handleSubmit(@ModelAttribute ContactForm contactForm, ⏎ BindingResult result) { new ContactFormValidator().validate(contactForm, result); if (result.hasErrors()) { return "components/contactForm"; } return "redirect:/home/contact/thankyou.html";

}

55

But wait a minute …

56

Caused by: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778) at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at info.magnolia.module.blossom.render.BlossomDispatcherServlet.forward(BlossomDispatcherServlet.java:123) at info.magnolia.module.blossom.render.BlossomTemplateRenderer.render(BlossomTemplateRenderer.java:78) ... 92 more Caused by: java.lang.IllegalStateException at org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)

… the response has been sent.

Controller Pre-execution

57

C M V

PAGE

C M V

Area

C M V

Component

C M V

Component

C M V

Component

C

CPE Implementation 58

Code in View <form> <blossom:pecid-input /> <input type=”text” name=”email” /> ... !

HTML Output <form> <input type=”hidden” name=”_pecid” value=”ff6cefa6-d958-47b1-af70-c82a414f17e1” /> <input type=”text” name=”email” /> ...

Spring Web MVC +

Content

59

Spring Web Flow

60

REQUEST LANDING PAGE

SPRING WEB FLOW

Spring Web Flow

61

REQUEST LANDING PAGE

FORM SUBMIT?

SPRING WEB FLOW

62

Trademarks

Other trademarks are the property of their respective owners.

MagnoliaThe Pulse are trademarks of

Magnolia International Limited.}

Recommended