1. Jason Stonebraker
  2. JSONExclusionMarshaller

Commits

Jason Stonebraker  committed f897625 Merge

Merge branch 'release/0.3'

  • Participants
  • Parent commits 020bc1a, 107dc26
  • Branches master

Comments (0)

Files changed (4)

File JsonExclusionMarshallerGrailsPlugin.groovy

View file
 import grails.converters.JSON
-import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass
+import groovy.lang.GroovyObject
+import groovy.lang.GroovyClassLoader
+import groovy.util.ConfigSlurper
+import groovy.util.ConfigObject
+
+import java.beans.PropertyDescriptor
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+
+import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException
+import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller
+import org.codehaus.groovy.grails.web.json.JSONWriter
+import org.springframework.beans.BeanUtils
+
 
 class JsonExclusionMarshallerGrailsPlugin {
-    def version = "0.2"
+    def version = "0.3"
     def grailsVersion = "2.0 > *"
     def pluginExcludes = [
         "grails-app/domain/**"
 
                 jsonConverter.createNamedConfig(configName) {
                     it.registerObjectMarshaller(type) {
-                        def obj = it // Object being marshalled
-                        def gdc = new DefaultGrailsDomainClass(type)
+                        def o = it // Object being marshalled
                         def map = [:]
 
-                        map["${gdc.identifier.name}"] = obj."${gdc.identifier.name}"
-                        map['class'] = obj.getClass().name
+                        try {
+                            PropertyDescriptor[] properties = BeanUtils.getPropertyDescriptors(o.getClass())
+                            for (PropertyDescriptor property : properties) {
+                                String propertyName = property.getName()
+                                Method readMethod = property.getReadMethod()
+
+                                if (readMethod != null
+                                    && !(propertyName.equals("metaClass"))) {
+                                    Object value = readMethod.invoke(o, (Object[]) null)
+                                    map[propertyName] = value
+                                }
+                            }
+                            
+                            Field[] fields = o.getClass().getDeclaredFields()
+                            for (Field field : fields) {
+                                int modifiers = field.getModifiers()
+                                if (Modifier.isPublic(modifiers)
+                                    && !(Modifier.isStatic(modifiers)
+                                        || Modifier.isTransient(modifiers))) {
+                                    map[field.getName()] = field.get(o)
+                                }
+                            }
 
-                        gdc.persistentProperties.each { property ->
-                            map["${property.name}"] = obj."${property.name}"
+                        } catch (ConverterException ce) {
+                            throw ce
+                        } catch (Exception e) {
+                            throw new ConverterException(
+                                "Error converting Bean with class "
+                                + o.getClass().getName(), e)
                         }
 
+                        propertiesToExclude += this.getConfiguredGlobalExclusions(application)
                         propertiesToExclude.each { property -> map.remove(property) }
-
+                        
                         return map
                     }
                 }
             return impl(args)
         }
     }
+
+    private List getConfiguredGlobalExclusions(application) {
+        GroovyClassLoader classLoader = new GroovyClassLoader(getClass().getClassLoader())
+        ConfigObject config
+        try {
+           config = new ConfigSlurper().parse(classLoader.loadClass('JsonExclusionMarshallerDefaultConfig'))
+        } catch (Exception e) {/*??handle or what? use default here?*/}
+        
+        def configGlobalExclusions = config.merge(application.config).jsonExclusionMarshaller.globalExclusions
+
+        return (configGlobalExclusions) ? configGlobalExclusions.split(/,\s*/).collect { it } : []
+    }
 }

File grails-app/conf/JsonExclusionMarshallerDefaultConfig.groovy

View file
+// jsonExclusionMarshaller.globalExclusions = "class, attached, errors, properties, version"

File grails-app/domain/stonebraker/jxm/TestStudent.groovy

-package stonebraker.jxm
-
-class TestStudent {
-	String firstName
-	String lastName
-	Number gradePointAverage
-	String studentID
-	String socialSecurityNumber
-}

File test/integration/stonebraker/jxm/TestStudentTests.groovy

-package stonebraker.jxm
-
-import static org.junit.Assert.*
-import org.junit.*
-import grails.converters.JSON
-
-class TestStudentTests {
-    // Logger log = LoggerFactory.getLogger(TestStudentTests)
-
-    @Test
-    void testCanPrintATestStudentAsJSON() {
-        // Given
-        def json,
-            resultTeachersWillSee,
-            resultOtherStudentsWillSee,
-            resultTestAdministratorsWillSee,
-
-        student = new TestStudent([
-            firstName: "Tobias",
-            lastName: "Funke",
-            gradePointAverage: 3.6,
-            studentID: "FS-210-7312",
-            socialSecurityNumber: "555-55-5555"
-        ])
-
-        // The meta programming technique being used here is synthesized method injection. It's cool, but
-        // it may warrant some explanation. Though hopefully not a lot.
-
-        // In order to exclude class properties from JSON output, we create 'excluders.' Don't go looking for
-        // an 'Excluder' class, you won't find it. That's just what I call ObjectMarshallers that exclude
-        // properties from rendering.
-
-        // Excluders are created using: excludeFor*(Class class, List<String> propertiesToExclude)
-        // Each excluder needs an excluder name, the target class name, and a list of properties to exclude from that class.
-
-        // We'll look at the 'excludeForTeachers' excluder.
-        // Name: "excludeForTeachers" <--- You can use anything you want after "excludeFor", in this case I
-        //       chose "Teachers" since in my domain, teachers aren't allowed to see a student's social
-        //       security number. In this way, I'm able to use domain specific language in code.
-        // Class: TestStudent <--- The domain class being marshalled
-        // List<String>: ['socialSecurityNumber', 'id', 'class'] <--- The properties that will be excluded
-        //       from the JSON output.
-
-        // Usage: Once you've created an excluder, you simply call JSON.use('excludeForWhatever') { ... }
-        // ... and of course do stuff within the closure per the example below.
-        // As seen here, we can create multiple excluders and use them as needed.
-
-        JSON.excludeForTeachers(TestStudent, ['socialSecurityNumber', 'id', 'class'])
-        JSON.excludeForOtherStudents(TestStudent, ['gradePointAverage', 'studentID', 'socialSecurityNumber', 'id', 'class'])
-        JSON.excludeForTestAdministrators(TestStudent, ['gradePointAverage', 'socialSecurityNumber', 'id', 'class'])
-
-        // When
-        JSON.use('excludeForTeachers') {
-            json = new JSON(student)
-        }
-        resultTeachersWillSee = json.toString()
-
-        JSON.use('excludeForOtherStudents') {
-            json = new JSON(student)
-        }
-        resultOtherStudentsWillSee = json.toString()
-
-        // We can also embed the excluder within use('deep')
-        // though it has no affect our simple class.
-        JSON.use('deep') {
-            JSON.use('excludeForTestAdministrators') {
-                json = new JSON(student)
-                json.prettyPrint = false
-                resultTestAdministratorsWillSee = json.toString()
-            }
-        }
-
-        // Then
-        assert resultTeachersWillSee == '{"firstName":"Tobias","gradePointAverage":3.6,"lastName":"Funke","studentID":"FS-210-7312"}'
-        assert resultOtherStudentsWillSee == '{"firstName":"Tobias","lastName":"Funke"}'
-        assert resultTestAdministratorsWillSee == '{"firstName":"Tobias","lastName":"Funke","studentID":"FS-210-7312"}'
-    }
-}
-
-/*
-
-What's actually happening? Basically the excludeFor*() method creates a Named Configuration with a name of
-'excludeForWhateverYouDecideToNameIt'. The Named Configuration then registers a custom ObjectMarshaller for the
-TestStudent domain class. The custom object marshaller is built into the excludesFor* sythesized method. It
-removes the properties we provided to it in the list. The JSON converter then outputs the desired JSON formatted string.
-
-*/
-
-
-