001/* $Id: OverlappingCallMethodRuleTestCase.java 1125723 2011-05-21 15:04:46Z simonetripodi $ 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.commons.digester3; 020 021import static org.apache.commons.digester3.binder.DigesterLoader.newLoader; 022import static org.junit.Assert.assertEquals; 023 024import java.io.IOException; 025import java.io.StringReader; 026 027import org.apache.commons.digester3.binder.AbstractRulesModule; 028import org.junit.Test; 029import org.xml.sax.SAXException; 030 031/** 032 * <p> 033 * Tests for situations where CallMethodRule instances and their parameters overlap each other. 034 * </p> 035 */ 036public class OverlappingCallMethodRuleTestCase 037{ 038 039 // --------------------------------------------------- Overall Test Methods 040 041 String itemId; 042 043 String itemName; 044 045 public void setItemId( String id ) 046 { 047 itemId = id; 048 } 049 050 public void setItemName( String name ) 051 { 052 itemName = name; 053 } 054 055 // ------------------------------------------------ Individual Test Methods 056 057 @Test 058 public void testItem1() 059 throws SAXException, IOException 060 { 061 StringBuilder input = new StringBuilder(); 062 input.append( "<root>" ); 063 input.append( " <item id='1'>anitem</item>" ); 064 input.append( "</root>" ); 065 066 Digester digester = newLoader( new AbstractRulesModule() 067 { 068 069 @Override 070 protected void configure() 071 { 072 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 ) 073 .then() 074 .callParam().fromAttribute( "id" ) 075 .then() 076 .callMethod( "setItemName" ).withParamCount( 1 ) 077 .then() 078 .callParam(); 079 } 080 081 }).newDigester(); 082 083 this.itemId = null; 084 this.itemName = null; 085 digester.push( this ); 086 digester.parse( new StringReader( input.toString() ) ); 087 088 assertEquals( "1", this.itemId ); 089 assertEquals( "anitem", this.itemName ); 090 } 091 092 @Test 093 public void testItem2() 094 throws SAXException, IOException 095 { 096 StringBuilder input = new StringBuilder(); 097 input.append( "<root>" ); 098 input.append( " <item id='1'>anitem</item>" ); 099 input.append( "</root>" ); 100 101 Digester digester = newLoader( new AbstractRulesModule() 102 { 103 104 @Override 105 protected void configure() 106 { 107 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 ) 108 .then() 109 .callParam().fromAttribute( "id" ) 110 .then() 111 .callMethod( "setItemName" ).withParamCount( 1 ) 112 .then() 113 .callParam(); 114 } 115 116 }).newDigester(); 117 118 this.itemId = null; 119 this.itemName = null; 120 digester.push( this ); 121 digester.parse( new StringReader( input.toString() ) ); 122 123 assertEquals( "1", this.itemId ); 124 assertEquals( "anitem", this.itemName ); 125 } 126 127 @Test 128 public void testItem3() 129 throws SAXException, IOException 130 { 131 StringBuilder input = new StringBuilder(); 132 input.append( "<root>" ); 133 input.append( " <item>1</item>" ); 134 input.append( "</root>" ); 135 136 Digester digester = newLoader( new AbstractRulesModule() 137 { 138 139 @Override 140 protected void configure() 141 { 142 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 ) 143 .then() 144 .callParam().fromAttribute( "id" ) 145 .then() 146 .callMethod( "setItemName" ).withParamCount( 1 ) 147 .then() 148 .callParam(); 149 } 150 151 }).newDigester(); 152 153 digester.addCallMethod( "root/item", "setItemId", 1 ); 154 digester.addCallParam( "root/item", 0 ); 155 digester.addCallMethod( "root/item", "setItemName", 1 ); 156 digester.addCallParam( "root/item", 0 ); 157 158 this.itemId = null; 159 this.itemName = null; 160 digester.push( this ); 161 digester.parse( new StringReader( input.toString() ) ); 162 163 assertEquals( "1", this.itemId ); 164 assertEquals( "1", this.itemName ); 165 } 166 167 /** 168 * This is an "anti-test" that demonstrates how digester can <i>fails</i> to produce the correct results, due to a 169 * design flaw (or at least limitation) in the way that CallMethodRule and CallParamRule work. 170 * <p> 171 * The following sequence always fails: 172 * <ul> 173 * <li>CallMethodRule A fires (pushing params array)</li> 174 * <li>CallMethodRule B fires (pushing params array)</li> 175 * <li>params rule for A fires --> writes to params of method B!</li> 176 * <li>params rule for B fires --> overwrites params for method B</li> 177 * </ul> 178 * The result is that method "b" appears to work ok, but method "a" loses its input parameters. 179 * <p> 180 * One solution is for CallParamRule objects to know which CallMethodRule they are associated with. Even this might 181 * fail in corner cases where the same rule is associated with multiple patterns, or with wildcard patterns which 182 * cause a rule to fire in a "recursive" manner. However implementing this is not possible with the current digester 183 * design. 184 */ 185 @Test 186 public void testItem4() 187 throws SAXException, IOException 188 { 189 StringBuilder input = new StringBuilder(); 190 input.append( "<root>" ); 191 input.append( " <item>" ); 192 input.append( " <id value='1'/>" ); 193 input.append( " <name value='name'/>" ); 194 input.append( " </item>" ); 195 input.append( "</root>" ); 196 197 Digester digester = newLoader( new AbstractRulesModule() 198 { 199 200 @Override 201 protected void configure() 202 { 203 forPattern( "root/item" ).callMethod( "setItemId" ).withParamCount( 1 ) 204 .then() 205 .callMethod( "setItemName" ).withParamCount( 1 ); 206 forPattern( "root/item/id" ).callParam().fromAttribute( "value" ); 207 forPattern( "root/item/name" ).callParam().fromAttribute( "value" ); 208 } 209 210 }).newDigester(); 211 212 this.itemId = null; 213 this.itemName = null; 214 digester.push( this ); 215 digester.parse( new StringReader( input.toString() ) ); 216 217 // These are the "correct" results 218 // assertEquals("1", this.itemId); 219 // assertEquals("name", this.itemName); 220 221 // These are what actually happens 222 assertEquals( null, this.itemId ); 223 assertEquals( "name", this.itemName ); 224 } 225 226 /** 227 * This test checks that CallParamRule instances which fetch data from xml attributes work ok when invoked 228 * "recursively", ie a rule instances' methods gets called in the order 229 * begin[1]/begin[2]/body[2]/end[2]/body[1]/end[1] 230 */ 231 @Test 232 public void testWildcard1() 233 throws SAXException, IOException 234 { 235 StringBuilder input = new StringBuilder(); 236 input.append( "<box id='A1'>" ); 237 input.append( " <box id='B1'>" ); 238 input.append( " <box id='C1'/>" ); 239 input.append( " <box id='C2'/>" ); 240 input.append( " </box>" ); 241 input.append( "</box>" ); 242 243 Digester digester = newLoader( new AbstractRulesModule() 244 { 245 246 @Override 247 protected void configure() 248 { 249 forPattern( "*/box" ).createObject().ofType( Box.class ) 250 .then() 251 .callMethod( "setId" ).withParamCount( 1 ) 252 .then() 253 .callParam().fromAttribute( "id" ) 254 .then() 255 .setNext( "addChild" ); 256 } 257 258 }).newDigester(); 259 260 Box root = new Box(); 261 root.setId( "root" ); 262 digester.push( root ); 263 digester.parse( new StringReader( input.toString() ) ); 264 265 // walk the object tree, concatenating the id strings 266 String ids = root.getIds(); 267 assertEquals( "root A1 B1 C1 C2", ids ); 268 } 269 270 /** 271 * This test checks that CallParamRule instances which fetch data from the xml element body work ok when invoked 272 * "recursively", ie a rule instances' methods gets called in the order 273 * begin[1]/begin[2]/body[2]/end[2]/body[1]/end[1] 274 */ 275 @Test 276 public void testWildcard2() 277 throws SAXException, IOException 278 { 279 StringBuilder input = new StringBuilder(); 280 input.append( "<box>A1" ); 281 input.append( " <box>B1" ); 282 input.append( " <box>C1</box>" ); 283 input.append( " <box>C2</box>" ); 284 input.append( " </box>" ); 285 input.append( "</box>" ); 286 287 Digester digester = newLoader( new AbstractRulesModule() 288 { 289 290 @Override 291 protected void configure() 292 { 293 forPattern( "*/box" ).createObject().ofType( Box.class ) 294 .then() 295 .callMethod( "setId" ).withParamCount( 1 ) 296 .then() 297 .callParam() 298 .then() 299 .setNext( "addChild" ); 300 } 301 302 }).newDigester(); 303 304 Box root = new Box(); 305 root.setId( "root" ); 306 digester.push( root ); 307 digester.parse( new StringReader( input.toString() ) ); 308 309 // walk the object tree, concatenating the id strings 310 String ids = root.getIds(); 311 assertEquals( "root A1 B1 C1 C2", ids ); 312 } 313}