Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merged Events back in to core. NO MORE CHANGING OUR MINDS OK!
  • Loading branch information
marcpalmer committed Mar 16, 2012
1 parent b422589 commit fecd749
Show file tree
Hide file tree
Showing 39 changed files with 2,366 additions and 266 deletions.
42 changes: 42 additions & 0 deletions PlatformCoreGrailsPlugin.groovy
Expand Up @@ -16,6 +16,11 @@
* limitations under the License.
*/

import org.grails.plugin.platform.events.dispatcher.DefaultEventsDispatcher
import org.grails.plugin.platform.events.dispatcher.GormTopicSupport1X
import org.grails.plugin.platform.events.dispatcher.GormTopicSupport2X
import org.grails.plugin.platform.events.publisher.DefaultEventsPublisher
import org.grails.plugin.platform.events.registry.DefaultEventsRegistry
import org.springframework.core.io.FileSystemResource

class PlatformCoreGrailsPlugin {
Expand Down Expand Up @@ -124,6 +129,38 @@ Grails Plugin Platform Core APIs
grailsConventions(org.grails.plugin.platform.conventions.Conventions) {
grailsApplication = ref('grailsApplication')
}

// Events API
task.executor(id: "grailsTopicExecutor", 'pool-size': 10)//todo config

//init api bean
if (grailsVersion.startsWith('1')) {
gormTopicSupport(GormTopicSupport1X)
} else {
gormTopicSupport(GormTopicSupport2X) {
translateTable = [
'PreInsertEvent': 'beforeInsert', 'PreUpdateEvent': 'beforeUpdate', 'PreLoadEvent': 'beforeLoad',
'PreDeleteEvent': 'beforeDelete', 'ValidationEvent': 'beforeValidate', 'PostInsertEvent': 'afterInsert',
'PostUpdateEvent': 'afterUpdate', 'PostDeleteEvent': 'afterDelete', 'PostLoadEvent': 'afterLoad',
'SaveOrUpdateEvent': 'onSaveOrUpdate'
]
}
}

grailsEventsRegistry(DefaultEventsRegistry)
grailsEventsPublisher(DefaultEventsPublisher) {
grailsEventsRegistry = ref('grailsEventsRegistry')
taskExecutor = ref('grailsTopicExecutor')
persistenceInterceptor = ref("persistenceInterceptor")
gormTopicSupport = ref("gormTopicSupport")
}
grailsEventsDispatcher(DefaultEventsDispatcher)

grailsEvents(org.grails.plugin.platform.events.Events) {
grailsEventsRegistry = ref('grailsEventsRegistry')
grailsEventsPublisher = ref('grailsEventsPublisher')
grailsEventsDispatcher = ref('grailsEventsDispatcher')
}
}

def doWithDynamicMethods = { ctx ->
Expand All @@ -141,6 +178,7 @@ Grails Plugin Platform Core APIs

register ctx.grailsPluginConfiguration.injectedMethods
register ctx.grailsSecurity.injectedMethods
register ctx.grailsEvents.injectedMethods
}

def doWithApplicationContext = { applicationContext ->
Expand All @@ -153,6 +191,10 @@ Grails Plugin Platform Core APIs
case Class:
ctx.grailsInjection.applyTo(event.source)
// @todo add call to update auto nav for controllers, we badly need "onreload" events for this

if (application.isServiceClass(event.source)) {
ctx.grailsEvents.reloadListener(event.source)
}
break
}
}
Expand Down
212 changes: 1 addition & 211 deletions README.md
Expand Up @@ -514,219 +514,9 @@ If no page layout mapping is supplied, the name defaults to "theme" and would lo

### Security API

Plugins that provide high level functionality often need to make use of
security - to either restrict access to their features or to interact with the
user's identity.

This API provides the most basic security features to enable this
interoperatibility, using a bridging interface that security plugins must
implement to actually provide these services.
### Navigation API

#### Security tags

Plugins and applications can use this set of uniform tags instead of
plugin-specific security tags for the most common features.

<s:userName/>

<s:userInfo property="email"/>

<s:ifPermitted role="ROLE_ADMIN">
...
</s:ifPermitted>

<s:ifNotPermitted role="ROLE_ADMIN">
...
</s:ifNotPermitted>

<a href="${s.createLoginLink()}">Log in</a>

<a href="${s.createLogoutLink()}">Log out</a>

<a href="${s.createSignupLink()}">Sign up</a>

These tags will support object-level permissions, not just role= in future.

#### Security bean

The Security bean provides access to the basic security functions. These are passed through to the security bridge implementation.

String getUserName()
/**
* Get user info object i.e. email address, other stuff defined by the application
*/
def getUserInfo()

def userHasRole(role)

/**
* Can the current user access this object to perform the named action?
* @param object The object, typically domain but we don't care what
* @param action Some application-defined action string i.e. "view" or "edit"
*/
def userIsAllowed(object, action)

def ifUserHasRole(role, Closure code)

/**
* Can the current user access this object to perform the named action?
* @param object The object, typically domain but we don't care what
* @param action Some application-defined action string i.e. "view" or "edit"
*/
def ifUserIsAllowed(object, action, Closure code)

You simply auto wire this bean into your classes using the name "grailsSecurity"

#### Security Provider Interface

The information required to implement the basic security features is provided
by an implementation of the SecurityBridge interface.

An application or more typically a plugin that provides the security
implementation will have to implement this interface and expose it as a bean
called grailsSecurityBridge.

The interface is defined here:

interface SecurityBridge {

/**
* Get user id string i.e. "marcpalmer" of the currently logged in user, from whatever
* underlying security API is in force
*/
String getUserName()

/**
* Get user info object i.e. email address, other stuff defined by the application
*/
def getUserInfo()

/**
* Return true if the current logged in user has the specified role
*/
boolean userHasRole(role)

/**
* Can the current user access this object to perform the named action?
* @param object The object, typically domain but we don't care what
* @param action Some application-defined action string i.e. "view" or "edit"
*/
boolean userIsAllowed(object, action)

/**
* Create a link to the specified security action
* @param action One of "login", "logout", "signup"
*/
String createLink(String action)
}


syst### Navigation API

Site navigation is a combination of two concerns - declaring what navigation
items there are in the site, and what the structure of them is - as presented to the use.

Specifying items and structure separately is not DRY, and applications also
need to have the ability to change the structure to suit them - therefore the
structure information provided by a plugin is merely a guide, and inherently
disposable.

#### DSL

A Navigation artefact is used to define navigation items, positioned in their
default structure. By combining these two different categories data, we end up
with a more DRY approach:

navigation = {
// Everything here is under "user" in the structure
user {
// This defines the "<pluginName>.logout" item
logout {
// url is string or g.createLink map
url controller:'sec', action:'logout'
isVisible { ... }
isEnabled { ... }
}
login {
uri controller:'sec', action:'login'
isVisible { ... }
isEnabled { ... }
}
}
}

Items are specified in a default structure (i.e. user > logout) that the
application can override, and ordering is implied. Unique id of each item is
the endpoint name in the DSL i.e. "logout" prefixed with the plugin name i.e.
"security.logout"

Sub items are supported:

navigation = {
mail {
url controller:'mail'
isVisible { ... }
isEnabled { ... }
subItems {
inbox {
url action:'inbox'
}
archive {
url action:'archive'
}
}
}
}

Here the controller is inherited by subitems. The subitems have implicit ids
of "<pluginName>.inbox" etc.

The subItems clause is merely a way to express the default placement of these
items and gain some inheritance of values from the "parent" item, the
application can re-structure them. Another way of writing the above is:

navigation = {
mail {
url controller:'mail'
isVisible { ... }
isEnabled { ... }
}

mail {
inbox {
url controller:'mail', action:'inbox'
}

archive {
url controller:'mail', action:'archive'
}
}
}


The text for items is resolved using i18n messages based on the id of the
navigation entry (not its placement in the structured).

If no isXXXX closure is specified for an item subordinate in the structure,
the isXXX handlers of the ancestor will be used.

#### Application menu re-structuring/re-ordering

Applications need to be able to re-organise menu items into the structure and ordering they desire.

This is done through configuration (or DSL with empty entries?):

grails {
navigation {
user = ['security.login', 'security.logout']
admin = ['admin-home', 'admin-advanced', 'security.logout']
}
}

#### Tags

#### NavigationManager bean

### Events API

Expand Down
@@ -0,0 +1,61 @@
/* Copyright 2011-2012 the original author or authors:
*
* Marc Palmer (marc@grailsrocks.com)
* Stéphane Maldini (stephane.maldini@gmail.com)
*
* 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.
*/
package org.grails.plugin.platform.test

import org.grails.plugin.platform.events.Listener
/**
* @file
* @author Stephane Maldini <smaldini@doc4web.com>
* @version 1.0
* @date 02/01/12
* @section DESCRIPTION
*
* [Does stuff]
*/
class SampleService {

static transactional = true

static events = {
testEvent when: {
it.source == 'plugin-platform'
}
}

@Listener('sampleHello')
void testEvent(test) {
println "Hello $test"
}

@Listener
void beforeInsert(Book book) {
println "will insert $book.title"
}

@Listener
void afterLoad(Author author) {
println "will load $author.name"
}

@Listener('sampleHello')
def testEvent3(test) {
println "Hello (bis) - $test"
true
}
}
Expand Up @@ -25,18 +25,17 @@ class NavigationTagLib {
def grailsNavigation

def menu = { attrs ->
println "ATTRS: $attrs"
def cssClass = attrs.class != null ? attrs.class : 'nav primary'
def id = attrs.id ? "id=\"${attrs.id.encodeAsHTML()}\" " : ''
def scope = attrs.scope
if (!scope) {
scope = 'app'
}
println "scope: $scope is a (${scope.getClass()})"
if (!(scope instanceof String)) {
println "xxxscope: $scope"
scope = scope.id
}

println "scope: $scope"
if (log.debugEnabled) {
log.debug "Rendering menu for scope [${scope}]"
}
Expand All @@ -45,7 +44,7 @@ class NavigationTagLib {

def scopeNode = grailsNavigation.scopeByName(scope)
if (scopeNode) {
out << "<ul class=\"nav primary\">"
out << "<ul ${id}class=\"${cssClass}\">"
for (n in scopeNode.children) {
if (n.visible) {
def liClass
Expand All @@ -56,8 +55,8 @@ class NavigationTagLib {
liClass = ' class="disabled"'
}
out << "<li${liClass ?: ''}>"
def linkArgs = [:]
out << g.link(n.linkArgs, g.message(code:n.titleMessageCode, default:n.titleDefault))
def linkArgs = n.linkArgs.clone() // Clone! naughty g.link changes them otherwise. Naughty g.link!
out << g.link(linkArgs, g.message(code:n.titleMessageCode, default:n.titleDefault))
out << "</li>"
}
}
Expand Down

0 comments on commit fecd749

Please sign in to comment.