Want Spring seamlessly available inside a CMS? How about being able to integrate existing Spring apps into your CMS without rewriting a bunch of code? What about a robust CMS solution for Grails? Meet Magnolia, a mature open source CMS written in Java on the best of the Java stack (including Spring and Groovy.)
This session will introduce Magnolia's Spring integration and give you a tour of its architecture, key features and use. Along the way, you'll also get insights into the development of Magnolia's Spring integration, an overview of Magnolia's key features (like workflows, innovative multi-channel support and a damn fine user experience that includes touch devices), and brief tutorials on solving some key content management challenges faced by Spring developers. There will also be a quick detour into Magnolia's Groovy shell and MagLev, a Grails plugin for Magnolia.
5. Sr. Software Engineer, Magnolia
Lead developer of Magnolia’s Spring integration
Spring Framework user since 2005
Tobias Mattsson
5
6. 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
21. 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
26. 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
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
34. 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
35. 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
36. @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
37. @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
38. @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 -->
39. 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
40. 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 -->
41. 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
42. 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 -->
43. 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
44. 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 -->
45. 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
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";
}
46
55. 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.
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 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
76. 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)
77. 68
Trademarks
Other trademarks are the property of their respective owners.
Magnolia
The Pulse are trademarks of
Magnolia International Limited.}