Spring and Web Content Management

7,660 views

Published on

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.

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,660
On SlideShare
0
From Embeds
0
Number of Embeds
3,068
Actions
Shares
0
Downloads
38
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Spring and Web Content Management

  1. 1. © 2013 SpringOne 2GX. All rights reserved. Spring & Web Content Management Tobias Mattsson & Daniel Lipp Magnolia International
  2. 2. 2
  3. 3. 3
  4. 4. 4
  5. 5. Sr. Software Engineer, Magnolia Lead developer of Magnolia’s Spring integration Spring Framework user since 2005 Tobias Mattsson 5
  6. 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
  7. 7. ® 7
  8. 8. 8
  9. 9. @magnolia_cms #magnolia_cms 9
  10. 10. Spring Spring MVC Code, beautiful code Dependency Injection Integrations Business logic 10
  11. 11. CMS Security Multi-lingual User interface Image manipulation Content versioning 11
  12. 12. CMS Security Multi-lingual User interface Image manipulation Content versioning Spring Spring MVC Code, beautiful code Dependency Injection Integrations Business logic 12
  13. 13. USES IFRAMES LIKE IT'S 1999 13
  14. 14. Magnolia + Spring = Blossom 14
  15. 15. @Template 15 or, "How Blossom is Built"
  16. 16. TEMPLATEREQUEST CONTENT CMS 16
  17. 17. TEMPLATEREQUEST CONTENT CMS + Blossom 17 CONTROLLER MODEL VIEW
  18. 18. Page Template @Controller @Template(id="myModule:pages/main", title="Main") public class MainTemplate {    @RequestMapping("/main")    public String render(ModelMap model) {        return "pages/main";    } } 18
  19. 19. PAGES CONTAIN 0:n AREAS 19 PAGE
  20. 20. PAGES CONTAIN 0:n AREAS 19 PAGE AREA A R E A AREA
  21. 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
  22. 22. 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
  23. 23. 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
  24. 24. Views 22
  25. 25. Rendering an Area FreeMarker [@cms.area name="main" /] JSP <cms:area name="main" /> 23
  26. 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. 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. 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. 29. Magnolia JCR API Persistence Manager Jackrabbit Hierarchical content repository In memoryDatabaseFile system or or 27
  30. 30. 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
  31. 31. 29 Dialogs
  32. 32. 30 GWT with server-side state Component-based UI Cross-browser compatibility (via GWT) Rich component library Fast development pace
  33. 33. 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
  34. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  46. 46. 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
  47. 47. 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
  48. 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. 49. Available Components @Controller @Area("promos") @AvailableComponentClasses({TextComponent.class,                            ShoppingCartComponent.class}) public static class PromosArea {    @RequestMapping("/main/promos")    public String render() {        return "areas/promos";    } } 41
  50. 50. 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
  51. 51. SERVLET CONTAINER RENDERING FILTER RENDERING ENGINE BLOSSOM DISPATCHER SERVLET CONTROLLER 43 How Blossom Works
  52. 52. Magnolia Server Data Workflow STK Modules Magnolia Core Custom module Custom module DMS Imaging Custom Theme Module JAR - templates - resources 44
  53. 53. 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
  54. 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. 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.
  56. 56. 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
  57. 57. 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” /> ...
  58. 58. Spring Web MVC + Content 50
  59. 59. Spring Web Flow 51 REQUEST LANDING PAGE SPRING WEB FLOW
  60. 60. Spring Web Flow 52 REQUEST LANDING PAGE FORM SUBMIT? SPRING WEB FLOW
  61. 61. Deployment & Scaling 53
  62. 62. Author Magnolia Public A Magnolia Public B Magnolia Firewall Activate Internal Network Internet LoadBalancer JCR JCR JCR 54
  63. 63. 55 Backend Application DB Author Magnolia JCR Public Magnolia JCRPublic Magnolia JCR Clustering
  64. 64. Dealing with Change 56
  65. 65. Filter Chain in JCR 57
  66. 66. Configuring Spring Beans 58 <blossom:observed-bean default-class="com.sample.DiscountService" path="/modules/myModule/beans/discountService" />
  67. 67. example.com/vanity 59
  68. 68. Changing Resources Live 60 CSS FreeMarker Images Javascript Views
  69. 69. What Do You Win? 61
  70. 70. 62
  71. 71. 63
  72. 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. 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. 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
  75. 75. Thank You! 66
  76. 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. 77. 68 Trademarks Other trademarks are the property of their respective owners. Magnolia The Pulse are trademarks of Magnolia International Limited.}

×