/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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 org.civilian.internal; import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Locale; import org.civilian.Request; import org.civilian.Response; import org.civilian.content.ContentSerializer; import org.civilian.content.ContentType; import org.civilian.internal.intercept.InterceptedOutput; import org.civilian.internal.intercept.InterceptedOutputStream; import org.civilian.internal.intercept.InterceptedTemplateWriter; import org.civilian.internal.intercept.RespStreamInterceptorChain; import org.civilian.internal.intercept.RespWriterInterceptorChain; import org.civilian.response.ResponseStreamInterceptor; import org.civilian.response.ResponseWriterInterceptor; import org.civilian.response.UriEncoder; import org.civilian.template.Template; import org.civilian.template.TemplateWriter; import org.civilian.text.LocaleService; import org.civilian.util.Check; /** * AbstractResponse is a partial Response implementation * with useful defaults. */ public abstract class AbstractResponse implements Response { /** * Creates a new AbstractResponse. * @param request the associated request. */ protected AbstractResponse(Request request) { request_ = Check.notNull(request, "request"); } /** * Creates a new AbstractResponse. * @param request the associated request. */ protected AbstractResponse(AbstractRequest request) { request_ = Check.notNull(request, "request"); request.setResponse(this); } @Override public Request getRequest() { return request_; } /** * Throws an IllegalStateException if the response is committed. */ protected void checkNotCommitted() { if (isCommitted()) throw new IllegalStateException("response is committed"); } @Override public UriEncoder getUriEncoder() { if (uriEncoder_ == null) uriEncoder_ = new UriEncoder(); return uriEncoder_; } @Override public LocaleService getLocaleService() { return localeService_ != null ? localeService_ : request_.getLocaleService(); } @Override public void setLocaleService(LocaleService localeService) { localeService_ = Check.notNull(localeService, "localeService"); } @Override public void sendError(int statusCode, String message, Throwable error) throws IOException { checkNotCommitted(); // do not allow repeated or reentrant calls of sendError if (type_ == Type.ERROR) throw new IllegalStateException("sendError already called"); setType(Type.ERROR, false); try { getApplication().createErrorResponse().send(this, statusCode, message, error); } catch(Exception e) { Logs.RESPONSE.error("error during sendError", e); } finally { flushBuffer(); // commits the response } } /** * Sends a redirect response to the client. * After using this method, the response is committed and should not be written to. * @throws IllegalStateException if the response has already been committed */ @Override public void sendRedirect(String url) throws IOException { checkNotCommitted(); setType(Type.REDIRECT, false); sendRedirectImpl(url); } /** * Implements sendRedirect. Should commit the response. */ protected abstract void sendRedirectImpl(String url) throws IOException; @Override public void writeContent(Object object, ContentType contentType) throws Exception { if (object == null) return; if (object instanceof Template) { if (contentType != null) setContentType(contentType); ((Template)object).print(getContentWriter()); return; } if (contentType == null) { contentType = getContentType(); if (contentType == null) { if (object instanceof String) setContentType(contentType = ContentType.TEXT_PLAIN); else throw new IllegalStateException("no content-type set"); } } else setContentType(contentType); ContentSerializer serializer = getApplication().getContentSerializer(contentType); if (serializer != null) serializer.write(object, getContentWriter()); else if (object instanceof String) getContentWriter().write((String)object); else throw new IllegalStateException("don't know how to write a " + object.getClass().getName() + " with content type '" + contentType + "'"); } @Override public ContentAccess getContentAccess() { if (contentOutput_ == null) return ContentAccess.NONE; else if (contentOutput_ instanceof Writer) return ContentAccess.WRITER; else return ContentAccess.OUTPUTSTREAM; } @Override public void closeContent() { if (contentOutput_ != null) { try { ((Closeable)contentOutput_).close(); } catch (IOException e) { Logs.RESPONSE.error("error when closing " + contentOutput_.getClass().getName(), e); } } } @Override public TemplateWriter getContentWriter() throws IOException { if (!(contentOutput_ instanceof Writer)) initContentOutput(true /*we want a writer*/); return (TemplateWriter)contentOutput_; } @Override public OutputStream getContentStream() throws IOException { if (!(contentOutput_ instanceof OutputStream)) initContentOutput(false /*we want a stream*/); return (OutputStream)contentOutput_; } private void initContentOutput(boolean createWriter) throws IOException { // contentOutput_ should be null checkNoContentOutput(); // prepare the actual stream interceptors ResponseStreamInterceptor streamInterceptor = streamInterceptor_ != null ? streamInterceptor_.prepareStreamIntercept(this) : null; if (createWriter) initContentWriter(streamInterceptor); else initContentStream(streamInterceptor); } private void initContentStream(ResponseStreamInterceptor streamInterceptor) throws IOException { OutputStream originalStream = getContentStreamImpl(); contentOutput_ = originalStream; if (streamInterceptor != null) contentOutput_ = new InterceptedOutputStream(originalStream, streamInterceptor); } private void initContentWriter(ResponseStreamInterceptor streamInterceptor) throws IOException { // prepare the actual writer interceptors ResponseWriterInterceptor writerInterceptor = (writerInterceptor_ != null) ? writerInterceptor_.prepareWriterIntercept(this) : null; // make sure that encoding is initialized, fallback to application encoding if (contentEncoding_ == null) setContentEncoding(getApplication().getEncoding()); try { if ((streamInterceptor != null) || !initContentWriterNoStream(writerInterceptor)) initContentWriterWithStream(streamInterceptor, writerInterceptor); } finally { if (contentOutput_ != null) { if (!(contentOutput_ instanceof TemplateWriter)) initContentWriterForError(); TemplateWriter tw = (TemplateWriter)contentOutput_; tw.addContext(this); } } } /** * Creates a TemplateWriter from a implementation writer. * @param writerInterceptor a writer interceptor, can be null * @return writer successful created? */ private boolean initContentWriterNoStream(ResponseWriterInterceptor writerInterceptor) throws IOException { Writer originalWriter = getContentWriterImpl(); if (originalWriter == null) return false; else { contentOutput_ = originalWriter; contentOutput_ = (writerInterceptor_ != null) ? new InterceptedTemplateWriter(originalWriter, writerInterceptor) : new TemplateWriter(originalWriter); return true; } } /** * Creates a TemplateWriter from a implementation OutputStream. * @param streamInterceptor a stream interceptor, can be null * @param writerInterceptor a writer interceptor, can be null */ private void initContentWriterWithStream(ResponseStreamInterceptor streamInterceptor, ResponseWriterInterceptor writerInterceptor) throws IOException { OutputStream originalStream = getContentStreamImpl(); contentOutput_ = originalStream; if ((streamInterceptor != null) || (writerInterceptor != null)) contentOutput_ = new InterceptedTemplateWriter(originalStream, streamInterceptor, writerInterceptor, contentEncoding_); else contentOutput_ = new TemplateWriter(new OutputStreamWriter(originalStream, contentEncoding_)); } /** * Reinit the response writer if an exception was thrown during regular init. */ private void initContentWriterForError() throws IOException { if (contentOutput_ instanceof OutputStream) contentOutput_ = new OutputStreamWriter((OutputStream)contentOutput_, getContentEncoding()); contentOutput_ = new TemplateWriter((Writer)contentOutput_); } private void checkNoContentOutput() { if (contentOutput_ instanceof OutputStream) throw new IllegalStateException("Response.getContentStream() has already been called"); if (contentOutput_ instanceof Writer) throw new IllegalStateException("Response.getContentWriter() has already been called"); } /** * Provides an OutputStream to write binary Response content. * In a servlet environment this returns HttpServletResponse#getOutputStream(). */ protected abstract OutputStream getContentStreamImpl() throws IOException; /** * Provides a Writer to write text Response content. * In a servlet environment this returns HttpServletResponse#getWriter(). * If an implementation does not have an own writer implementation * (but only OutputStreams, it should return null). */ protected abstract Writer getContentWriterImpl() throws IOException; @Override public void setContentLanguage(Locale locale) { contentLanguage_ = Check.notNull(locale, "locale"); } @Override public Locale getContentLanguage() { return contentLanguage_; } @Override public void setContentEncoding(String encoding) { if ((contentOutput_ == null) && !isCommitted()) { contentEncoding_ = encoding; setContentEncodingImpl(encoding); } } /** * Implements setContentEncoding(). */ protected abstract void setContentEncodingImpl(String encoding); @Override public String getContentEncoding() { return contentEncoding_; } @Override public void addInterceptor(ResponseStreamInterceptor interceptor) { Check.notNull(interceptor, "interceptor"); checkNoContentOutput(); streamInterceptor_ = streamInterceptor_ != null ? new RespStreamInterceptorChain(streamInterceptor_, interceptor) : interceptor; } @Override public void addInterceptor(ResponseWriterInterceptor interceptor) { Check.notNull(interceptor, "interceptor"); checkNoContentOutput(); writerInterceptor_ = writerInterceptor_ != null ? new RespWriterInterceptorChain(writerInterceptor_, interceptor) : interceptor; } //-------------------- // type //-------------------- /** * Allow implementations to set the response type. */ private void setType(Type type, boolean force) { if ((type_ == Type.NORMAL) || force) type_ = type; else if (type_ != type) throw new IllegalStateException("response type already set to " + type_); } @Override public Type getType() { return type_; } //-------------------- // buffer //-------------------- @Override public void flushBuffer() throws IOException { flushBuffer(contentOutput_); } protected abstract void flushBuffer(Flushable flushable) throws IOException; @Override public void resetBuffer() { resetBufferImpl(); if (contentOutput_ instanceof InterceptedOutput) ((InterceptedOutput)contentOutput_).reset(); } protected abstract void resetBufferImpl(); /** * Sets all fields of the AbstractRequest to its initial state. */ protected void clear() { type_ = Type.NORMAL; localeService_ = null; contentLanguage_ = null; contentEncoding_ = null; contentOutput_ = null; streamInterceptor_ = null; writerInterceptor_ = null; } private Request request_; private UriEncoder uriEncoder_; private LocaleService localeService_; private Flushable contentOutput_; // we duplicate the encoding since we want to know if an encoding was explicitly set // (the servlet response returns ISO-8859-1 if no encoding was set). private String contentEncoding_; private Locale contentLanguage_; private Type type_ = Type.NORMAL; private ResponseStreamInterceptor streamInterceptor_; private ResponseWriterInterceptor writerInterceptor_; }