Date post: | 15-Jan-2015 |
Category: |
Technology |
Upload: | spring-io |
View: | 6,762 times |
Download: | 2 times |
© 2013 SpringOne 2GX. All rights reserved.
Spring & WebContent Management
Tobias Mattsson & Daniel LippMagnolia International
2
3
4
Sr. Software Engineer, MagnoliaLead developer of Magnolia’s Spring integration
Spring Framework user since 2005
Tobias Mattsson
5
Daniel LippSr. Software Engineer, Magnolia
17 years of experience as software architect and Java developerGrateful to Spring for inspiring improvements to JEE 6
®
7
8
@magnolia_cms#magnolia_cms
9
SpringSpring MVC
Code, beautiful codeDependency Injection
IntegrationsBusiness logic
10
CMSSecurityMulti-lingualUser interfaceImage manipulationContent versioning
11
CMSSecurityMulti-lingualUser interfaceImage manipulationContent versioning
SpringSpring MVC
Code, beautiful codeDependency Injection
IntegrationsBusiness 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
AREA
AREA
PAGES CONTAIN 0:n AREAS
19
PAGE
AREA
AREA
AREA
AREAS HAVE 0:n COMPONENTSCOMPONENT
COMPONENTEtiam porta sem malesuada magna mollis euismod. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
COMPONENTEtiam 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.
COMPONENT
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 CMS100% Java/J2EE compliantBest of breed open technology stack, including:
JSR-283 / JCR 2.0Vaadin / GWTServlet APIHTML5
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-basedsecurity
Locking
Fine-grained contentand large binaries
Full-textSearch
StructuredQueries
ReferentialIntegrity
TransactionalSchema-less or Strongly-typed
Versioning
29
Dialogs
30
GWT with server-side stateComponent-based UICross-browser compatibility (via GWT)Rich component libraryFast 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.")@Controllerpublic 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
SERVLETCONTAINER
RENDERINGFILTER
RENDERINGENGINE
BLOSSOM DISPATCHERSERVLET CONTROLLER
43
How Blossom Works
Magnolia Server
Dat
a
Wor
kflo
w
STK Mod
ules
Magnolia Core
Cus
tom
m
odul
e
Cus
tom
m
odul
e
DM
S Imag
ing
Cus
tom
Th
eme
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
RequestRendering
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: 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 moreCaused 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 LANDINGPAGE
SPRING WEB FLOW
Spring Web Flow
52
REQUEST LANDINGPAGE
FORMSUBMIT?
SPRING WEB FLOW
Deployment & Scaling 53
Author
MagnoliaPublic A
Magnolia
Public B
Magnolia
Firewall
Activate
Internal Network Internet
Load
Bal
ance
r
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
CSSFreeMarkerImagesJavascriptViews
What Do You Win? 61
62
63
64
ShorterLearning
Curve
MVC
Use ExistingSpring Apps &Components
@Annotations
LooseCoupling
DependencyInjection
Code-drivenDialogs
Test-drivenDevelopment
SpringIdiomsSecurity
Scalability
Mobile& Touch
FasterDevelopment
FasterDevelopment
Cycles
Staging
Maglev
64
ShorterLearning
Curve
MVC
Use ExistingSpring Apps &Components
@Annotations
LooseCoupling
DependencyInjection
Code-drivenDialogs
Test-drivenDevelopment
SpringIdiomsSecurity
Scalability
Mobile& Touch
FasterDevelopment
FasterDevelopment
Cycles
Staging
Questions?
Maglev
Want to Learn More?
65
Talk to us in the exhibit hall
Attend Blossom Q & A webinar on Sept. 26http://magnolia-cms.com/blossom-qa
Visit http://magnolia-cms.com/springDashboard 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.
MagnoliaThe Pulse are trademarks of
Magnolia International Limited.}