blob: 37b6d9e7264234b39216ebb838968d9c866bdb3e [file] [log] [blame]
/*
* Copyright 2004-2005 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.
*/
package grails.util;
import groovy.xml.MarkupBuilder;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.groovy.grails.web.servlet.GrailsHttpServletResponse;
/**
* <p>OpenRicoBuilder provides support for creating OpenRico AJAX responses.
*
* <p>If this builder is used in controllers no views should be configured since
* content will be written to the HttpServletResponse instance. Also no operations
* should be performed on the response object prior to passing it to this builder.
*
* <p>This builder will set the content type of the response to "text/xml" and will
* render a correct XML prolog (<?xml version="1.0" encoding="ISO-8859-1"?>).
*
* <p>This builder supports no namespaces or hyphens in element and attribute names.
*
* <p>Use this builder to write a OpenRico reponse to the client. Sending a simple
* DIV tag to the client requires this code:
*
* <pre>
* new OpenRicoBuilder(response).ajax { element(id:"personInfo") { div(class:"person") } }
* </pre>
*
* <p>Sending object XML to the client requires this code:
*
* <pre>
* new OpenRicoBuilder(response).ajax { object(id:"formLetterUpdater") {
* person(
* fullName:"Pat Barnes",
* title:"Mr.",
* firstName:"Pat",
* lastName:"Barnes")
* }
* }
* </pre>
*
* <p>The first call on a OpenRicoBuilder instance should be "ajax", the second either "element" (to return HTML)
* or object (to return XML) both with a required "id" attribute. Violations against these rules will be
* reported as exceptions.
*
* <p>OpenRico responses can contain multiple element and/or object nodes which is supported by this builder.
*
* <p>When using OpenRicoBuilder in controllers you may need to return a null value at the end of a closure to avoid an
* UnsupporterReturnValueException.
*
* @author Steven Devijver
* @since Jul 5, 2005
*/
public class OpenRicoBuilder extends MarkupBuilder {
private static final String TEXT_XML = "text/xml";
private static final String UTF_8 = "UTF-8";
private static final String AJAX = "ajax";
private static final String AJAX_RESPONSE = "ajax-response";
private static final String ELEMENT = "element";
private static final String OBJECT = "object";
private static final String RESPONSE = "response";
private static final String TYPE = "type";
private static final String ID = "id";
private static final String OPENRICO = "OpenRicoBuilder: ";
private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
private boolean ajaxOnly = true;
private boolean responseOnly = false;
private boolean start = true;
public OpenRicoBuilder(HttpServletResponse response) throws IOException {
this(new GrailsHttpServletResponse(response));
}
public OpenRicoBuilder(GrailsHttpServletResponse response) throws IOException {
super(response.getWriter(TEXT_XML, UTF_8));
getPrinter().println(XML_HEADER);
}
protected Object createNode(Object name) {
if (responseOnly) {
throw new IllegalArgumentException(OPENRICO + "only call to [element { }] is allowed with attribute [id], not [" + name + "{ }]!");
}
if (start && AJAX.equals(name)) {
ajaxOnly = false;
responseOnly = true;
start = false;
return super.createNode(AJAX_RESPONSE);
} else if (ajaxOnly) {
throw new IllegalArgumentException(OPENRICO + "only call to [ajax { }] is allowed, not [" + name + " { }]!");
} else {
return super.createNode(name);
}
}
protected Object createNode(Object name, Map attributes, Object value) {
if (checkElementName((String)name, attributes)) {
return super.createNode(name, attributes, value);
} else {
return RESPONSE;
}
}
protected Object createNode(Object name, Object value) {
if (checkElementName((String)name, null)) {
return super.createNode(name, value);
} else {
throw new UnsupportedOperationException(OPENRICO + "invalid method call [" + name + "{ }]!");
}
}
protected void nodeCompleted(Object parent, Object node) {
responseOnly = AJAX_RESPONSE.equals(parent) && RESPONSE.equals(node);
super.nodeCompleted(parent, node);
}
private boolean checkElementName(String elementName, Map attributes) {
if (ajaxOnly) {
throw new IllegalArgumentException(OPENRICO + "only call to [ajax {}] allowed without arguments, not [" + elementName + " { }]!");
}
if (responseOnly) {
responseOnly = false;
if (!(ELEMENT.equals(elementName) || OBJECT.equals(elementName))) {
throw new IllegalArgumentException(OPENRICO + "only calls to [element { }] or [object { }] are allowed, not [" + elementName + " { }]!");
}
if (attributes == null || attributes.isEmpty()) {
throw new IllegalArgumentException(OPENRICO + "call to [" + elementName + " { }] requires id attribute!");
} else if (!(attributes.containsKey(ID) && attributes.get(ID) != null)) {
throw new IllegalArgumentException(OPENRICO + "call to [" + elementName + " { }] requires id attribute with value!");
} else if (ELEMENT.equals(elementName)) {
attributes.put(TYPE, ELEMENT);
super.createNode(RESPONSE, attributes);
} else if (OBJECT.equals(elementName)) {
attributes.put(TYPE, OBJECT);
super.createNode(RESPONSE, attributes);
}
return false;
}
return true;
}
}