-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathFeedsGrailsPlugin.groovy
152 lines (141 loc) · 5.52 KB
/
FeedsGrailsPlugin.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
* Copyright 2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import grails.util.GrailsUtil
import com.sun.syndication.io.SyndFeedOutput
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler
import feedsplugin.FeedBuilder
/**
*
* A plugin that renders RSS/Atom feeds, or any other formats supported by the ROME API.
*
* It works like this - you call render() method from your controller action as normal, but instead
* of using a view you specify a feedType parameter and a feedVersion parameter, as well as a closure
* that uses a custom feed builder to define the feed. Currently feed types "rss" and "atom" are supported.
*
* Example:
*
* class YourController {
* def feed = {
* render(feedType:"rss", feedVersion:"2.0") {
* title = "My test feed"
* link = "http://your.test.server/yourController/feed"
*
* Article.list().each() {
* entry(it.title) {
* link = "http://your.test.server/article/${it.id}"
* it.content // return the content
* }
* }
* }
* }
* }
*
* Some of the feed formats have different required properties that you must set on the feed (AKA Channel)
* and the child nodes (entry and content). Exceptions will occur if you don't meet these constraints.
*
* The feed builder is very forgiving. The general pattern is:
*
* 1. set feed top-level properties i.e. link and title
* 2. define entry nodes and properties of them
* 3. define content for entry nodes, and properties of content
*
* There are some smarts, namely:
*
* * entry nodes can take a title parameter as a shortcut.
* * content nodes can take a string parameter which is used as the text/plain content body
* * entry node bodies can just return an object, the string value of which will be used as text/plain content for the entry
* * entry and content nodes can take a map as parameter, to set any properties of the node
*
* Common properties of entry nodes you may want to set:
*
* publishedDate - the date the entry was published
* categories - the list of categories
* author - author name
* link - link to the online entry
*
* Common properties of content nodes you may want to set:
*
* type - mime type
*
* Enclosures and descriptions are currently not directly supported by the builder but can be constructed
* using the ROME API directly.
*
* @author Marc Palmer ([email protected])
*/
class FeedsGrailsPlugin {
def version = "1.5"
def grailsVersion = "1.1 > *"
def dependsOn = [controllers:'1.1 > *']
def loadAfter = ['controllers']
def observe = ['controllers']
def author = "Marc Palmer"
def authorEmail = "[email protected]"
def title = "Render RSS/Atom feeds with a simple builder"
def description = '''
This plugin adds a feedType and feedVersion parameters to the render method of controllers, which if passed
a valid feed type such as "rss" or "atom" and a version will expect a closure to be passed with render,
which will render a feed using a custom builder. The FeedBuilder used for this accepts entry and content nodes, any properties
of which can be set within the builder. These are beans from the ROME API so all properties there
should work.
'''
def documentation = "http://grails.org/Feeds+Plugin"
static MIME_TYPES = [
atom:'application/atom+xml',
rss:'application/rss+xml'
]
def doWithSpring = {
}
def doWithApplicationContext = { applicationContext ->
}
def doWithWebDescriptor = { xml ->
}
def doWithDynamicMethods = { ctx ->
application.controllerClasses.each() { controllerClass ->
replaceRenderMethod(controllerClass)
}
}
def onChange = { event ->
if(application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) {
def clz = application.getControllerClass(event.source?.name)
replaceRenderMethod(clz)
}
}
def onApplicationChange = { event ->
}
void replaceRenderMethod(controllerClass) {
def oldRender = controllerClass.metaClass.pickMethod("render", [Map, Closure] as Class[])
controllerClass.metaClass.render = { Map params, Closure closure ->
if (params.feedType) {
// Here we should assert feed type is supported
def builder = new FeedBuilder()
builder.feed(closure)
def type = params.feedType
def version = params.feedVersion
def mimeType = params.contentType ? params.contentType : MIME_TYPES[type]
if (!mimeType) {
throw new IllegalArgumentException("No mime type known for feed type [${type}]")
}
response.contentType = mimeType
response.characterEncoding = "UTF-8"
SyndFeedOutput output = new SyndFeedOutput()
output.output(builder.makeFeed(type, version),response.writer)
} else {
// Defer to original render method
oldRender.invoke(delegate, [params, closure] as Object[])
}
}
}
}