Java tutorial
/* * Copyright 2006-2007 Graeme Rocher * * 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.converters; import grails.util.GrailsNameUtils; import grails.util.GrailsWebUtil; import groovy.lang.Closure; import groovy.util.BuilderSupport; import groovy.util.XmlSlurper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.grails.support.proxy.EntityProxyHandler; import org.codehaus.groovy.grails.support.proxy.ProxyHandler; import org.codehaus.groovy.grails.web.converters.AbstractConverter; import org.codehaus.groovy.grails.web.converters.Converter; import org.codehaus.groovy.grails.web.converters.ConverterUtil; import org.codehaus.groovy.grails.web.converters.IncludeExcludeConverter; import org.codehaus.groovy.grails.web.converters.configuration.ConverterConfiguration; import org.codehaus.groovy.grails.web.converters.configuration.ConvertersConfigurationHolder; import org.codehaus.groovy.grails.web.converters.configuration.DefaultConverterConfiguration; import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException; import org.codehaus.groovy.grails.web.converters.marshaller.ClosureObjectMarshaller; import org.codehaus.groovy.grails.web.converters.marshaller.NameAwareMarshaller; import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller; import org.codehaus.groovy.grails.web.mime.MimeType; import org.codehaus.groovy.grails.web.pages.FastStringWriter; import org.codehaus.groovy.grails.web.xml.PrettyPrintXMLStreamWriter; import org.codehaus.groovy.grails.web.xml.StreamingMarkupWriter; import org.codehaus.groovy.grails.web.xml.XMLStreamWriter; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.util.List; import java.util.Map; import java.util.Stack; /** * A converter that converts domain classes to XML. * * @author Siegfried Puchbauer * @author Graeme Rocher */ public class XML extends AbstractConverter<XMLStreamWriter> implements IncludeExcludeConverter<XMLStreamWriter> { public static final Log log = LogFactory.getLog(XML.class); private static final String CACHED_XML = "org.codehaus.groovy.grails.CACHED_XML_REQUEST_CONTENT"; private Object target; private StreamingMarkupWriter stream; private final ConverterConfiguration<XML> config; private final String encoding; private final CircularReferenceBehaviour circularReferenceBehaviour; private XMLStreamWriter writer; private Stack<Object> referenceStack = new Stack<Object>(); private boolean isRendering = false; public XML() { config = ConvertersConfigurationHolder.getConverterConfiguration(XML.class); encoding = config.getEncoding() != null ? config.getEncoding() : "UTF-8"; contentType = MimeType.XML.getName(); circularReferenceBehaviour = config.getCircularReferenceBehaviour(); } public XML(Object target) { this(); this.target = target; } public XML(XMLStreamWriter writer) { this(); this.writer = writer; this.isRendering = true; } protected ConverterConfiguration<XML> initConfig() { return ConvertersConfigurationHolder.getConverterConfiguration(XML.class); } @Override public void setTarget(Object target) { this.target = target; } private void finalizeRender(Writer out) { try { if (out != null) { out.flush(); out.close(); } } catch (Exception e) { log.warn("Unexpected exception while closing a writer: " + e.getMessage()); } } public void render(Writer out) throws ConverterException { stream = new StreamingMarkupWriter(out, encoding); writer = config.isPrettyPrint() ? new PrettyPrintXMLStreamWriter(stream) : new XMLStreamWriter(stream); try { isRendering = true; writer.startDocument(encoding, "1.0"); writer.startNode(getElementName(target)); convertAnother(target); writer.end(); } catch (Exception e) { throw new ConverterException(e); } finally { finalizeRender(out); isRendering = false; } } private void checkState() { Assert.state(isRendering, "Illegal XML Converter call!"); } public String getElementName(Object o) { ObjectMarshaller<XML> om = config.getMarshaller(o); if (om instanceof NameAwareMarshaller) { return ((NameAwareMarshaller) om).getElementName(o); } final ProxyHandler proxyHandler = config.getProxyHandler(); if (proxyHandler.isProxy(o) && (proxyHandler instanceof EntityProxyHandler)) { EntityProxyHandler entityProxyHandler = (EntityProxyHandler) proxyHandler; final Class<?> cls = entityProxyHandler.getProxiedClass(o); return GrailsNameUtils.getPropertyName(cls); } return GrailsNameUtils.getPropertyName(o.getClass()); } public void convertAnother(Object o) throws ConverterException { o = config.getProxyHandler().unwrapIfProxy(o); try { if (o == null) { // noop } else if (o instanceof CharSequence) { writer.characters(o.toString()); } else if (o instanceof Class<?>) { writer.characters(((Class<?>) o).getName()); } else if ((o.getClass().isPrimitive() && !o.getClass().equals(byte[].class)) || o instanceof Number || o instanceof Boolean) { writer.characters(String.valueOf(o)); } else { if (referenceStack.contains(o)) { handleCircularRelationship(o); } else { referenceStack.push(o); ObjectMarshaller<XML> marshaller = config.getMarshaller(o); if (marshaller == null) { throw new ConverterException("Unconvertable Object of class: " + o.getClass().getName()); } marshaller.marshalObject(o, this); referenceStack.pop(); } } } catch (Throwable t) { throw ConverterUtil.resolveConverterException(t); } } public ObjectMarshaller<XML> lookupObjectMarshaller(Object target) { return config.getMarshaller(target); } public int getDepth() { return referenceStack.size(); } public XML startNode(String tagName) { checkState(); try { writer.startNode(tagName); } catch (Exception e) { throw ConverterUtil.resolveConverterException(e); } return this; } public XML chars(String chars) { checkState(); try { writer.characters(chars); } catch (Exception e) { throw ConverterUtil.resolveConverterException(e); } return this; } public XML attribute(String name, String value) { checkState(); try { writer.attribute(name, value); } catch (Exception e) { throw ConverterUtil.resolveConverterException(e); } return this; } public XML end() { checkState(); try { writer.end(); } catch (Exception e) { throw ConverterUtil.resolveConverterException(e); } return this; } @SuppressWarnings("incomplete-switch") protected void handleCircularRelationship(Object o) throws ConverterException { switch (circularReferenceBehaviour) { case DEFAULT: StringBuilder ref = new StringBuilder(); int idx = referenceStack.indexOf(o); for (int i = referenceStack.size() - 1; i > idx; i--) { ref.append("../"); } attribute("ref", ref.substring(0, ref.length() - 1)); break; case EXCEPTION: throw new ConverterException("Circular Reference detected: class " + o.getClass().getName()); case INSERT_NULL: convertAnother(null); } } public void render(HttpServletResponse response) throws ConverterException { response.setContentType(GrailsWebUtil.getContentType(contentType, encoding)); try { render(response.getWriter()); } catch (IOException e) { throw new ConverterException(e); } } public XMLStreamWriter getWriter() throws ConverterException { checkState(); return writer; } public StreamingMarkupWriter getStream() { checkState(); return stream; } public void build(@SuppressWarnings("rawtypes") Closure c) throws ConverterException { // checkState(); // chars(""); // StreamingMarkupBuilder smb = new StreamingMarkupBuilder(); // Writable writable = (Writable) smb.bind(c); // try { // writable.writeTo(getStream().unescaped()); // } // catch (IOException e) { // throw new ConverterException(e); // } new Builder(this).execute(c); } @Override public String toString() { FastStringWriter strw = new FastStringWriter(); render(strw); strw.flush(); return strw.toString(); } /** * Parses the given XML * * @param source a String containing some XML * @return a groovy.util.XmlSlurper * @throws ConverterException */ public static Object parse(String source) throws ConverterException { try { return new XmlSlurper().parseText(source); } catch (Exception e) { throw new ConverterException("Error parsing XML", e); } } /** * Parses the given XML * * @param is an InputStream to read from * @param encoding the Character Encoding to use * @return a groovy.util.XmlSlurper * @throws ConverterException */ public static Object parse(InputStream is, String encoding) throws ConverterException { try { InputStreamReader reader = new InputStreamReader(is, encoding); return new XmlSlurper().parse(reader); } catch (Exception e) { throw new ConverterException("Error parsing XML", e); } } /** * Parses the give XML (read from the POST Body of the Request) * * @param request an HttpServletRequest * @return a groovy.util.XmlSlurper * @throws ConverterException */ public static Object parse(HttpServletRequest request) throws ConverterException { Object xml = request.getAttribute(CACHED_XML); if (xml != null) return xml; String encoding = request.getCharacterEncoding(); if (encoding == null) { encoding = Converter.DEFAULT_REQUEST_ENCODING; } try { if (!request.getMethod().equalsIgnoreCase("GET")) { xml = parse(request.getInputStream(), encoding); request.setAttribute(CACHED_XML, xml); } return xml; } catch (IOException e) { throw new ConverterException("Error parsing XML", e); } } public static ConverterConfiguration<XML> getNamedConfig(String configName) throws ConverterException { ConverterConfiguration<XML> cfg = ConvertersConfigurationHolder.getNamedConverterConfiguration(configName, XML.class); if (cfg == null) { throw new ConverterException( String.format("Converter Configuration with name '%s' not found!", configName)); } return cfg; } public static Object use(String configName, Closure<?> callable) throws ConverterException { ConverterConfiguration<XML> old = ConvertersConfigurationHolder .getThreadLocalConverterConfiguration(XML.class); ConverterConfiguration<XML> cfg = getNamedConfig(configName); ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(XML.class, cfg); try { return callable.call(); } finally { ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(XML.class, old); } } public static void use(String cfgName) throws ConverterException { if (cfgName == null || "default".equals(cfgName)) { ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(XML.class, null); } else { ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(XML.class, getNamedConfig(cfgName)); } } public static void registerObjectMarshaller(Class<?> clazz, Closure<?> callable) throws ConverterException { registerObjectMarshaller(new ClosureObjectMarshaller<XML>(clazz, callable)); } public static void registerObjectMarshaller(Class<?> clazz, int priority, Closure<?> callable) throws ConverterException { registerObjectMarshaller(new ClosureObjectMarshaller<XML>(clazz, callable), priority); } public static void registerObjectMarshaller(ObjectMarshaller<XML> om) throws ConverterException { ConverterConfiguration<XML> cfg = ConvertersConfigurationHolder.getConverterConfiguration(XML.class); if (cfg == null) { throw new ConverterException("Default Configuration not found for class " + XML.class.getName()); } if (!(cfg instanceof DefaultConverterConfiguration<?>)) { cfg = new DefaultConverterConfiguration<XML>(cfg); ConvertersConfigurationHolder.setDefaultConfiguration(XML.class, cfg); } ((DefaultConverterConfiguration<XML>) cfg).registerObjectMarshaller(om); } public static void registerObjectMarshaller(ObjectMarshaller<XML> om, int priority) throws ConverterException { ConverterConfiguration<XML> cfg = ConvertersConfigurationHolder.getConverterConfiguration(XML.class); if (cfg == null) { throw new ConverterException("Default Configuration not found for class " + XML.class.getName()); } if (!(cfg instanceof DefaultConverterConfiguration<?>)) { cfg = new DefaultConverterConfiguration<XML>(cfg); ConvertersConfigurationHolder.setDefaultConfiguration(XML.class, cfg); } ((DefaultConverterConfiguration<XML>) cfg).registerObjectMarshaller(om, priority); } public static void createNamedConfig(String name, Closure<?> callable) throws ConverterException { DefaultConverterConfiguration<XML> cfg = new DefaultConverterConfiguration<XML>( ConvertersConfigurationHolder.getConverterConfiguration(XML.class)); try { callable.call(cfg); ConvertersConfigurationHolder.setNamedConverterConfiguration(XML.class, name, cfg); } catch (Exception e) { throw ConverterUtil.resolveConverterException(e); } } public static void withDefaultConfiguration(Closure<?> callable) throws ConverterException { ConverterConfiguration<XML> cfg = ConvertersConfigurationHolder.getConverterConfiguration(XML.class); if (!(cfg instanceof DefaultConverterConfiguration<?>)) { cfg = new DefaultConverterConfiguration<XML>(cfg); } try { callable.call(cfg); ConvertersConfigurationHolder.setDefaultConfiguration(XML.class, cfg); } catch (Throwable t) { throw ConverterUtil.resolveConverterException(t); } } @Override public void setIncludes(List<String> includes) { setIncludes(target.getClass(), includes); } @Override public void setExcludes(List<String> excludes) { setExcludes(target.getClass(), excludes); } public class Builder extends BuilderSupport { private XML xml; public Builder(XML xml) { this.xml = xml; } public void execute(Closure<?> callable) { callable.setDelegate(this); callable.call(); } @Override protected Object createNode(Object name) { return createNode(name, null, null); } @Override protected Object createNode(Object name, Object value) { return createNode(name, null, value); } @SuppressWarnings("rawtypes") @Override protected Object createNode(Object name, Map attributes) { return createNode(name, attributes, null); } @SuppressWarnings("rawtypes") @Override protected Object createNode(Object name, Map attributes, Object value) { xml.startNode(name.toString()); if (attributes != null) { for (Object o : attributes.entrySet()) { Map.Entry attribute = (Map.Entry) o; xml.attribute(attribute.getKey().toString(), attribute.getValue().toString()); } } if (value != null) { xml.convertAnother(value); } return name; } @Override protected void nodeCompleted(Object o, Object o1) { xml.end(); } @Override protected void setParent(Object o, Object o1) { // do nothing } } }