© 2013 SpringOne 2GX. All rights reserved.
Spring & Web
Content Management
Tobias Mattsson & Daniel Lipp
Magnolia International
2
3
4
Sr. Software Engineer, Magnolia
Lead developer of Magnolia’s Spring integration
Spring Framework user since 2005
Tobias Mattsson
5
Daniel Lipp
Sr. Software Engineer, Magnolia
17 years of experience as software architect and Java developer
Grateful to Spring for inspiring improvements to JEE 6
®
7
8
@magnolia_cms
#magnolia_cms
9
Spring
Spring MVC
Code, beautiful code
Dependency Injection
Integrations
Business logic
10
CMS
Security
Multi-lingual
User interface
Image manipulation
Content versioning
11
CMS
Security
Multi-lingual
User interface
Image manipulation
Content versioning
Spring
Spring MVC
Code, beautiful code
Dependency Injection
Integrations
Business logic
12
USES IFRAMES
LIKE IT'S 1999 13
Magnolia + Spring = Blossom 14
@Template
15
or, "How Blossom is Built"
TEMPLATEREQUEST CONTENT
CMS
16
TEMPLATEREQUEST CONTENT
CMS + Blossom
17
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";
   }
}
18
PAGES CONTAIN 0:n AREAS
19
PAGE
PAGES CONTAIN 0:n AREAS
19
PAGE
AREA
A
R
E
A
AREA
PAGES CONTAIN 0:n AREAS
19
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";
       }
   ...
20
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";
   }
...
21
Views 22
Rendering an Area
FreeMarker
[@cms.area name="main" /]
JSP
<cms:area name="main" />
23
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>
24
About Magnolia CMS
100% Java/J2EE compliant
Best of breed open technology stack, including:
JSR-283 / JCR 2.0
Vaadin / GWT
Servlet API
HTML5
Highly Customizable
25
Java Content Repository
26
“… defines an abstract model and a Java
API for data storage and related services
commonly used by content-oriented
applications.” – JSR-283
Magnolia
JCR API
Persistence Manager
Jackrabbit Hierarchical
content repository
In memoryDatabaseFile system
or or
27
Why JCR?
28
Hierarchical
Event Notification
(Observation)
Role-based
security
Locking
Fine-grained content
and large binaries
Full-text
Search
Structured
Queries
Referential
Integrity
TransactionalSchema-less or
Strongly-typed
Versioning
29
Dialogs
30
GWT with server-side state
Component-based UI
Cross-browser compatibility (via GWT)
Rich component library
Fast development pace
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")
 );
}
31
Page Template with Dialog
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
   @TabFactory("Content")
   public void contentTab(UiConfig cfg, TabBuilder tab) {
       tab.fields(cfg.fields.text("title").label("Title"));
   }
}
32
Page Template with Dialog
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
   @TabFactory("Content")
   public void contentTab(UiConfig cfg, TabBuilder tab) {
       tab.fields(cfg.fields.text("title").label("Title"));
   }
} <title>${content.title}</title>
<!-- In the view -->
32
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controller
public static class MainArea {
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label("Heading"),
cfg.fields.text("border").label("Border width")
);
Area Template with Dialog 33
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controller
public static class MainArea {
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label("Heading"),
cfg.fields.text("border").label("Border width")
);
Area Template with Dialog 33
@Controller
@Template(title="Article", id="myModule:pages/article")
public class ArticleTemplate {
@Area("main")
@Controller
public static class MainArea {
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label("Heading"),
cfg.fields.text("border").label("Border width")
);
Area Template with Dialog 33
<div id="main"
style="border:${content.border}px solid #000">
<h2>${content.heading}</h2>
<c:forEach items="${components}" var="component">
<cms:component content="${component}" />
</c:forEach>
</div> <!-- In the view -->
Component Template with Dialog
@Controller
@Template(title="Text", id="myModule:components/text")
public class TextComponent {
@RequestMapping("/text")
public String render() {
return "components/text";
}
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label("Heading"),
cfg.fields.richText("body").label("Text body")
34
Component Template with Dialog
@Controller
@Template(title="Text", id="myModule:components/text")
public class TextComponent {
@RequestMapping("/text")
public String render() {
return "components/text";
}
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("heading").label("Heading"),
cfg.fields.richText("body").label("Text body")
34
<h1>${content.heading}</h1>
<p>${cmsfn:decode(content).body}</p>
<!-- In the view -->
YouTube Video Component
@Controller
@Template(title="YouTube Video", id="myModule:components/youtube")
@TemplateDescription("Embed a YouTube video")
public class YoutubeComponent {
@RequestMapping("/youtube")
public String render(Node node, ModelMap model) throws RepositoryException {
model.put("videoId", node.getProperty("videoId").getString());
return "components/youtube";
}
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("videoId").label("Video ID")
);
}
}
35
YouTube Video Component
@Controller
@Template(title="YouTube Video", id="myModule:components/youtube")
@TemplateDescription("Embed a YouTube video")
public class YoutubeComponent {
@RequestMapping("/youtube")
public String render(Node node, ModelMap model) throws RepositoryException {
model.put("videoId", node.getProperty("videoId").getString());
return "components/youtube";
}
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
tab.fields(
cfg.fields.text("videoId").label("Video ID")
);
}
}
35
<iframe width="100%" height="400" src="//www.youtube.com/
embed/${videoId}" frameborder="0" allowfullscreen></iframe>
<!-- In the view -->
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")
   );
 }
}
36
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")
   );
 }
}
36
<head>
<meta name="description" content="${content.metaDescription}"/>
<meta name="keywords" content="${content.metaKeywords}" />
 <meta name="author" content="${content.metaAuthor}" />
</head> <!-- In the view -->
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(ModelMap model, Node content) 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)
       );
   }
}
37
Dynamic Dialog
...
@TabFactory("Content")
public void contentTab(UiConfig cfg, TabBuilder tab) {
Collection<String> categories = service.getBookCategories();
tab.fields(
cfg.fields.select("category").label("Category").options(categories)
 );
}
...
38
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())
);
}
...
39
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/");
   }
}
40
Available Components
@Controller
@Area("promos")
@AvailableComponentClasses({TextComponent.class,
                           ShoppingCartComponent.class})
public static class PromosArea {
   @RequestMapping("/main/promos")
   public String render() {
       return "areas/promos";
   }
}
41
Area Inheritance
@Controller
@Area("promos")
@Inherits
@AvailableComponentClasses({TextComponent.class,
                           ShoppingCartComponent.class})
public static class PromosArea {
   @RequestMapping("/main/promos")
   public String render() {
       return "areas/promos";
   }
}
42
SERVLET
CONTAINER
RENDERING
FILTER
RENDERING
ENGINE
BLOSSOM
DISPATCHER
SERVLET
CONTROLLER
43
How Blossom Works
Magnolia
Server
Data
Workflow
STK
Modules
Magnolia Core
Custom
module
Custom
module
DMS
Imaging
Custom
Theme
Module JAR
- templates
- resources
44
Web container (Tomcat)
Web Archive
Magnolia CMS
Filter chain CMS
Context Security Cache Aggregation
Hierarchical content
repository
Content
Custom
filter
Request
Rendering
Rendering
engine
Response
45
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";
}
46
But wait a minute …
47
Caused by: org.springframework.web.util.NestedServletException: Reque
java.lang.IllegalStateException
at org.springframework.web.servlet.FrameworkServlet.processRequest(F
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkS
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at info.magnolia.module.blossom.render.BlossomDispatcherServlet.forw
at info.magnolia.module.blossom.render.BlossomTemplateRenderer.rende
... 92 more
Caused by: java.lang.IllegalStateException
… the response has been sent.
Controller Pre-execution
48
C M V
PAGE
C M V
Area
C M V
Component
C M V
Component
C M V
Component
C
CPE Implementation 49
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
50
Spring Web Flow
51
REQUEST LANDING
PAGE
SPRING
WEB FLOW
Spring Web Flow
52
REQUEST LANDING
PAGE
FORM
SUBMIT?
SPRING
WEB FLOW
Deployment & Scaling 53
Author
Magnolia
Public A
Magnolia
Public B
Magnolia
Firewall
Activate
Internal Network Internet
LoadBalancer
JCR
JCR
JCR
54
55
Backend
Application
DB
Author
Magnolia
JCR
Public
Magnolia
JCRPublic
Magnolia
JCR
Clustering
Dealing with Change 56
Filter Chain in JCR
57
Configuring Spring Beans 58
<blossom:observed-bean
default-class="com.sample.DiscountService"
path="/modules/myModule/beans/discountService" />
example.com/vanity
59
Changing Resources Live
60
CSS
FreeMarker
Images
Javascript
Views
What Do You Win? 61
62
63
64
Shorter
Learning
Curve
MVC
Use Existing
Spring Apps &
Components
@Annotations
Loose
Coupling
Dependency
Injection
Code-driven
Dialogs
Test-driven
Development
Spring
IdiomsSecurity
Scalability
Mobile
& Touch
Faster
Development
Staging
Maglev
64
Shorter
Learning
Curve
MVC
Use Existing
Spring Apps &
Components
@Annotations
Loose
Coupling
Dependency
Injection
Code-driven
Dialogs
Test-driven
Development
Spring
IdiomsSecurity
Scalability
Mobile
& Touch
Faster
Development
Staging
Questions?
Maglev
Want to Learn More?
65
Talk to us in the exhibit hall
Attend Blossom Q & A webinar on Sept. 26
http://magnolia-cms.com/blossom-qa
Visit http://magnolia-cms.com/spring
Dashboard for all things Spring in Magnolia
Thank You!
66
Image Credits
67
#2: "Long Drive" by Nicholas A. Tonelli (CC-BY 2.0)
#3: "Sluggish" by Nicholas A. Tonelli (CC-BY 2.0)
#4: Still from "The Matrix"
#7: "South Beach Miami Sunset" by Justin Ornellas (CC-BY 2.0)
#14: "HBW - Magnolia Edition" by Nana B Agyei (CC-BY 2.0)
#16: "Worker" designed by James Fenton from The Noun Project
#16: "Database" designed by Ed Jones from The Noun Project
#16: "Document" designed by Timur Zima from The Noun Project
#22: "Three levels" by Paolo Fefe (CC-ND 2.0)
#43:"Headset" designed by Benoît Bâlon from The Noun Project
#43: "Engine" released into the Public Domain
#43: "Flower" designed by Danilo Gusmão Silveira from The Noun Project
#43: "Video Game Controller" designed by "Michael Rowe" from The Noun Project
#51: "Anvil" designed by Masrur Mahmood from The Noun Project
#52: "Hand" designed by Mikhail Bazilevsky from The Noun Project
#53: "NYC Steam" by Don O'Brien (CC-BY 2.0)
#56: "Floods 2013: Riverfront Ave" by Ryan Quan (CC-SA 2.0)
#61: "3 Strikes for £2.50" by Julian Frost (CC-BY 2.0)
68
Trademarks
Other trademarks are the property of their respective owners.
Magnolia
The Pulse are trademarks of
Magnolia International Limited.}

Spring and Web Content Management

  • 1.
    © 2013 SpringOne2GX. All rights reserved. Spring & Web Content Management Tobias Mattsson & Daniel Lipp Magnolia International
  • 2.
  • 3.
  • 4.
  • 5.
    Sr. Software Engineer,Magnolia Lead developer of Magnolia’s Spring integration Spring Framework user since 2005 Tobias Mattsson 5
  • 6.
    Daniel Lipp Sr. SoftwareEngineer, Magnolia 17 years of experience as software architect and Java developer Grateful to Spring for inspiring improvements to JEE 6
  • 7.
  • 8.
  • 9.
  • 10.
    Spring Spring MVC Code, beautifulcode Dependency Injection Integrations Business logic 10
  • 11.
  • 12.
    CMS Security Multi-lingual User interface Image manipulation Contentversioning Spring Spring MVC Code, beautiful code Dependency Injection Integrations Business logic 12
  • 13.
  • 14.
    Magnolia + Spring= Blossom 14
  • 15.
  • 16.
  • 17.
    TEMPLATEREQUEST CONTENT CMS +Blossom 17 CONTROLLER MODEL VIEW
  • 18.
    Page Template @Controller @Template(id="myModule:pages/main", title="Main") publicclass MainTemplate {    @RequestMapping("/main")    public String render(ModelMap model) {        return "pages/main";    } } 18
  • 19.
    PAGES CONTAIN 0:nAREAS 19 PAGE
  • 20.
    PAGES CONTAIN 0:nAREAS 19 PAGE AREA A R E A AREA
  • 21.
    PAGES CONTAIN 0:nAREAS 19 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
  • 22.
    Area Template @Controller @Template(id="myModule:pages/main",title="Main Template") publicclass MainTemplate {    @Controller    @Area("main")    public static class MainArea {        @RequestMapping("/main/mainArea")        public String render() {            return "areas/main";        }    ... 20
  • 23.
    Component Template @Controller @Template(id="myModule:components/shoppingCart",          title="Shopping Cart") @TemplateDescription("Shoppingcart") public class ShoppingCartComponent {    @RequestMapping("/shoppingCart")    public String handleRequest() {        ...        return "components/shoppingCart";    } ... 21
  • 24.
  • 25.
    Rendering an Area FreeMarker [@cms.areaname="main" /] JSP <cms:area name="main" /> 23
  • 26.
    Rendering Components inan Area FreeMarker [#list components as component]    [@cms.component content=component /] [/#list] JSP <c:forEach items="${components}" var="component">    <cms:component content="${component}" /> </c:forEach> 24
  • 27.
    About Magnolia CMS 100%Java/J2EE compliant Best of breed open technology stack, including: JSR-283 / JCR 2.0 Vaadin / GWT Servlet API HTML5 Highly Customizable 25
  • 28.
    Java Content Repository 26 “…defines an abstract model and a Java API for data storage and related services commonly used by content-oriented applications.” – JSR-283
  • 29.
    Magnolia JCR API Persistence Manager JackrabbitHierarchical content repository In memoryDatabaseFile system or or 27
  • 30.
    Why JCR? 28 Hierarchical Event Notification (Observation) Role-based security Locking Fine-grainedcontent and large binaries Full-text Search Structured Queries Referential Integrity TransactionalSchema-less or Strongly-typed Versioning
  • 31.
  • 32.
    30 GWT with server-sidestate Component-based UI Cross-browser compatibility (via GWT) Rich component library Fast development pace
  • 33.
    Fluent Builder-style API @TabFactory("Content") publicvoid 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")  ); } 31
  • 34.
    Page Template withDialog @Controller @Template(title="Article", id="myModule:pages/article") public class ArticleTemplate {    @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        tab.fields(cfg.fields.text("title").label("Title"));    } } 32
  • 35.
    Page Template withDialog @Controller @Template(title="Article", id="myModule:pages/article") public class ArticleTemplate {    @TabFactory("Content")    public void contentTab(UiConfig cfg, TabBuilder tab) {        tab.fields(cfg.fields.text("title").label("Title"));    } } <title>${content.title}</title> <!-- In the view --> 32
  • 36.
    @Controller @Template(title="Article", id="myModule:pages/article") public classArticleTemplate { @Area("main") @Controller public static class MainArea { @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") ); Area Template with Dialog 33
  • 37.
    @Controller @Template(title="Article", id="myModule:pages/article") public classArticleTemplate { @Area("main") @Controller public static class MainArea { @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") ); Area Template with Dialog 33
  • 38.
    @Controller @Template(title="Article", id="myModule:pages/article") public classArticleTemplate { @Area("main") @Controller public static class MainArea { @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.text("border").label("Border width") ); Area Template with Dialog 33 <div id="main" style="border:${content.border}px solid #000"> <h2>${content.heading}</h2> <c:forEach items="${components}" var="component"> <cms:component content="${component}" /> </c:forEach> </div> <!-- In the view -->
  • 39.
    Component Template withDialog @Controller @Template(title="Text", id="myModule:components/text") public class TextComponent { @RequestMapping("/text") public String render() { return "components/text"; } @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body") 34
  • 40.
    Component Template withDialog @Controller @Template(title="Text", id="myModule:components/text") public class TextComponent { @RequestMapping("/text") public String render() { return "components/text"; } @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("heading").label("Heading"), cfg.fields.richText("body").label("Text body") 34 <h1>${content.heading}</h1> <p>${cmsfn:decode(content).body}</p> <!-- In the view -->
  • 41.
    YouTube Video Component @Controller @Template(title="YouTubeVideo", id="myModule:components/youtube") @TemplateDescription("Embed a YouTube video") public class YoutubeComponent { @RequestMapping("/youtube") public String render(Node node, ModelMap model) throws RepositoryException { model.put("videoId", node.getProperty("videoId").getString()); return "components/youtube"; } @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("videoId").label("Video ID") ); } } 35
  • 42.
    YouTube Video Component @Controller @Template(title="YouTubeVideo", id="myModule:components/youtube") @TemplateDescription("Embed a YouTube video") public class YoutubeComponent { @RequestMapping("/youtube") public String render(Node node, ModelMap model) throws RepositoryException { model.put("videoId", node.getProperty("videoId").getString()); return "components/youtube"; } @TabFactory("Content") public void contentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("videoId").label("Video ID") ); } } 35 <iframe width="100%" height="400" src="//www.youtube.com/ embed/${videoId}" frameborder="0" allowfullscreen></iframe> <!-- In the view -->
  • 43.
    Dialogs and theClass 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")    );  } } 36
  • 44.
    Dialogs and theClass 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")    );  } } 36 <head> <meta name="description" content="${content.metaDescription}"/> <meta name="keywords" content="${content.metaKeywords}" />  <meta name="author" content="${content.metaAuthor}" /> </head> <!-- In the view -->
  • 45.
    Dynamic Dialog @Template(id="myModule:components/bookCategory", title="Bookcategory") @TemplateDescription("A list of books in a given category.") @Controller public class BookCategoryComponent {    @Autowired    private SalesApplicationWebService service;    @RequestMapping("/bookcategory")    public String render(ModelMap model, Node content) 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)        );    } } 37
  • 46.
    Dynamic Dialog ... @TabFactory("Content") public voidcontentTab(UiConfig cfg, TabBuilder tab) { Collection<String> categories = service.getBookCategories(); tab.fields( cfg.fields.select("category").label("Category").options(categories)  ); } ... 38
  • 47.
    Input Validation ... public voidcontentTab(UiConfig cfg, TabBuilder tab) { tab.fields( cfg.fields.text("name").label("Name").required(), cfg.fields.text("email").label("Email")⏎ .validator(cfg.validators.email()) ); } ... 39
  • 48.
    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/");    } } 40
  • 49.
    Available Components @Controller @Area("promos") @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public staticclass PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } } 41
  • 50.
    Area Inheritance @Controller @Area("promos") @Inherits @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public staticclass PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } } 42
  • 51.
  • 52.
  • 53.
    Web container (Tomcat) WebArchive Magnolia CMS Filter chain CMS Context Security Cache Aggregation Hierarchical content repository Content Custom filter Request Rendering Rendering engine Response 45
  • 54.
    Form Submission /* Standardannotations 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"; } 46
  • 55.
    But wait aminute … 47 Caused by: org.springframework.web.util.NestedServletException: Reque java.lang.IllegalStateException at org.springframework.web.servlet.FrameworkServlet.processRequest(F at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkS at javax.servlet.http.HttpServlet.service(HttpServlet.java:617) at info.magnolia.module.blossom.render.BlossomDispatcherServlet.forw at info.magnolia.module.blossom.render.BlossomTemplateRenderer.rende ... 92 more Caused by: java.lang.IllegalStateException … the response has been sent.
  • 56.
    Controller Pre-execution 48 C MV PAGE C M V Area C M V Component C M V Component C M V Component C
  • 57.
    CPE Implementation 49 Codein 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” /> ...
  • 58.
  • 59.
    Spring Web Flow 51 REQUESTLANDING PAGE SPRING WEB FLOW
  • 60.
    Spring Web Flow 52 REQUESTLANDING PAGE FORM SUBMIT? SPRING WEB FLOW
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
    Configuring Spring Beans58 <blossom:observed-bean default-class="com.sample.DiscountService" path="/modules/myModule/beans/discountService" />
  • 67.
  • 68.
  • 69.
    What Do YouWin? 61
  • 70.
  • 71.
  • 72.
    64 Shorter Learning Curve MVC Use Existing Spring Apps& Components @Annotations Loose Coupling Dependency Injection Code-driven Dialogs Test-driven Development Spring IdiomsSecurity Scalability Mobile & Touch Faster Development Staging Maglev
  • 73.
    64 Shorter Learning Curve MVC Use Existing Spring Apps& Components @Annotations Loose Coupling Dependency Injection Code-driven Dialogs Test-driven Development Spring IdiomsSecurity Scalability Mobile & Touch Faster Development Staging Questions? Maglev
  • 74.
    Want to LearnMore? 65 Talk to us in the exhibit hall Attend Blossom Q & A webinar on Sept. 26 http://magnolia-cms.com/blossom-qa Visit http://magnolia-cms.com/spring Dashboard for all things Spring in Magnolia
  • 75.
  • 76.
    Image Credits 67 #2: "LongDrive" by Nicholas A. Tonelli (CC-BY 2.0) #3: "Sluggish" by Nicholas A. Tonelli (CC-BY 2.0) #4: Still from "The Matrix" #7: "South Beach Miami Sunset" by Justin Ornellas (CC-BY 2.0) #14: "HBW - Magnolia Edition" by Nana B Agyei (CC-BY 2.0) #16: "Worker" designed by James Fenton from The Noun Project #16: "Database" designed by Ed Jones from The Noun Project #16: "Document" designed by Timur Zima from The Noun Project #22: "Three levels" by Paolo Fefe (CC-ND 2.0) #43:"Headset" designed by Benoît Bâlon from The Noun Project #43: "Engine" released into the Public Domain #43: "Flower" designed by Danilo Gusmão Silveira from The Noun Project #43: "Video Game Controller" designed by "Michael Rowe" from The Noun Project #51: "Anvil" designed by Masrur Mahmood from The Noun Project #52: "Hand" designed by Mikhail Bazilevsky from The Noun Project #53: "NYC Steam" by Don O'Brien (CC-BY 2.0) #56: "Floods 2013: Riverfront Ave" by Ryan Quan (CC-SA 2.0) #61: "3 Strikes for £2.50" by Julian Frost (CC-BY 2.0)
  • 77.
    68 Trademarks Other trademarks arethe property of their respective owners. Magnolia The Pulse are trademarks of Magnolia International Limited.}