com.jsmartframework.web.manager.FilterControl.java Source code

Java tutorial

Introduction

Here is the source code for com.jsmartframework.web.manager.FilterControl.java

Source

/*
 * JSmart Framework - Java Web Development Framework
 * Copyright (c) 2015, Jeferson Albino da Silva, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/

package com.jsmartframework.web.manager;

import static com.jsmartframework.web.config.Config.CONFIG;
import static com.jsmartframework.web.config.Constants.CSRF_TOKEN_NAME;
import static com.jsmartframework.web.config.Constants.CSRF_TOKEN_VALUE;
import static com.jsmartframework.web.config.Constants.ENCODING;
import static com.jsmartframework.web.config.Constants.FILTER_HEADERS;
import static com.jsmartframework.web.config.Constants.FILTER_RESOURCES;
import static com.jsmartframework.web.config.Constants.IMPLEMENTATION_VERSION;
import static com.jsmartframework.web.config.Constants.INDEX_JSP;
import static com.jsmartframework.web.config.Constants.LIB_FILE_PATH;
import static com.jsmartframework.web.config.Constants.LIB_JAR_FILE_PATTERN;
import static com.jsmartframework.web.config.Constants.MANIFEST;
import static com.jsmartframework.web.config.Constants.PATH_SEPARATOR;
import static com.jsmartframework.web.config.Constants.ROOT_PATH;
import static com.jsmartframework.web.config.Constants.REQUEST_META_DATA_CSRF_TOKEN_NAME;
import static com.jsmartframework.web.config.Constants.REQUEST_META_DATA_CSRF_TOKEN_VALUE;
import static com.jsmartframework.web.config.Constants.REQUEST_PAGE_DOC_SCRIPT_ATTR;
import static com.jsmartframework.web.config.Constants.REQUEST_PAGE_SCRIPT_ATTR;
import static com.jsmartframework.web.config.Constants.REQUEST_REDIRECT_PATH_AJAX_ATTR;
import static com.jsmartframework.web.config.Constants.REQUEST_REDIRECT_WINDOW_PATH_AJAX_ATTR;
import static com.jsmartframework.web.config.Constants.REQUEST_EXPOSE_VARS_ATTR;
import static com.jsmartframework.web.config.Constants.SESSION_RESET_ATTR;
import static com.jsmartframework.web.manager.BeanHandler.HANDLER;
import static com.jsmartframework.web.manager.BeanHandler.AnnotatedFunction;
import static com.jsmartframework.web.manager.ExpressionHandler.EXPRESSIONS;

import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import com.jsmartframework.web.config.FileVersion;
import com.jsmartframework.web.config.HtmlCompress;
import com.jsmartframework.web.json.Headers;
import com.jsmartframework.web.json.Resources;
import com.jsmartframework.web.tag.FunctionTagHandler;
import com.jsmartframework.web.tag.html.DocScript;
import com.jsmartframework.web.tag.html.Head;
import com.jsmartframework.web.tag.html.Meta;
import com.jsmartframework.web.tag.html.Script;

import com.jsmartframework.web.tag.html.Tag;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.reflections.vfs.Vfs;
import org.reflections.vfs.Vfs.Dir;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;

public final class FilterControl implements Filter {

    private static final int STREAM_BUFFER = 2048;

    private static final Logger LOGGER = Logger.getLogger(FilterControl.class.getPackage().getName());

    private static final Pattern HTML_PATTERN = Pattern.compile("(<html.*?>)");

    private static final Pattern START_HEAD_PATTERN = Pattern.compile("(<head.*?>)");

    private static final Pattern CLOSE_BODY_PATTERN = Pattern.compile("(</body.*?>)");

    private static final Pattern SCRIPT_BODY_PATTERN = Pattern.compile("(<body.*?>\\s*)(<script.*?>)",
            Pattern.DOTALL);

    private static final Pattern JAR_FILE_PATTERN = Pattern.compile(LIB_JAR_FILE_PATTERN);

    private static Map<String, Pattern> versionFilePatterns = new ConcurrentHashMap<>();

    private static StringBuilder headerScripts = new StringBuilder();

    private static StringBuilder headerStyles = new StringBuilder();

    @Override
    public void init(FilterConfig config) throws ServletException {
        initHeaders(config);
        initResources(config);
        versionResources(config);
    }

    @Override
    public void destroy() {
        // DO NOTHING
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        httpRequest.setCharacterEncoding(ENCODING);
        httpResponse.setCharacterEncoding(ENCODING);

        // Initiate bean context based on current thread instance
        WebContext.initCurrentInstance(httpRequest, httpResponse);

        // Instantiate request scoped authentication bean
        HANDLER.instantiateAuthBean(httpRequest);

        // Instantiate web security for request extra validation
        HANDLER.instantiateWebSecurity(httpRequest);

        // Anonymous subclass to wrap HTTP response to print output
        WebFilterResponseWrapper responseWrapper = new WebFilterResponseWrapper(httpResponse);

        Throwable throwable = null;
        try {
            filterChain.doFilter(request, responseWrapper);
        } catch (Throwable thrown) {
            throwable = thrown;
            thrown.printStackTrace();
        }

        // Finalize request scoped web and auth beans
        HANDLER.finalizeBeans(httpRequest, responseWrapper);

        // Check if response was written before closing the WebContext
        boolean responseWritten = WebContext.isResponseWritten();

        // Close bean context based on current thread instance
        WebContext.closeCurrentInstance();

        // Case AsyncBean or RequestPath process was started it cannot proceed because it will not provide HTML via framework
        if (httpRequest.isAsyncStarted() || responseWritten) {

            // Generate response value after flushing the response wrapper buffer
            responseWrapper.flushBuffer();
            String responseVal = responseWrapper.toString();

            // Close current outputStream on responseWrapper
            responseWrapper.close();

            // Write the response value on real response object
            if (!httpResponse.isCommitted()) {
                httpResponse.getWriter().write(responseVal);
            }

            // Case internal server error
            if (throwable != null) {
                if (throwable instanceof IOException) {
                    throw new IOException(throwable);
                }
                throw new ServletException(throwable);
            }
            return;
        }

        // Add Ajax headers to control redirect and reset
        addAjaxHeaders(httpRequest, responseWrapper);

        // Generate HTML after flushing the response wrapper buffer
        responseWrapper.flushBuffer();
        String html = completeHtml(httpRequest, responseWrapper);

        // Close current outputStream on responseWrapper
        responseWrapper.close();

        // Case internal server error
        if (throwable != null) {
            responseWrapper.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

            if (throwable instanceof IOException) {
                throw new IOException(throwable);
            }
            throw new ServletException(throwable);
        }

        if (StringUtils.isBlank(html)) {
            return;
        }

        if (CONFIG.getContent().isPrintHtml()) {
            LOGGER.log(Level.INFO, html);
        }

        // Compress html to better load performance
        HtmlCompress compressHtml = CONFIG.getContent().getCompressHtml();
        if (compressHtml.isCompressHtml()) {
            HtmlCompressor compressor = new HtmlCompressor();
            compressor.setRemoveComments(!compressHtml.isSkipComments());
            html = compressor.compress(html);
        }

        // Write our modified text to the real response
        if (!httpResponse.isCommitted()) {
            httpResponse.setContentLength(html.getBytes().length);
            httpResponse.getWriter().write(html);
        }
    }

    private void addAjaxHeaders(HttpServletRequest httpRequest, HttpServletResponseWrapper response) {
        // Case redirect via ajax, place tag with path to be handled by java script
        String ajaxPath = (String) httpRequest.getAttribute(REQUEST_REDIRECT_PATH_AJAX_ATTR);
        if (ajaxPath != null) {
            Boolean newWindow = (Boolean) httpRequest.getAttribute(REQUEST_REDIRECT_WINDOW_PATH_AJAX_ATTR);

            if (Boolean.TRUE.equals(newWindow)) {
                response.addHeader("New-Window-Ajax", ajaxPath);
            } else {
                response.addHeader("Redirect-Ajax", ajaxPath);
            }
        }

        // Case session reset, place tag to force java script reset the page
        HttpSession session = httpRequest.getSession();
        synchronized (session) {
            if (session.getAttribute(SESSION_RESET_ATTR) != null) {
                if (ajaxPath == null && WebContext.isAjaxRequest()) {
                    response.addHeader("Reset-Ajax", "Session");
                }
                session.removeAttribute(SESSION_RESET_ATTR);
            }
        }
    }

    private String completeHtml(HttpServletRequest httpRequest, HttpServletResponseWrapper response) {
        String html = response.toString();

        // Ajax request do not use scripts returned on html body
        if ("XMLHttpRequest".equals(httpRequest.getHeader("X-Requested-With"))) {
            return html;
        }

        // Check if it is a valid html, if not just return the html
        Matcher htmlMatcher = HTML_PATTERN.matcher(html);
        if (!htmlMatcher.find()) {
            return html;
        }

        html = completeHeader(httpRequest, htmlMatcher, html);
        html = completeScripts(httpRequest, html);
        html = completeVersions(httpRequest, html);
        return html;
    }

    private String completeHeader(HttpServletRequest httpRequest, Matcher htmlMatcher, String html) {
        // Try to place the css as the first link in the head tag
        Matcher startHeadMatcher = START_HEAD_PATTERN.matcher(html);
        if (startHeadMatcher.find()) {
            html = startHeadMatcher.replaceFirst("$1" + Matcher.quoteReplacement(headerStyles.toString()));
        } else {
            Head head = new Head();
            head.addText(headerStyles);
            html = htmlMatcher.replaceFirst("$1" + Matcher.quoteReplacement(head.getHtml().toString()));
        }

        // Place the CSRF token as Meta tags
        String tokenName = (String) httpRequest.getAttribute(REQUEST_META_DATA_CSRF_TOKEN_NAME);
        if (tokenName != null) {
            String tokenValue = (String) httpRequest.getAttribute(REQUEST_META_DATA_CSRF_TOKEN_VALUE);
            startHeadMatcher = START_HEAD_PATTERN.matcher(html);

            Tag csrfName = new Meta().addAttribute("name", CSRF_TOKEN_NAME).addAttribute("content", tokenName);
            Tag csrfToken = new Meta().addAttribute("name", CSRF_TOKEN_VALUE).addAttribute("content", tokenValue);
            StringBuilder metaTags = csrfName.getHtml().append(csrfToken.getHtml());

            html = startHeadMatcher.replaceFirst("$1" + Matcher.quoteReplacement(metaTags.toString()));
        }
        return html;
    }

    private String completeScripts(HttpServletRequest httpRequest, String html) {
        // Stand alone script with mapped exposed variables
        Script varScript = getExposeVarScripts(httpRequest);

        // Stand alone functions mapped via function tag
        Script funcScript = getFunctionScripts(httpRequest);

        // General page scripts executed when document is ready
        DocScript docScript = (DocScript) httpRequest.getAttribute(REQUEST_PAGE_DOC_SCRIPT_ATTR);

        StringBuilder scriptBuilder = new StringBuilder(headerScripts);
        if (varScript != null) {
            scriptBuilder.append(varScript.getHtml());
        }
        if (funcScript != null) {
            scriptBuilder.append(funcScript.getHtml());
        }
        if (docScript != null) {
            scriptBuilder.append(docScript.getHtml());
        }

        // Place the scripts before the last script tag inside body
        Matcher scriptMatcher = SCRIPT_BODY_PATTERN.matcher(html);
        if (scriptMatcher.find()) {
            return scriptMatcher.replaceFirst("$1" + Matcher.quoteReplacement(scriptBuilder.toString()) + "$2");
        }

        // Place the scripts before the end body tag
        Matcher bodyMatcher = CLOSE_BODY_PATTERN.matcher(html);
        if (!bodyMatcher.find()) {
            throw new RuntimeException("HTML tag [body] could not be find. Please insert the body tag in your JSP");
        }
        return bodyMatcher.replaceFirst(Matcher.quoteReplacement(scriptBuilder.toString()) + "$1");
    }

    private String completeVersions(HttpServletRequest httpRequest, String html) {
        if (versionFilePatterns.isEmpty()) {
            return html;
        }
        for (String version : versionFilePatterns.keySet()) {
            Pattern filePattern = versionFilePatterns.get(version);
            Matcher fileMatcher = filePattern.matcher(html);
            html = fileMatcher.replaceAll("$1" + Matcher.quoteReplacement("?" + version));
        }
        return html;
    }

    private Script getFunctionScripts(HttpServletRequest httpRequest) {
        String requestPath = httpRequest.getServletPath();
        List<AnnotatedFunction> annotatedFunctions = HANDLER.getAnnotatedFunctions(requestPath);

        for (AnnotatedFunction annotatedFunction : annotatedFunctions) {
            try {
                new FunctionTagHandler().executeTag(httpRequest, annotatedFunction);
            } catch (JspException | IOException e) {
                LOGGER.log(Level.SEVERE, "Annotated Function could not be generated for path [" + requestPath
                        + "]: " + e.getMessage());
            }
        }
        return (Script) httpRequest.getAttribute(REQUEST_PAGE_SCRIPT_ATTR);
    }

    private Script getExposeVarScripts(HttpServletRequest httpRequest) {
        Map<String, Object> exposeVars = (Map) httpRequest.getAttribute(REQUEST_EXPOSE_VARS_ATTR);
        if (exposeVars == null || exposeVars.isEmpty()) {
            return null;
        }

        StringBuilder varBuilder = new StringBuilder();
        for (String key : exposeVars.keySet()) {
            varBuilder.append("var ").append(key).append(" = ").append(EXPRESSIONS.GSON.toJson(exposeVars.get(key)))
                    .append(";");
        }
        Script script = new Script();
        script.addAttribute("type", "text/javascript");
        script.addText(varBuilder);
        return script;
    }

    private void initHeaders(FilterConfig config) {
        String assetsUrl = CONFIG.getContent().getAssetsUrl();
        String contextPath = config.getServletContext().getContextPath();

        String headerPath = "/";
        if (StringUtils.isNotBlank(assetsUrl)) {
            headerPath = assetsUrl + (assetsUrl.endsWith("/") ? "" : "/");
        } else if (StringUtils.isNotBlank(contextPath)) {
            headerPath = contextPath + "/";
        }

        Headers jsonHeaders = EXPRESSIONS.GSON.fromJson(convertResourceToString(FILTER_HEADERS), Headers.class);
        for (String style : jsonHeaders.getStyles()) {
            headerStyles.append(String.format(style, headerPath));
        }
        for (String script : jsonHeaders.getScripts()) {
            headerScripts.append(String.format(script, headerPath));
        }
    }

    @SuppressWarnings("resource")
    private String convertResourceToString(String resource) {
        InputStream is = FilterControl.class.getClassLoader().getResourceAsStream(resource);
        Scanner scanner = new Scanner(is).useDelimiter("\\A");
        return scanner.hasNext() ? scanner.next() : "";
    }

    private void initResources(FilterConfig config) {
        try {
            if (CONFIG.getContent().getAssetsUrl() != null) {
                LOGGER.log(Level.INFO, "Using external assets, please provide the jsmart assets content at ["
                        + CONFIG.getContent().getAssetsUrl() + "]");
            }

            ServletContext context = config.getServletContext();
            Set<String> libs = context.getResourcePaths(LIB_FILE_PATH);

            if (libs == null || libs.isEmpty()) {
                LOGGER.log(Level.SEVERE, "Could not find the JSmart library JAR file. Empty [" + LIB_FILE_PATH
                        + "] resource folder.");
                return;
            }

            String libFilePath = null;
            for (String lib : libs) {
                Matcher matcher = JAR_FILE_PATTERN.matcher(lib);
                if (matcher.find()) {
                    libFilePath = matcher.group();
                    break;
                }
            }

            if (libFilePath == null) {
                LOGGER.log(Level.SEVERE,
                        "Could not find the JSmart library JAR file inside [" + LIB_FILE_PATH + "]");
                return;
            }

            Resources jsonResources = EXPRESSIONS.GSON.fromJson(convertResourceToString(FILTER_RESOURCES),
                    Resources.class);

            File libFile = new File(context.getRealPath(libFilePath));
            Dir content = Vfs.fromURL(libFile.toURI().toURL());

            Iterator<Vfs.File> files = content.getFiles().iterator();
            while (files.hasNext()) {
                Vfs.File file = files.next();

                // Copy index.jsp and replace content to redirect to welcome-url case configured
                if (file.getRelativePath().startsWith(INDEX_JSP)) {
                    if (CONFIG.getContent().getWelcomeUrl() != null) {
                        StringWriter writer = new StringWriter();
                        IOUtils.copy(file.openInputStream(), writer);
                        String index = writer.toString().replace("{0}", CONFIG.getContent().getWelcomeUrl());
                        copyFileResource(new ByteArrayInputStream(index.getBytes(ENCODING)), file.getRelativePath(),
                                context);
                    }
                }

                // Do not copy anything if assets-url was provided
                if (CONFIG.getContent().getAssetsUrl() != null) {
                    continue;
                }

                // Copy js, css and font resources to specific location
                for (String resource : jsonResources.getResources()) {
                    String resourcePath = resource.replace("*", "");

                    if (file.getRelativePath().startsWith(resourcePath)) {
                        initDirResources(context.getRealPath(PATH_SEPARATOR), file.getRelativePath());
                        copyFileResource(file.openInputStream(), file.getRelativePath(), context);
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, ex.getMessage());
        }
    }

    private void initDirResources(String currentPath, String relativePath) {
        if (relativePath.contains(PATH_SEPARATOR)) {
            String[] paths = relativePath.split(PATH_SEPARATOR);

            for (int i = 0; i < paths.length - 1; i++) {
                currentPath += PATH_SEPARATOR + paths[i];

                File dir = new File(currentPath);
                if (!dir.exists()) {
                    dir.mkdir();
                }
            }
        }
    }

    private void versionResources(FilterConfig config) {
        try {
            if (CONFIG.getContent().getFileVersions() == null) {
                LOGGER.log(Level.INFO, "Not using files version control.");
                return;
            }

            String automaticVersion = getAutomaticResourceVersion(config);
            if (automaticVersion != null) {
                LOGGER.log(Level.INFO, "Automatic versioning detected as " + automaticVersion);
            }

            ServletContext context = config.getServletContext();
            File rootFile = new File(context.getRealPath(ROOT_PATH));
            Dir content = Vfs.fromURL(rootFile.toURI().toURL());

            Map<String, StringBuilder> patternBuilders = new HashMap<>();
            Iterator<Vfs.File> files = content.getFiles().iterator();

            while (files.hasNext()) {
                Vfs.File file = files.next();
                String relativePath = file.getRelativePath();

                FileVersion fileVersion = CONFIG.getContent().getFileVersion(relativePath);
                if (fileVersion != null && !fileVersion.isOnExcludeFolders(relativePath)) {
                    if (!fileVersion.isIncludeMinified() && fileVersion.isMinifiedFile(relativePath)) {
                        continue;
                    }

                    String patternVersion = automaticVersion;
                    if (!fileVersion.isAuto()) {
                        patternVersion = fileVersion.getVersion();
                    }
                    if (patternVersion == null) {
                        continue;
                    }

                    StringBuilder patternBuilder = patternBuilders.get(patternVersion);
                    if (patternBuilder == null) {
                        patternBuilder = new StringBuilder("(");
                        patternBuilders.put(patternVersion, patternBuilder);
                    }
                    if (patternBuilder.length() > 1) {
                        patternBuilder.append("|");
                    }
                    patternBuilder.append(relativePath.replace(".", "\\."));
                }
            }

            for (String version : patternBuilders.keySet()) {
                StringBuilder patternBuilder = patternBuilders.get(version).append(")");
                versionFilePatterns.put(version, Pattern.compile(patternBuilder.toString(), Pattern.DOTALL));
            }
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, ex.getMessage());
        }
    }

    private String getAutomaticResourceVersion(FilterConfig config) {
        try {
            Properties manifestProperties = new Properties();
            manifestProperties.load(config.getServletContext().getResourceAsStream(MANIFEST));
            return manifestProperties.getProperty(IMPLEMENTATION_VERSION);
        } catch (Exception e) {
            LOGGER.warning("Failed to retrieve [MANIFEST.MF] information for automatic versioning");
            return null;
        }
    }

    private void copyFileResource(InputStream is, String relativePath, ServletContext context) throws Exception {
        int count = 0;
        BufferedInputStream bis = new BufferedInputStream(is);
        String realFilePath = new File(context.getRealPath(PATH_SEPARATOR)).getPath() + PATH_SEPARATOR
                + relativePath;

        FileOutputStream fos = new FileOutputStream(realFilePath);
        BufferedOutputStream bos = new BufferedOutputStream(fos, STREAM_BUFFER);

        byte data[] = new byte[STREAM_BUFFER];
        while ((count = bis.read(data, 0, STREAM_BUFFER)) != -1) {
            bos.write(data, 0, count);
        }
        bos.close();
        bis.close();
    }

    private class WebFilterResponseWrapper extends HttpServletResponseWrapper {

        private WebFilterOutputStream outputStream = new WebFilterOutputStream();

        public WebFilterResponseWrapper(HttpServletResponse servletResponse) {
            super(servletResponse);
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return outputStream;
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return new PrintWriter(outputStream.getWriter(), true);
        }

        @Override
        public void reset() {
            outputStream.reset();
        }

        @Override
        public void flushBuffer() throws IOException {
            outputStream.flush();
        }

        public void close() throws IOException {
            outputStream.close();
        }

        @Override
        public String toString() {
            return outputStream.toString();
        }
    }

    private class WebFilterOutputStream extends ServletOutputStream {

        private StringWriter writer = new StringWriter();

        private WriteListener writeListener;

        public StringWriter getWriter() {
            return writer;
        }

        public void reset() {
            writer = new StringWriter();
        }

        @Override
        public void write(int b) throws IOException {
            try {
                writer.write(b);
                if (writeListener != null) {
                    writeListener.onWritePossible();
                }
            } catch (IOException ex) {
                if (writeListener != null) {
                    writeListener.onError(ex);
                }
                throw ex;
            }
        }

        @Override
        public void write(byte[] b) throws IOException {
            try {
                writer.write(new String(b));
                if (writeListener != null) {
                    writeListener.onWritePossible();
                }
            } catch (IOException ex) {
                if (writeListener != null) {
                    writeListener.onError(ex);
                }
                throw ex;
            }
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            try {
                writer.write(new String(b), off, len);
                if (writeListener != null) {
                    writeListener.onWritePossible();
                }
            } catch (IOException ex) {
                if (writeListener != null) {
                    writeListener.onError(ex);
                }
                throw ex;
            }
        }

        @Override
        public void flush() throws IOException {
            writer.flush();
        }

        @Override
        public void close() throws IOException {
            writer.close();
        }

        @Override
        public String toString() {
            return writer.toString();
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            this.writeListener = writeListener;
        }
    }

}