| 1 16 package org.apache.cocoon.forms.transformation; 17 18 import org.apache.avalon.excalibur.pool.Recyclable; 19 20 import org.apache.cocoon.forms.FormsConstants; 21 import org.apache.cocoon.forms.formmodel.AggregateField; 22 import org.apache.cocoon.forms.formmodel.DataWidget; 23 import org.apache.cocoon.forms.formmodel.Group; 24 import org.apache.cocoon.forms.formmodel.Repeater; 25 import org.apache.cocoon.forms.formmodel.Struct; 26 import org.apache.cocoon.forms.formmodel.Union; 27 import org.apache.cocoon.forms.formmodel.Widget; 28 import org.apache.cocoon.forms.validation.ValidationError; 29 import org.apache.cocoon.forms.validation.ValidationErrorAware; 30 import org.apache.cocoon.i18n.I18nUtils; 31 import org.apache.cocoon.xml.AbstractXMLPipe; 32 import org.apache.cocoon.xml.SaxBuffer; 33 import org.apache.cocoon.xml.XMLUtils; 34 35 import org.apache.commons.jxpath.JXPathException; 36 import org.xml.sax.Attributes ; 37 import org.xml.sax.ContentHandler ; 38 import org.xml.sax.SAXException ; 39 import org.xml.sax.ext.LexicalHandler ; 40 import org.xml.sax.helpers.AttributesImpl ; 41 42 import java.util.ArrayList ; 43 import java.util.HashMap ; 44 import java.util.LinkedList ; 45 import java.util.List ; 46 import java.util.Locale ; 47 import java.util.Map ; 48 49 64 public class EffectWidgetReplacingPipe extends EffectPipe { 65 66 72 private static final String LOCATION = "location"; 73 74 private static final String AGGREGATE_WIDGET = "aggregate-widget"; 75 private static final String CHOOSE = "choose"; 76 private static final String CLASS = "class"; 77 private static final String CONTINUATION_ID = "continuation-id"; 78 private static final String FORM_TEMPLATE_EL = "form-template"; 79 private static final String GROUP = "group"; 80 private static final String NEW = "new"; 81 private static final String REPEATER = "repeater"; 82 private static final String REPEATER_ROWS = "repeater-rows"; 83 private static final String REPEATER_SIZE = "repeater-size"; 84 private static final String REPEATER_WIDGET = "repeater-widget"; 85 private static final String REPEATER_WIDGET_LABEL = "repeater-widget-label"; 86 private static final String STRUCT = "struct"; 87 private static final String STYLING_EL = "styling"; 88 private static final String UNION = "union"; 89 private static final String VALIDATION_ERROR = "validation-error"; 90 private static final String WIDGET = "widget"; 91 private static final String WIDGET_LABEL = "widget-label"; 92 93 private final AggregateWidgetHandler hAggregate = new AggregateWidgetHandler(); 94 private final ChooseHandler hChoose = new ChooseHandler(); 95 private final ChoosePassThruHandler hChoosePassThru = new ChoosePassThruHandler(); 96 private final ClassHandler hClass = new ClassHandler(); 97 private final ContinuationIdHandler hContinuationId = new ContinuationIdHandler(); 98 private final DocHandler hDocument = new DocHandler(); 99 private final FormHandler hForm = new FormHandler(); 100 private final GroupHandler hGroup = new GroupHandler(); 101 private final NestedHandler hNested = new NestedHandler(); 102 private final NewHandler hNew = new NewHandler(); 103 private final RepeaterSizeHandler hRepeaterSize = new RepeaterSizeHandler(); 104 private final RepeaterHandler hRepeater = new RepeaterHandler(); 105 private final RepeaterRowsHandler hRepeaterRows = new RepeaterRowsHandler(); 106 private final RepeaterWidgetHandler hRepeaterWidget = new RepeaterWidgetHandler(); 107 private final RepeaterWidgetLabelHandler hRepeaterWidgetLabel = new RepeaterWidgetLabelHandler(); 108 private final SkipHandler hSkip = new SkipHandler(); 109 private final StructHandler hStruct = new StructHandler(); 110 private final StylingContentHandler hStyling = new StylingContentHandler(); 111 private final UnionHandler hUnion = new UnionHandler(); 112 private final UnionPassThruHandler hUnionPassThru = new UnionPassThruHandler(); 113 private final ValidationErrorHandler hValidationError = new ValidationErrorHandler(); 114 private final WidgetHandler hWidget = new WidgetHandler(); 115 private final WidgetLabelHandler hWidgetLabel = new WidgetLabelHandler(); 116 117 120 protected final Map templates; 121 122 protected FormsPipelineConfig pipeContext; 123 124 127 private final List namespaces; 128 129 133 private boolean hasInstanceNamespace; 134 135 protected Widget contextWidget; 136 protected LinkedList contextWidgets; 137 protected LinkedList chooseWidgets; 138 protected Widget widget; 139 protected Map classes; 140 141 142 public EffectWidgetReplacingPipe() { 143 namespaces = new ArrayList (5); 144 templates = new HashMap (); 146 templates.put(AGGREGATE_WIDGET, hAggregate); 147 templates.put(CHOOSE, hChoose); 148 templates.put(CLASS, hClass); 149 templates.put(CONTINUATION_ID, hContinuationId); 150 templates.put(GROUP, hGroup); 151 templates.put(NEW, hNew); 152 templates.put(REPEATER, hRepeater); 153 templates.put(REPEATER_ROWS, hRepeaterRows); 154 templates.put(REPEATER_SIZE, hRepeaterSize); 155 templates.put(REPEATER_WIDGET, hRepeaterWidget); 156 templates.put(REPEATER_WIDGET_LABEL, hRepeaterWidgetLabel); 157 templates.put(STRUCT, hStruct); 158 templates.put(UNION, hUnion); 159 templates.put(VALIDATION_ERROR, hValidationError); 160 templates.put(WIDGET, hWidget); 161 templates.put(WIDGET_LABEL, hWidgetLabel); 162 } 163 164 public void init(Widget contextWidget, FormsPipelineConfig pipeContext) { 165 super.init(hDocument); 167 this.pipeContext = pipeContext; 168 169 this.contextWidgets = new LinkedList (); 171 this.chooseWidgets = new LinkedList (); 172 this.classes = new HashMap (); 173 } 174 175 public void recycle() { 176 super.recycle(); 177 this.contextWidget = null; 178 this.widget = null; 179 this.pipeContext = null; 180 this.namespaces.clear(); 181 this.hasInstanceNamespace = false; 182 } 183 184 187 protected String getAttributeValue(String loc, Attributes attrs, String name) throws SAXException { 188 String value = attrs.getValue(name); 189 if (value == null) { 190 throw new SAXException ("Element '" + loc + "' missing required '" + name + "' attribute, " + 191 "at " + getLocation()); 192 } 193 return value; 194 } 195 196 199 protected String getRequiredAttributeValue(String loc, Attributes attrs, String name) throws SAXException { 200 String value = attrs.getValue(name); 201 if (value == null || value.equals("")) { 202 throw new SAXException ("Element '" + loc + "' missing required '" + name + "' attribute, " + 203 "at " + getLocation()); 204 } 205 return value; 206 } 207 208 211 protected void setWidget(String loc, Attributes attrs) throws SAXException { 212 setWidget(loc, getRequiredAttributeValue(loc, attrs, "id")); 213 } 214 215 218 protected void setWidget(String loc, String path) throws SAXException { 219 widget = contextWidget.lookupWidget(path); 220 if (widget == null) { 221 if (contextWidget.getRequestParameterName().equals("")) { 222 throw new SAXException ("Element '" + loc + "' refers to unexistent widget path '" + path + "', " + 223 "relative to the form container, at " + getLocation()); 224 } else { 225 throw new SAXException ("Element '" + loc + "' refers to unexistent widget path '" + path + "', " + 226 "relative to the '" + contextWidget.getRequestParameterName() + "', " + 227 "at " + getLocation()); 228 } 229 } 230 } 231 232 235 protected void setTypedWidget(String loc, Attributes attrs, Class wclass, String wname) throws SAXException { 236 setWidget(loc, attrs); 237 if (!wclass.isInstance(widget)) { 238 throw new SAXException ("Element '" + loc + "' can only be used with " + wname + " widgets, " + 239 "at " + getLocation()); 240 } 241 } 242 243 protected boolean isVisible(Widget widget) { 244 return widget.getCombinedState().isDisplayingValues(); 245 } 246 247 251 private ContentHandler getContentHandler() { 252 return this.contentHandler; 253 } 254 255 259 private LexicalHandler getLexicalHandler() { 260 return this.lexicalHandler; 261 } 262 263 264 268 public void startPrefixMapping(String prefix, String uri) 269 throws SAXException { 270 if (prefix != null) { 271 this.namespaces.add(new String [] {prefix, uri}); 272 } 273 274 if (!FormsConstants.TEMPLATE_NS.equals(uri)) { 276 super.startPrefixMapping(prefix, uri); 277 } 278 } 279 280 284 public void endPrefixMapping(String prefix) 285 throws SAXException { 286 String uri = null; 287 288 if (prefix != null) { 289 boolean found = false; 291 for (int i = this.namespaces.size() - 1; i >= 0; i--) { 292 final String [] prefixAndUri = (String []) this.namespaces.get(i); 293 if (prefixAndUri[0].equals(prefix)) { 294 uri = prefixAndUri[1]; 295 this.namespaces.remove(i); 296 found = true; 297 break; 298 } 299 } 300 if (!found) { 301 throw new SAXException ("Namespace for prefix '" + prefix + "' not found."); 302 } 303 } 304 305 if (!FormsConstants.TEMPLATE_NS.equals(uri)) { 307 super.endPrefixMapping(prefix); 308 } 309 } 310 311 314 protected boolean hasPrefixMapping(String uri, String prefix) { 315 final int l = this.namespaces.size(); 316 for (int i = 0; i < l; i++) { 317 String [] prefixAndUri = (String []) this.namespaces.get(i); 318 if (prefixAndUri[0].equals(prefix) && prefixAndUri[1].equals(uri)) { 319 return true; 320 } 321 } 322 323 return false; 324 } 325 326 330 protected class NestedHandler extends CopyHandler { 331 public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) 332 throws SAXException { 333 if (!FormsConstants.TEMPLATE_NS.equals(uri)) { 335 return hNested; 336 } 337 338 Handler handler = (Handler ) templates.get(loc); 339 if (handler == null) { 340 throw new SAXException ("Element '" + loc + "' was not recognized, " + 341 "at " + getLocation()); 342 } 343 344 return handler; 345 } 346 } 347 348 351 protected class DocHandler extends CopyHandler { 352 public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) 353 throws SAXException { 354 if (FormsConstants.TEMPLATE_NS.equals(uri)) { 355 if (!FORM_TEMPLATE_EL.equals(loc)) { 356 throw new SAXException ("Element '" + loc + "' is not permitted outside of " + 357 "'form-template', at " + getLocation()); 358 } 359 360 return hForm; 361 } 362 363 return super.nestedElement(uri, loc, raw, attrs); 364 } 365 } 366 367 375 protected class FormHandler extends NestedHandler { 376 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 377 throws SAXException { 378 if (contextWidget != null) { 379 throw new SAXException ("Element 'form-template' can not be nested, " + 380 "at " + getLocation()); 381 } 382 383 AttributesImpl newAttrs = attrs == null || attrs.getLength() == 0? 384 new AttributesImpl (): 385 new AttributesImpl (attrs); 386 387 String formLocation = attrs.getValue(LOCATION); 389 if (formLocation != null) { 390 newAttrs.removeAttribute(newAttrs.getIndex(LOCATION)); 392 } 393 contextWidget = pipeContext.findForm(formLocation); 394 395 if (!isVisible(contextWidget)) { 397 return hNull; 398 } 399 400 String localeAttr = attrs.getValue("locale"); 403 if (localeAttr != null) { localeAttr = pipeContext.translateText(localeAttr); 405 pipeContext.setLocale(I18nUtils.parseLocale(localeAttr)); 406 } else if (pipeContext.getLocaleParameter() != null) { pipeContext.setLocale(pipeContext.getLocaleParameter()); 408 } else { 409 Object locale = null; 411 try { 412 locale = pipeContext.evaluateExpression("/locale"); 413 } catch (JXPathException e) {} 414 if (locale != null) { 415 pipeContext.setLocale((Locale )locale); 416 } else { 417 pipeContext.setLocale(Locale.getDefault()); 419 } 420 } 421 422 pipeContext.addFormAttributes(newAttrs); 425 String [] namesToTranslate = {"action"}; 426 Attributes transAttrs = null; 427 try { 428 transAttrs = translateAttributes(newAttrs, namesToTranslate); 429 } catch (RuntimeException e) { 430 throw new SAXException ( e.getMessage() + " " +getLocation()); 431 } 432 433 hasInstanceNamespace = hasPrefixMapping(FormsConstants.INSTANCE_NS, FormsConstants.INSTANCE_PREFIX); 434 if (!hasInstanceNamespace) { 435 getContentHandler().startPrefixMapping(FormsConstants.INSTANCE_PREFIX, FormsConstants.INSTANCE_NS); 436 } 437 getContentHandler().startElement(FormsConstants.INSTANCE_NS, "form-template", FormsConstants.INSTANCE_PREFIX_COLON + "form-template", transAttrs); 438 return this; 439 } 440 441 public void endElement(String uri, String loc, String raw) 442 throws SAXException { 443 getContentHandler().endElement(FormsConstants.INSTANCE_NS, "form-template", FormsConstants.INSTANCE_PREFIX_COLON + "form-template"); 444 if (!hasInstanceNamespace) { 445 getContentHandler().endPrefixMapping(FormsConstants.INSTANCE_PREFIX); 446 } 447 contextWidget = null; 448 } 449 } 450 451 454 protected class SkipHandler extends NestedHandler { 455 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 456 throws SAXException { 457 return this; 458 } 459 460 public void endElement(String uri, String loc, String raw) 461 throws SAXException { 462 } 463 } 464 465 469 472 protected class WidgetLabelHandler extends ErrorHandler { 473 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 474 throws SAXException { 475 setWidget(loc, attrs); 476 widget.generateLabel(getContentHandler()); 477 return this; 478 } 479 480 public void endElement(String uri, String loc, String raw) 481 throws SAXException { 482 } 483 } 484 485 488 protected class WidgetHandler extends NullHandler { 489 private boolean hasStyling; 491 492 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 493 throws SAXException { 494 setWidget(loc, attrs); 495 if (!isVisible(widget)) { 496 return hNull; 497 } 498 499 hasStyling = false; 500 return this; 501 } 502 503 public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) 504 throws SAXException { 505 if (FormsConstants.INSTANCE_NS.equals(uri)) { 506 if (!STYLING_EL.equals(loc)) { 507 throw new SAXException ("Element '" + loc + "' is not permitted within 'widget', " + 508 "at " + getLocation()); 509 } 510 hasStyling = true; 511 beginBuffer(); 512 return hBuffer; 514 } 515 return hNull; 516 } 517 518 public void endElement(String uri, String loc, String raw) 519 throws SAXException { 520 if (hasStyling) { 521 hasStyling = false; 524 hStyling.recycle(); 525 hStyling.setSaxFragment(endBuffer()); 526 hStyling.setContentHandler(getContentHandler()); 527 hStyling.setLexicalHandler(getLexicalHandler()); 528 widget.generateSaxFragment(hStyling, pipeContext.getLocale()); 529 } else { 530 widget.generateSaxFragment(getContentHandler(), pipeContext.getLocale()); 532 } 533 widget = null; 534 } 535 } 536 537 541 544 protected class RepeaterSizeHandler extends ErrorHandler { 545 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 546 throws SAXException { 547 setTypedWidget(loc, attrs, Repeater.class, "repeater"); 548 ((Repeater) widget).generateSize(getContentHandler()); 549 widget = null; 550 return this; 551 } 552 553 public void endElement(String uri, String loc, String raw) 554 throws SAXException { 555 } 556 } 557 558 561 protected class RepeaterWidgetLabelHandler extends ErrorHandler { 562 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 563 throws SAXException { 564 Repeater repeater; 565 if (contextWidget instanceof Repeater) { 566 repeater = (Repeater)contextWidget; 567 } else { 568 setTypedWidget(loc, attrs, Repeater.class, "repeater"); 569 repeater = (Repeater)widget; 570 widget = null; 571 } 572 String path = getRequiredAttributeValue(loc, attrs, "widget-id"); 573 repeater.generateWidgetLabel(path, getContentHandler()); 574 return this; 575 } 576 577 public void endElement(String uri, String loc, String raw) 578 throws SAXException { 579 } 580 } 581 582 585 protected class RepeaterHandler extends NestedHandler { 586 protected Class getWidgetClass() { 587 return Repeater.class; 588 } 589 590 protected String getWidgetName() { 591 return "repeater"; 592 } 593 594 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 595 throws SAXException { 596 setTypedWidget(loc, attrs, getWidgetClass(), getWidgetName()); 597 if (!isVisible(widget)) { 598 return hNull; 599 } 600 601 contextWidgets.addFirst(contextWidget); 602 contextWidget = widget; 603 return this; 604 } 605 606 public void endElement(String uri, String loc, String raw) 607 throws SAXException { 608 contextWidget = (Widget) contextWidgets.removeFirst(); 609 } 610 } 611 612 615 protected class RepeaterRowsHandler extends BufferHandler { 616 public Handler startElement(String uri, String loc, String raw, Attributes attrs) 617 throws SAXException { 618 if (!(contextWidget instanceof Repeater)) { 619 throw new SAXException ("<repeater-rows> cannot be used with " + contextWidget + ", at " + getLocation()); 620 } 621 beginBuffer(); 622 return this; 623 } 624 625 public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) 626 throws SAXException { 627 return hBuffer; 628 } 629 630 public void endElement(String uri, String loc, String raw) 631 throws SAXException { 632 SaxBuffer buffer = endBuffer(); 633 final |