
/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 *
 * Portions Copyright Apache Software Foundation.
 */ 

package com.sun.enterprise.web.connector.grizzly.standalone;

import com.sun.enterprise.web.connector.grizzly.Constants;
import com.sun.enterprise.web.connector.grizzly.FileCache;
import com.sun.enterprise.web.connector.grizzly.FileCacheFactory;
import com.sun.enterprise.web.connector.grizzly.SelectorThread;
import com.sun.enterprise.web.connector.grizzly.SocketChannelOutputBuffer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.apache.coyote.ActionCode;

import org.apache.coyote.Adapter;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.tomcat.util.buf.Ascii;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.MimeHeaders;

/**
 * Simple HTTP based Web Server. Part of this class is from Tomcat sandbox code
 * from Costin Manolache.
 *
 * @author Jeanfrancois Arcand
 */
public class StaticResourcesAdapter implements Adapter {
    
    static Properties contentTypes=new Properties();
    
    static {
        initContentTypes();
    }
    
    static void initContentTypes() {
        contentTypes.put("html", "text/html");
        contentTypes.put("txt", "text/plain");
    }
        
    private String rootFolder = ".";
    
    private File rootFolderF;
    
    private ConcurrentHashMap<String,File> cache
            = new ConcurrentHashMap<String,File>();
    
    /**
     * Grizzly FileCache
     */
    private FileCache fileCache;
    
    
    public StaticResourcesAdapter() {
    }
    
    
    public void setRootFolder(String s) {
        rootFolder = s;
        rootFolderF = new File(rootFolder);
        try {
            rootFolder = rootFolderF.getCanonicalPath();
            SelectorThread.logger().log(Level.INFO, "Servicing page from: " 
                + rootFolder);
        
        } catch (IOException e) {
        }
    }
    
    
    public void service(Request req, final Response res) throws Exception {
        MessageBytes mb = req.requestURI();
        ByteChunk requestURI = mb.getByteChunk();
        
        if (fileCache == null){                   
            fileCache = FileCacheFactory.getFactory(req.getLocalPort())
                            .getFileCache();
        }      
        
        if (!fileCache.sendCache(requestURI.getBytes(), requestURI.getStart(),
                                 requestURI.getLength(), getChannel(res),
                                 keepAlive(req))){
            
            String uri = req.requestURI().toString();
            if (uri.indexOf("..") >= 0) {
                res.setStatus(404);
                return;
            }        
            // local file
            File resource = cache.get(uri);
            if (resource == null){
                resource = new File(rootFolderF, uri);
                cache.put(uri,resource);
            }

            if (!resource.getCanonicalPath().startsWith(rootFolder)) {
                res.setStatus(404);
                return;
            }

            if (resource.isDirectory()) {
                resource = new File(resource, "index.html");
                cache.put(uri,resource);            
            }

            if (!resource.exists()) {
                SelectorThread.logger().log(Level.INFO,"File not found  " + resource);
                res.setStatus(404);
                return;
            }        
            res.setStatus(200);

            int dot=uri.lastIndexOf(".");
            if( dot > 0 ) {
                String ext=uri.substring(dot+1);
                String ct=getContentType(ext);
                if( ct!=null) {
                    res.setContentType(ct);
                }
            }

            res.setContentLength((int)resource.length());        
            ((SocketChannelOutputBuffer)res.getOutputBuffer()).flush();

            SocketChannel socketChannel = 
                    ((SocketChannelOutputBuffer)res.getOutputBuffer()).getChannel();
            FileInputStream stream = new FileInputStream(resource);
            FileChannel fileChannel = stream.getChannel();  

            long nread =0;
            while (nread < resource.length() && socketChannel.isOpen()) { 
                nread += fileChannel.transferTo(nread, fileChannel.size(), 
                        socketChannel);
            }

            try{
                stream.close();
            } catch (IOException ex){
                ;
            }

            try{
                fileChannel.close();
            } catch (IOException ex){
                ;
            }
            
            fileCache.add(FileCache.DEFAULT_SERVLET_NAME,rootFolder,uri,
                          res.getMimeHeaders(),false);            
        }
        
        res.finish();
    }

    
    private SocketChannel getChannel(Response res) {
        SocketChannelOutputBuffer buffer = (SocketChannelOutputBuffer) res.getOutputBuffer();
        SocketChannel channel = buffer.getChannel();
        return channel;
    }
 
    
    /**
     * Get the keep-alive header.
     */
    private boolean keepAlive(Request request){
        MimeHeaders headers = request.getMimeHeaders();

        // Check connection header
        MessageBytes connectionValueMB = headers.getValue("connection");
        if (connectionValueMB != null) {
            ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
            if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
                return false;
            } else if (findBytes(connectionValueBC, 
                                 Constants.KEEPALIVE_BYTES) != -1) {
                return true;
            }
        }
        return true;
    }    
        
    
    /**
     * Specialized utility method: find a sequence of lower case bytes inside
     * a ByteChunk.
     */
    protected int findBytes(ByteChunk bc, byte[] b) {

        byte first = b[0];
        byte[] buff = bc.getBuffer();
        int start = bc.getStart();
        int end = bc.getEnd();

        // Look for first char 
        int srcEnd = b.length;

        for (int i = start; i <= (end - srcEnd); i++) {
            if (Ascii.toLower(buff[i]) != first) continue;
            // found first char, now look for a match
            int myPos = i+1;
            for (int srcPos = 1; srcPos < srcEnd; ) {
                    if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
                break;
                    if (srcPos == srcEnd) return i - start; // found it
            }
        }
        return -1;
    }
    
    
    public String getContentType( String ext ) {
        return contentTypes.getProperty( ext, "text/plain" );
    }

    
    public void afterService(Request req, Response res) 
        throws Exception {
        try{
            req.action( ActionCode.ACTION_POST_REQUEST , null);
        }catch (Throwable t) {
            t.printStackTrace();
        } finally {
            // Recycle the wrapper request and response
            req.recycle();
            res.recycle();
        }        
    }

    
    public void fireAdapterEvent(String string, Object object) {
    }

    
    public String getRootFolder() {
        return rootFolder;
    }

    
}