/* * Copyright 2008-2011 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 com.nominanuda.springmvc; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.concurrent.Immutable; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.xml.transform.Source; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.helpers.AttributesImpl; import com.nominanuda.web.html.HtmlFragmentParser; import com.nominanuda.web.html.XHtml5Serializer; import com.nominanuda.web.htmlcomposer.DomManipulationStmt; import com.nominanuda.web.htmlcomposer.DomOp; import com.nominanuda.web.htmlcomposer.HtmlSaxPage; import com.nominanuda.web.http.HttpProtocol; import com.nominanuda.zen.common.Check; import com.nominanuda.zen.common.InstanceFactory; import com.nominanuda.zen.stereotype.CodeConstants; import com.nominanuda.zen.stereotype.Initializable; import com.nominanuda.zen.xml.ForwardingTransformerHandlerBase; import com.nominanuda.zen.xml.SAXPipeline; import com.nominanuda.zen.xml.SaxBuffer; import nu.validator.htmlparser.sax.HtmlParser; public class HtmlSaxPageViewResolver implements CodeConstants, ViewResolver, ApplicationContextAware, Initializable, HttpProtocol { private List<ViewResolver> resolvers = null; private ApplicationContext applicationContext; private boolean html = true; public void init() { resolvers = new LinkedList<ViewResolver>(); resolvers.addAll(applicationContext.getBeansOfType(ViewResolver.class).values()); } public View resolveViewName(String viewName, Locale locale) throws Exception { return "htmlcomposer_".equals(viewName) ? makeView(locale) : null; } private View makeView(Locale locale) { return new AsyncView(locale); } private List<ViewResolver> getViewResolvers() { return resolvers; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Immutable public static class JqFragmentAndManipulationStmt { final View view; final Map<String, ?> model; final String selector; final DomOp domOp; public JqFragmentAndManipulationStmt(View view, Map<String, ?> model, String selector, DomOp domOp) { this.view = Check.illegalargument.assertNotNull(view); this.model = Check.illegalargument.assertNotNull(model); this.selector = Check.illegalargument.assertNotNull(selector); this.domOp = Check.illegalargument.assertNotNull(domOp); } public View getView() { return view; } public Map<String, ?> getModel() { return model; } public String getSelector() { return selector; } public DomOp getDomOp() { return domOp; } } private class AsyncView implements View { private Locale locale; public AsyncView(Locale locale) { this.locale = locale; } public String getContentType() { return HttpProtocol.CT_TEXT_HTML_CS_UTF8; } public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ContentHandler ch = createSerializer(new OutputStreamWriter(baos, UTF_8)); HtmlSaxPage p = new HtmlSaxPage(); List<JqFragmentAndManipulationStmt> springMavs = getSpringViewsAndModels(model); for(JqFragmentAndManipulationStmt mav : springMavs) { View v = mav.getView(); Map<String, ?> m = mav.getModel(); String selector = mav.getSelector(); DomOp domOp = mav.getDomOp(); CollectingResponse cr = new CollectingResponse(response); v.render(m, request, cr); SaxBuffer sbuf = new SaxBuffer(); new SAXPipeline() .complete() .build(saxSource(cr.getBuffer()), new SAXResult(sbuf)) .run(); p.applyStmt(new DomManipulationStmt(selector, new InstanceFactory<SaxBuffer>(sbuf), domOp)); } new SaxBuffer.StartDocument().send(ch); new SaxBuffer.StartElement(HTMLNS,"html","html",new AttributesImpl()).send(ch); p.toSAX(ch); new SaxBuffer.EndElement(HTMLNS,"html","html").send(ch); new SaxBuffer.EndDocument().send(ch); byte[] page = baos.toByteArray(); response.setHeader(HDR_CONTENT_LENGTH, new Integer(page.length).toString()); response.getOutputStream().write(page); } private Source saxSource(InputStream is) { if(html) { HtmlParser parser = new HtmlParser(); parser.setMappingLangToXmlLang(true); parser.setReportingDoctype(false); InputSource inputSource = new InputSource(is); inputSource.setEncoding(UTF_8); SAXSource src = new SAXSource(new HtmlFragmentParser(parser), inputSource); return src; } else { return new StreamSource(is); } } private ContentHandler createSerializer(Writer out) { if(html) { XHtml5Serializer ser = new XHtml5Serializer(out); return ser; } else { ForwardingTransformerHandlerBase tx = new ForwardingTransformerHandlerBase(); tx.setResult(new StreamResult(out)); return tx; } } @SuppressWarnings("unchecked") private List<JqFragmentAndManipulationStmt> getSpringViewsAndModels(Map<String, ?> model) throws Exception { List<JqFragmentAndManipulationStmt> mavs = new LinkedList<JqFragmentAndManipulationStmt>(); List<ViewResolver> resolvers = getViewResolvers(); List<Map<String, ?>> viewDefs = (List<Map<String, ?>>)model.get("views_"); for(Map<String, ?> viewDef : viewDefs) { String viewName = (String)viewDef.get("view_"); View v = null; for(ViewResolver r : resolvers) { v = r.resolveViewName(viewName, locale); if(v != null) { DomOp op = DomOp.valueOf((String)viewDef.get("domOp_")); mavs.add(new JqFragmentAndManipulationStmt( v, (Map<String, ?>)viewDef.get("data_"), (String)viewDef.get("selector_"), op )); break; } } Check.illegalargument.assertNotNull(v, "cannot resolve view named:"+viewName); } return mavs; } } public static class CollectingResponse extends HttpServletResponseWrapper { private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); public CollectingResponse(HttpServletResponse response) { //TODO shield setters headers etc super(response); } public InputStream getBuffer() { return new ByteArrayInputStream(baos.toByteArray()); } public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { public void write(int b) throws IOException { baos.write(b); } public void write(byte[] b, int off, int len) throws IOException { baos.write(b, off, len); } public void write(byte[] b) throws IOException { baos.write(b); } //@Override public boolean isReady() { return true; } //@Override public void setWriteListener(WriteListener arg0) { Check.illegalstate.fail(NOT_IMPLEMENTED); } }; } public PrintWriter getWriter() throws IOException { throw new UnsupportedOperationException(NOT_IMPLEMENTED); //return new PrintWriter(getOutputStream()); } } }