/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.sling.scripting.java.impl;

import java.io.IOException;
import java.util.List;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.sling.commons.compiler.CompilationResult;
import org.apache.sling.commons.compiler.CompilerMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class wraps the java servlet and handles its compilation,
 * instantiation, invocation und destruction.
 */
public class ServletWrapper {

    /** The logger. */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** The servlet config. */
    private final ServletConfig config;

    /** Sling IO Provider. */
    private final SlingIOProvider ioProvider;

    /** The name of the generated class. */
    private final String className;

    /** The path to the servlet. */
    private final String sourcePath;

    private volatile long lastModificationTest = 0L;

    private volatile Class<?> servletClass;

    private volatile Servlet theServlet;

    private long available = 0L;

    private volatile Exception compileException;

    /**
     * A wrapper for servlets.
     */
    public ServletWrapper(final ServletConfig config,
                          final SlingIOProvider ioProvider,
                          final String servletPath) {
        this.config = config;
        this.ioProvider = ioProvider;
        this.sourcePath = servletPath;
        this.className = CompilerUtil.mapSourcePath(this.sourcePath).substring(1).replace('/', '.');
    }

    /**
     * Call the servlet.
     * @param request The current request.
     * @param response The current response.
     * @throws ServletException
     * @throws IOException
     */
    public void service(HttpServletRequest request,
                         HttpServletResponse response)
    throws Exception {
        try {
            if ((available > 0L) && (available < Long.MAX_VALUE)) {
                if (available > System.currentTimeMillis()) {
                    response.setDateHeader("Retry-After", available);
                    response.sendError
                        (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                         "Servlet unavailable.");
                    logger.error("Java servlet {} is unavailable.", this.sourcePath);
                    return;
                }
                // Wait period has expired. Reset.
                available = 0;
            }

            // check for compilation
            if (this.lastModificationTest == 0 ) {
                synchronized (this) {
                    if (this.lastModificationTest == 0 ) {
                        try {
                            // clear exception
                            this.compileException = null;
                            this.compile();
                        } catch (Exception ex) {
                            // store exception for futher access attempts
                            this.compileException = ex;
                            throw ex;
                        } finally {
                            this.lastModificationTest = System.currentTimeMillis();
                        }
                    } else if (compileException != null) {
                        // Throw cached compilation exception
                        throw compileException;
                    }
                }
            } else if (compileException != null) {
                // Throw cached compilation exception
                throw compileException;
            }

            // invoke the servlet
            if (theServlet instanceof SingleThreadModel) {
                // sync on the wrapper so that the freshness
                // of the page is determined right before servicing
                synchronized (this) {
                    theServlet.service(request, response);
                }
            } else {
                theServlet.service(request, response);
            }

        } catch (UnavailableException ex) {
            int unavailableSeconds = ex.getUnavailableSeconds();
            if (unavailableSeconds <= 0) {
                unavailableSeconds = 60;        // Arbitrary default
            }
            available = System.currentTimeMillis() +
                (unavailableSeconds * 1000L);
            response.sendError
                (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                 ex.getMessage());
            logger.error("Java servlet {} is unavailable.", this.sourcePath);
            return;
        }
    }

    /**
     * Destroy the servlet.
     */
    public void destroy() {
        if (theServlet != null) {
            theServlet.destroy();
            theServlet = null;
        }
    }

    /** Handle the modification. */
    public void handleModification() {
        this.lastModificationTest = 0;
    }

    private void compile() throws Exception {
        final CompilationUnit unit = new CompilationUnit(this.sourcePath, className, ioProvider);
        final CompilationResult result = this.ioProvider.getCompiler().compile(new org.apache.sling.commons.compiler.CompilationUnit[] {unit},
                ioProvider.getOptions());

        final List<CompilerMessage> errors = result.getErrors();
        if ( errors != null && errors.size() > 0 ) {
            throw CompilerException.create(errors);
        }
        if ( result.didCompile() || this.theServlet == null ) {
            destroy();

            this.servletClass = result.loadCompiledClass(this.className);
            final Servlet servlet = (Servlet) servletClass.newInstance();
            servlet.init(config);

            theServlet = servlet;

        }
    }

    protected final static class CompilerException extends ServletException {

        public static CompilerException create(List<CompilerMessage> errors) {
            final StringBuilder buffer = new StringBuilder();
            buffer.append("Compilation errors:\n");
            for(final CompilerMessage e : errors) {
                buffer.append(e.getFile());
                buffer.append(", line ");
                buffer.append(e.getLine());
                buffer.append(", column ");
                buffer.append(e.getColumn());
                buffer.append(" : " );
                buffer.append(e.getMessage());
                buffer.append("\n");
            }
            return new CompilerException(buffer.toString());
        }

        public CompilerException(final String message) {
           super(message);
        }
    }
}

@@ -6384,16 +6384,33 @@
 e(errors
+, this.sourcePath
 );%0A     
@@ -6812,24 +6812,100 @@
 xception %7B%0A%0A
+        private static final long serialVersionUID = 7353686069328527452L;%0A%0A
         publ
@@ -6939,16 +6939,22 @@
  create(
+final 
 List%3CCom
@@ -6969,24 +6969,63 @@
 sage%3E errors
+,%0A                final String fileName
 ) %7B%0A        
@@ -7127,11 +7127,12 @@
 rors
-:%5Cn
+ in 
 %22);%0A
@@ -7147,91 +7147,118 @@
     
-for(final CompilerMessage e : errors) %7B%0A                buffer.append(e.getFile());
+buffer.append(fileName);%0A            buffer.append(%22:%5Cn%22);%0A            for(final CompilerMessage e : errors) %7B
 %0A   
@@ -7289,11 +7289,9 @@
 nd(%22
-, l
+L
 ine 

Improve error reporting.