package js.mvc;

import java.io.File;
import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import js.http.ContentType;
import js.http.HttpHeader;
import js.http.Resource;
import js.util.Files;
import js.util.Params;

/**
 * Resource implementation used to send files back to the client. It takes care to initialize HTTP response headers and
 * serialize wrapped file to HTTP response output stream. Current implementation disable cache.
 *
 * @author Iulian Rotaru
 * @version final
 */
public final class FileResource implements Resource {
	/** Resource file path. */
	private final File file;
	/** Resource content type. */
	private final String contentType;

	/**
	 * Create file resource for requested file path and content type inferred from extension. Uses
	 * {@link ContentType#forFile(File)} to infer content type.
	 *
	 * @param path file path, absolute or relative to current working directory.
	 * @throws IllegalArgumentException if path argument is null or empty or does not denote an ordinary file.
	 */
	public FileResource(String path) {
		this(file(path));
	}

	/**
	 * Create file resource for requested file and content type inferred from extension. Uses {@link ContentType#forFile(File)}
	 * to infer content type.
	 *
	 * @param file source file, absolute or relative to current working directory.
	 * @throws IllegalArgumentException if file argument is null or not an ordinary file.
	 */
	public FileResource(File file) {
		this(file, ContentType.forFile(file));
	}

	/**
	 * Create file resource for requested file path and content type.
	 *
	 * @param path file path, absolute or relative to current working directory.
	 * @param contentType file content type.
	 * @throws IllegalArgumentException if path argument is null or empty or does not denote an ordinary file.
	 * @throws IllegalArgumentException if contentType argument is null.
	 */
	public FileResource(String path, ContentType contentType) {
		this(file(path), contentType);
	}

	/**
	 * Create file resource for requested file and content type.
	 *
	 * @param file source file, absolute or relative to current working directory.
	 * @param contentType file content type.
	 * @throws IllegalArgumentException if file argument is null or not an ordinary file.
	 * @throws IllegalArgumentException if contentType argument is null.
	 */
	public FileResource(File file, ContentType contentType) {
		Params.notNull(file, "File");
		Params.isFile(file, "File");
		Params.notNull(contentType, "Content type");
		this.file = file;
		this.contentType = contentType.getValue();
	}

	/**
	 * Create file for given path.
	 *
	 * @param path file path.
	 * @return newly create file.
	 * @throws IllegalArgumentException if path argument is null or empty.
	 */
	private static File file(String path) {
		Params.notNullOrEmpty(path, "File path");
		return new File(path);
	}

	/**
	 * Serialize this file resource to HTTP response. Disable cache and set content type and content length.
	 *
	 * @param httpResponse HTTP response.
	 * @throws IOException if writing to underlying stream fails.
	 */
	@Override
	public void serialize(HttpServletResponse httpResponse) throws IOException {
		httpResponse.setHeader(HttpHeader.CACHE_CONTROL, HttpHeader.NO_CACHE);
		httpResponse.addHeader(HttpHeader.CACHE_CONTROL, HttpHeader.NO_STORE);
		httpResponse.setHeader(HttpHeader.PRAGMA, HttpHeader.NO_CACHE);
		httpResponse.setDateHeader(HttpHeader.EXPIRES, 0);
		httpResponse.setContentType(contentType);
		httpResponse.setHeader(HttpHeader.CONTENT_LENGTH, Long.toString(file.length()));
		Files.copy(file, httpResponse.getOutputStream());
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies;

import de.cologneintelligence.fitgoodies.htmlparser.FitCell;
import de.cologneintelligence.fitgoodies.htmlparser.FitRow;
import de.cologneintelligence.fitgoodies.valuereceivers.ValueReceiver;

import java.util.List;

import static de.cologneintelligence.fitgoodies.util.FitUtils.saveGet;

public class ColumnFixture extends Fixture {

	private String[] columnParameters;
	private ValueReceiver[] columnBindings;

	private boolean hasExecuted = false;

	// Traversal ////////////////////////////////

	/**
	 * Replacement of {@code doRows(Parse)} which resolves question marks
	 * in the first row and calls fit.ColumnFixture.doRows(Parse).
	 * <p>
	 * Question marks represent method calls,
	 * so getValue() and getValue? are equivalent.
	 *
     * @param rows rows to be processed
     */

	@Override
	protected void doRows(final List<FitRow> rows) throws Exception {
        FitRow headerRow = rows.get(0);
        columnParameters = extractColumnParameters(headerRow);
		bind(headerRow);
		super.doRows(rows.subList(1, rows.size()));
	}

	@Override
	protected void doRow(FitRow row) {
		hasExecuted = false;
		try {
			reset();
			super.doRow(row);
			if (!hasExecuted) {
				execute();
			}
		} catch (Exception e) {
            row.exception(e);
		}
	}

	/**
	 * Replacement of {@code doCell} which resolves cross-references
	 * before calling the original {@code doCell} method of fit.
	 *
	 * @param cell   the cell to check
	 * @param column the selected column
	 * @see Fixture#doCell(FitCell, int) fit.Fixture.doCell(Parse, int)
	 */
	@Override
	protected void doCell(final FitCell cell, final int column) {
		ValueReceiver receiver = columnBindings[column];

		String currentCellParameter = saveGet(column, columnParameters);
		if (receiver != null && !cell.getFitValue().trim().isEmpty() && receiver.canSet()) {
			setValue(cell, receiver, currentCellParameter);
		} else {
			check(cell, receiver, currentCellParameter);
		}
	}

	private void setValue(FitCell cell, ValueReceiver receiver, String currentCellParameter) {
		try {
			String text = validator.preProcess(cell);
			Object object = typeHandlerFactory.getHandler(receiver.getType(), currentCellParameter).parse(text);
			receiver.set(this, object);
		} catch (Exception e) {
            cell.exception(e);
		}
	}

	public void check(FitCell cell, ValueReceiver valueReceiver, String currentCellParameter) {
		if (!hasExecuted) {
			try {
				execute();
			} catch (Exception e) {
                cell.exception(e);
			}
			hasExecuted = true;
		}
		super.check(cell, valueReceiver, currentCellParameter);
	}

	public void reset() throws Exception {
		// about to process first cell of row
	}

	public void execute() throws Exception {
		// about to process first method call of row
	}

	// Utility //////////////////////////////////

	protected void bind(FitRow heads) {
		columnBindings = new ValueReceiver[heads.size()];
        for (int i = 0; i < columnBindings.length; i++) {
            FitCell cell = heads.cells().get(i);
            String name = cell.getFitValue();

			try {
				columnBindings[i] = createReceiver(this, name);
			} catch (Exception e) {
				cell.exception(e);
			}
		}
	}
}


package net.typedrest;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.*;
import static net.typedrest.URIUtils.ensureTrailingSlash;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.*;
import java.util.Base64;
import lombok.SneakyThrows;
import static org.apache.http.HttpHeaders.*;
import org.apache.http.HttpStatus;
import org.apache.http.client.fluent.*;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
import org.apache.http.message.BasicHeader;

/**
 * Entry point to a REST interface. Derive from this class and add your own set
 * of child-{@link Endpoint}s as properties.
 */
public class EntryEndpoint
        extends AbstractEndpoint {

    /**
     * Creates a new REST interface.
     *
     * @param uri The base URI of the REST interface. Missing trailing slash
     * will be appended automatically.
     */
    public EntryEndpoint(URI uri) {
        this(uri, defaultSerializer());
        setAcceptJsonHeader();
    }

    /**
     * Creates a new REST interface.
     *
     * @param uri The base URI of the REST interface. Missing trailing slash
     * will be appended automatically.
     * @param serializer Controls the serialization of entities sent to and
     * received from the server.
     */
    public EntryEndpoint(URI uri, ObjectMapper serializer) {
        super(ensureTrailingSlash(uri), Executor.newInstance(), serializer);
    }

    /**
     * Creates a new REST interface.
     *
     * @param uri The base URI of the REST interface. Missing trailing slash
     * will be appended automatically.
     * @param username The username used to authenticate against the REST
     * interface.
     * @param password The password used to authenticate against the REST
     * interface.
     */
    public EntryEndpoint(URI uri, String username, String password) {
        this(uri, username, password, defaultSerializer());
        setAcceptJsonHeader();
    }

    private static ObjectMapper defaultSerializer() {
        return new ObjectMapper()
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .findAndRegisterModules();
    }

    private void setAcceptJsonHeader() {
        defaultHeaders.add(new BasicHeader(ACCEPT, APPLICATION_JSON.getMimeType()));
    }

    /**
     * Creates a new REST interface.
     *
     * @param uri The base URI of the REST interface. Missing trailing slash
     * will be appended automatically.
     * @param username The username used to authenticate against the REST
     * interface.
     * @param password The password used to authenticate against the REST
     * interface.
     * @param serializer Controls the serialization of entities sent to and
     * received from the server.
     */
    public EntryEndpoint(URI uri, String username, String password, ObjectMapper serializer) {
        super(ensureTrailingSlash(uri), Executor.newInstance(), serializer);
        setBasicAuthHeader(username, password);
    }

    @SneakyThrows
    private void setBasicAuthHeader(String username, String password) {
        byte[] bytesEncoded = Base64.getEncoder().encode((username + ":" + password).getBytes("ISO-8859-1"));
        defaultHeaders.add(new BasicHeader(AUTHORIZATION, "Basic " + new String(bytesEncoded)));
    }

    /**
     * Fetches meta data such as links from the server.
     *
     * @throws IOException Network communication failed.
     * @throws IllegalArgumentException {@link HttpStatus#SC_BAD_REQUEST}
     * @throws IllegalAccessException {@link HttpStatus#SC_UNAUTHORIZED} or
     * {@link HttpStatus#SC_FORBIDDEN}
     * @throws FileNotFoundException {@link HttpStatus#SC_NOT_FOUND} or
     * {@link HttpStatus#SC_GONE}
     * @throws IllegalStateException {@link HttpStatus#SC_CONFLICT}
     * @throws RuntimeException Other non-success status code.
     */
    public void readMeta()
            throws IOException, IllegalArgumentException, IllegalAccessException, FileNotFoundException, IllegalStateException {
        executeAndHandle(Request.Get(uri));
    }
}


package js.mvc;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

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

import js.annotation.Controller;
import js.container.AuthorizationException;
import js.container.ContainerSPI;
import js.container.ManagedClassSPI;
import js.container.ManagedMethodSPI;
import js.http.NoSuchResourceException;
import js.http.Resource;
import js.http.encoder.ArgumentsReader;
import js.http.encoder.ArgumentsReaderFactory;
import js.http.encoder.ServerEncoders;
import js.lang.BugError;
import js.lang.InvocationException;
import js.log.Log;
import js.log.LogFactory;
import js.servlet.AppServlet;
import js.servlet.RequestContext;
import js.servlet.RequestPreprocessor;
import js.util.Types;

/**
 * Servlet implementation for resources generated dynamically, mostly views based on templates. Usually resources servlet is
 * enacted for web resources requested by links and forms. This servlet actually invokes controller method that returns the
 * {@link Resource}, then serialize that resource back to client.
 *

 * In order to enable this servlet it should be declared into deployment descriptor. Usually resources servlet is mapped to
 * .xsp extension but there is no formal requirements for that. XSP name stands for X(HT)ML Server Pages and refers
 * to server generated pages from X(HT)ML templates.
 *
 *

 * 	<servlet>
 * 		<servlet-name>xsp-servlet</servlet-name>
 * 		<servlet-class>js.mvc.ResourceServlet</servlet-class>
 * 		<load-on-startup>1</load-on-startup>
 * 	</servlet>
 * 	...
 * 	<servlet-mapping>
 * 		<servlet-name>xsp-servlet</servlet-name>
 * 		<url-pattern>*.xsp</url-pattern>
 * 	</servlet-mapping>
 *

 *

 * Request path for resource retrieval has two major parts: optional controller and resource paths. Extension is ignored but
 * query string is processed. A controller is a managed class annotated with {@link Controller} and controller from
 * request path is {@link ManagedClassSPI#getRequestPath()}. A resource is a managed method from a controller that returns a
 * {@link Resource} and resource from request path is {@link ManagedMethodSPI#getRequestPath()}.
 *
 *

 * request-path = ["/" controller] "/" resource ["." extension] ["?" query-string]
 * controller = < managed class request path >
 * resource = < managed method request path >
 *

 *
 * This servlet keeps a pool of resource methods mapped to request path, {@link #resourceMethods}. It is eagerly loaded at this
 * servlet initialization. Request handler method, {@link #handleRequest(RequestContext)} gets a request context that has
 * request path prepared, {@link RequestContext#getRequestPath()} that is used to route request to resource method. Once method
 * identified its arguments are deserialized from HTTP request. If arguments does not match method signature, resource servlet
 * responds with method not found with status code 404. If method execution fails, sends server error -
 * {@link HttpServletResponse#sendError(int, String)} with status code 500.
 *

 * If resource is private and HTTP request is not part of an authorized context this servlet redirect to login page, if
 * container has one defined, see {@link ContainerSPI#getLoginPage()}. If there is not login page performs servlet container
 * authentication via {@link HttpServletRequest#authenticate(HttpServletResponse)}.
 *
 * @author Iulian Rotaru
 * @version final
 */
public class ResourceServlet extends AppServlet {
	/** Java serialization version. */
	private static final long serialVersionUID = 1397044817485752955L;

	/** Class logger. */
	private static final Log log = LogFactory.getLog(ResourceServlet.class);

	/**
	 * Factory for invocation arguments readers. Create instances to read resource methods arguments from HTTP request,
	 * accordingly request content type.
	 */
	private final ArgumentsReaderFactory argumentsReaderFactory;

	/**
	 * Resource methods mapped to request paths. A resource method is a remotely accessible method that returns a
	 * {@link Resource}. This cache is eagerly loaded at this servlet initialization.
	 */
	private final Map<String, ManagedMethodSPI> resourceMethods;

	/** Construct resources servlet instance. */
	public ResourceServlet() {
		log.trace("ResourcesServlet()");
		argumentsReaderFactory = ServerEncoders.getInstance();
		resourceMethods = new HashMap<>();
	}

	/**
	 * Test constructor.
	 *
	 * @param argumentsReaderFactory mock arguments reader factory.
	 */
	public ResourceServlet(ArgumentsReaderFactory argumentsReaderFactory) {
		log.trace("ResourceServlet(ArgumentsReaderFactory)");
		this.argumentsReaderFactory = argumentsReaderFactory;
		resourceMethods = new HashMap<>();
	}

	/**
	 * Beside initialization performed by {@link AppServlet#init(ServletConfig)} this method takes care to load resource methods
	 * cache. A resource method is a remotely accessible method that returns a {@link Resource}. Map storage key is generated by
	 * {@link #key(ManagedMethodSPI)} based on controller and resource method request paths and is paired with retrieval key -
	 * {@link #key(String)}, generated from request path when method invocation occurs.
	 *
	 * @param config servlet configuration object.
	 * @throws UnavailableException if tiny container is not properly initialized.
	 */
	@Override
	public void init(ServletConfig config) throws UnavailableException {
		super.init(config);
		for (ManagedMethodSPI method : container.getManagedMethods()) {
			if (Types.isKindOf(method.getReturnType(), Resource.class)) {
				resourceMethods.put(key(method), method);
			}
		}
	}

	/**
	 * Handle request for a resource. Locate resource method based on request path from request context, execute method and
	 * serialize back to client returned resource. See class description for general behavior.
	 *
	 * @param context request context.
	 * @throws ServletException if a servlet container service fails.
	 * @throws IOException if reading from HTTP request or writing to HTTP response fail.
	 */
	@Override
	protected void handleRequest(RequestContext context) throws ServletException, IOException {
		// for exceptions generated before response commit uses HttpServletResponse.sendError
		// on send error, servlet container prepares error response using container internal HTML for response body
		// if <error-page> is configured into deployment descriptor servlet container uses that HTML for response body

		final HttpServletRequest httpRequest = context.getRequest();
		final HttpServletResponse httpResponse = context.getResponse();

		ArgumentsReader argumentsReader = null;
		Resource resource = null;
		try {
			ManagedMethodSPI method = resourceMethods.get(key(context.getRequestPath()));
			if (method == null) {
				throw new NoSuchMethodException(httpRequest.getRequestURI());
			}

			final Type[] formalParameters = method.getParameterTypes();
			argumentsReader = argumentsReaderFactory.getArgumentsReader(httpRequest, formalParameters);
			Object[] arguments = argumentsReader.read(httpRequest, formalParameters);

			Object controller = container.getInstance(method.getDeclaringClass());
			resource = method.invoke(controller, arguments);
			if (resource == null) {
				throw new BugError("Null resource |%s|.", httpRequest.getRequestURI());
			}
		} catch (AuthorizationException e) {
			// at this point, resource is private and need to redirect to a login page
			// if application provides one, tiny container is configured with, and use it
			// otherwise servlet container should have one
			// if no login page found send back servlet container error - that could be custom error page, if declared

			String loginPage = container.getLoginPage();
			if (loginPage != null) {
				httpResponse.sendRedirect(loginPage);
			} else {
				// expected servlet container behavior:
				// if <login-config> section exist into web.xml do what is configured there
				// else send back internal page with message about SC_UNAUTHORIZED

				// authenticate can throw ServletException if fails, perhaps because is not configured
				// let servlet container handle this error, that could be custom error page is configured
				httpRequest.authenticate(httpResponse);
			}
			return;
		} catch (NoSuchMethodException | IllegalArgumentException e) {
			// do not use AppServlet#sendError since it is encoded JSON and for resources need HTML
			dumpError(context, e);
			httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, httpRequest.getRequestURI());
			return;
		} catch (InvocationException e) {
			// do not use AppServlet#sendError since it is encoded JSON and for resources need HTML
			dumpError(context, e);
			if (e.getCause() instanceof NoSuchResourceException) {
				httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, httpRequest.getRequestURI());
			} else {
				httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getCause().getMessage());
			}
			return;
		} finally {
			if (argumentsReader != null) {
				argumentsReader.clean();
			}
		}

		// once serialization process started response becomes committed
		// and is not longer possible to use HttpServletResponse#sendError

		// let servlet container handle IO exceptions but since client already start rendering
		// there is no so much to do beside closing or reseting connection

		httpResponse.setStatus(HttpServletResponse.SC_OK);
		resource.serialize(httpResponse);
	}

	// --------------------------------------------------------------------------------------------
	// UTILITY METHODS

	/**
	 * Generate storage key for resource methods cache. This key is create from controller and resource method request paths and
	 * is used on cache initialization. It is paired with {@link #key(String)} created from request path on actual method
	 * invocation.
	 *

	 * Here is storage key syntax that should be identical with retrieval key. Key has optional controller path - missing if use
	 * default controller and resource method path. Controller path is the declaring class request path,
	 * {@link ManagedClassSPI#getRequestPath()} and resource path is managed method request path,
	 * {@link ManagedMethodSPI#getRequestPath()}.
	 *
	 *

	 * key = ["/" controller-path ] "/" resource-path
	 * controller-path = declaring class request path
	 * resource-path = managed method request path
	 *

	 *
	 * @param resourceMethod resource method.
	 * @return resource method key.
	 */
	private static String key(ManagedMethodSPI resourceMethod) {
		StringBuilder key = new StringBuilder();
		if (resourceMethod.getDeclaringClass().getRequestPath() != null) {
			key.append('/');
			key.append(resourceMethod.getDeclaringClass().getRequestPath());
		}
		key.append('/');
		key.append(resourceMethod.getRequestPath());
		return key.toString();
	}

	/**
	 * Generate retrieval key for resource methods cache. This key is used by request routing logic to locate resource method to
	 * invoke. It is based on request path extracted from request URI, see {@link RequestPreprocessor} and
	 * {@link RequestContext#getRequestPath()} - and should be identical with storage key.
	 *

	 * Retrieval key syntax is identical with storage key but is based on request path, that on its turn is extracted from
	 * request URI. In fact this method just trim query parameters and extension, if any.
	 *
	 *

	 * request-path = ["/" controller] "/" resource ["." extension] ["?" query-string]
	 * key = ["/" controller-path ] "/" resource-path
	 * controller-path = request-path controller
	 * resource-path = request-path resource
	 *

	 *
	 * @param requestPath request path identify resource to retrieve.
	 * @return resource method key.
	 */
	private static String key(String requestPath) {
		int queryParametersIndex = requestPath.lastIndexOf('?');
		if (queryParametersIndex == -1) {
			queryParametersIndex = requestPath.length();
		}
		int extensionIndex = requestPath.lastIndexOf('.', queryParametersIndex);
		if (extensionIndex == -1) {
			extensionIndex = queryParametersIndex;
		}
		return requestPath.substring(0, extensionIndex);
	}
}


package net.typedrest;

import java.io.*;
import java.net.*;
import lombok.*;
import org.apache.http.*;
import static org.apache.http.HttpHeaders.IF_NONE_MATCH;
import static org.apache.http.HttpStatus.SC_NOT_MODIFIED;
import org.apache.http.client.fluent.*;

/**
 * Base class for building REST endpoints that use ETags (entity tags) for
 * caching and to avoid lost updates.
 */
public class AbstractETagEndpoint
        extends AbstractEndpoint {

    protected AbstractETagEndpoint(Endpoint referrer, URI relativeUri) {
        super(referrer, relativeUri);
    }

    protected AbstractETagEndpoint(Endpoint referrer, String relativeUri) {
        super(referrer, relativeUri);
    }

    @AllArgsConstructor
    private class Memory {

        public final String etag;
        public final HttpEntity content;
    }

    // NOTE: Replace entire object rather than modifying it to ensure thread-safety.
    private Memory last;

    /**
     * Performs an HTTP GET request on the {@link Endpoint#getUri()} and caches
     * the response if the server sends an ETag header.
     *
     * Sends {@link HttpHeaders#IF_NONE_MATCH} if there is already a cached
     * ETag.
     *
     * @return The response of the request or the cached response if the server
     * responded with {@link HttpStatus#SC_NOT_MODIFIED}.
     *
     * @throws IOException Network communication failed.
     * @throws IllegalArgumentException {@link HttpStatus#SC_BAD_REQUEST}
     * @throws IllegalAccessException {@link HttpStatus#SC_UNAUTHORIZED} or
     * {@link HttpStatus#SC_FORBIDDEN}
     * @throws FileNotFoundException {@link HttpStatus#SC_NOT_FOUND} or
     * {@link HttpStatus#SC_GONE}
     * @throws IllegalStateException {@link HttpStatus#SC_CONFLICT}
     * @throws RuntimeException Other non-success status code.
     */
    protected HttpEntity getContent()
            throws IOException, IllegalArgumentException, IllegalAccessException, FileNotFoundException, IllegalStateException {
        Request request = Request.Get(uri);
        if (last != null) {
            request = request.addHeader(IF_NONE_MATCH, last.etag);
        }

        HttpResponse response = execute(request);
        if (response.getStatusLine().getStatusCode() == SC_NOT_MODIFIED && last != null) {
            return last.content;
        }

        handleResponse(response, request);
        Header etagHeader = response.getFirstHeader(HttpHeaders.ETAG);
        last = (etagHeader == null)
                ? null
                : new Memory(etagHeader.getValue(), response.getEntity());
        return response.getEntity();
    }

    /**
     * Performs an HTTP PUT request on the {@link Endpoint#getUri()}. Sets
     * {@link HttpHeaders#IF_MATCH} if there is a cached ETag to detect lost
     * updates.
     *
     * @param content The content to send to the server.
     * @return The response message.
     * @throws IOException Network communication failed.
     * @throws IllegalArgumentException {@link HttpStatus#SC_BAD_REQUEST}
     * @throws IllegalAccessException {@link HttpStatus#SC_UNAUTHORIZED} or
     * {@link HttpStatus#SC_FORBIDDEN}
     * @throws FileNotFoundException {@link HttpStatus#SC_NOT_FOUND} or
     * {@link HttpStatus#SC_GONE}
     * @throws IllegalStateException The content has changed since it was last
     * retrieved with {@link #getContent()}. Your changes were rejected to
     * prevent a lost update.
     * @throws RuntimeException Other non-success status code.
     */
    protected HttpResponse putContent(HttpEntity content)
            throws IOException, IllegalArgumentException, IllegalAccessException, FileNotFoundException, IllegalStateException {
        Request request = Request.Put(uri).body(content);
        if (last != null) {
            request.addHeader(HttpHeaders.IF_MATCH, last.etag);
        }

        return executeAndHandle(request);
    }

    /**
     * Performs an HTTP DELETE request on the {@link Endpoint#getUri()}. Sets
     * {@link HttpHeaders#IF_MATCH} if there is a cached ETag to detect lost
     * updates.
     *
     * @return The response message.
     * @throws IOException Network communication failed.
     * @throws IllegalArgumentException {@link HttpStatus#SC_BAD_REQUEST}
     * @throws IllegalAccessException {@link HttpStatus#SC_UNAUTHORIZED} or
     * {@link HttpStatus#SC_FORBIDDEN}
     * @throws FileNotFoundException {@link HttpStatus#SC_NOT_FOUND} or
     * {@link HttpStatus#SC_GONE}
     * @throws IllegalStateException The entity has changed since it was last
     * retrieved with {@link #getContent()}. Your delete call was rejected to
     * prevent a lost update.
     * @throws RuntimeException Other non-success status code.
     */
    protected HttpResponse deleteContent()
            throws IOException, IllegalArgumentException, IllegalAccessException, FileNotFoundException, IllegalStateException {
        Request request = Request.Delete(uri);
        if (last != null) {
            request.addHeader(HttpHeaders.IF_MATCH, last.etag);
        }

        return executeAndHandle(request);
    }
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.cologneintelligence.fitgoodies.runners;

import java.io.File;
import java.io.Serializable;
import java.util.Comparator;
import java.util.regex.Pattern;

/**
 * Compares two FileInformation objects. They are sorted in alphabetical order,
 * with two exceptions: files with the name "setup.html" are the first
 * file in the directory, files named "teardown.html" are processed as
 * the last item in a directory
 */
public abstract class FitFilenameComparator<T> implements Comparator<T>, Serializable {
	private static final long serialVersionUID = 2;

	private static final int RIGHT_IS_SMALLER = 1;
	private static final int LEFT_IS_SMALLER = -1;
	private static final int EQUAL = 0;

	private static final Pattern setupPattern =
			Pattern.compile("([\\\\/]|^)setup\\.html?$", Pattern.CASE_INSENSITIVE);

	private static final Pattern tearDownPattern =
			Pattern.compile("([\\\\//]|^)teardown\\.html?$", Pattern.CASE_INSENSITIVE);

	/**
	 * Compare two FileInformation objects. See {@link FitFilenameComparator}
	 * for more information.
	 *
	 * @param lhs left hand side
	 * @param rhs right hand side
	 * @return < 0 if lhs is greater, > 0 if rhr
	 * is greater, 0 if they are equal.
	 */
	@Override
	public final int compare(final T lhs, final T rhs) {

		int c = compareDirectoryNames(lhs, rhs);

		if (c == EQUAL) {
			if (isSetupFile(lhs) || isTearDownFile(rhs)) {
				return LEFT_IS_SMALLER;
			} else if (isSetupFile(rhs) || isTearDownFile(lhs)) {
				return RIGHT_IS_SMALLER;
			} else {
				return compareFullnames(lhs, rhs);
			}
		} else if (c <= LEFT_IS_SMALLER) {
			return subCompare(lhs, rhs);
		} else {
			return -subCompare(rhs, lhs);
		}
	}

	private int subCompare(final T lhs, final T rhs) {
		if (isSubDirectory(lhs, rhs) && isSetupFile(lhs)) {
			return LEFT_IS_SMALLER;
		} else if (isSubDirectory(lhs, rhs) && isTearDownFile(lhs)) {
			return RIGHT_IS_SMALLER;
		} else {
			return compareFullnames(lhs, rhs);
		}
	}

	private boolean isSubDirectory(final T lhs, final T rhs) {
		String leftDir = getParent(lhs);
		String rightDir = getParent(rhs);

		return leftDir.length() <= rightDir.length() && rightDir.startsWith(leftDir);
	}

	private int compareFullnames(final T lhs, final T rhs) {
		return getFile(lhs).getPath().compareTo(getFile(rhs).getPath());
	}

	private int compareDirectoryNames(final T lhs, final T rhs) {
		return getParent(lhs).compareTo(getParent(rhs));
	}

	private String getParent(T file) {
		String parent = getFile(file).getParent();
		return parent == null ? "" : parent;
	}

	private boolean isTearDownFile(final T fo) {
		return tearDownPattern.matcher(getFileName(fo)).find();
	}

	private boolean isSetupFile(final T fo) {
		return setupPattern.matcher(getFileName(fo)).find();
	}

	protected abstract File getFile(T file);

	protected abstract String getFileName(T file);
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.cologneintelligence.fitgoodies.database;

import de.cologneintelligence.fitgoodies.RowFixture;
import de.cologneintelligence.fitgoodies.database.dynamic.ResultSetWrapper;
import de.cologneintelligence.fitgoodies.htmlparser.FitRow;
import de.cologneintelligence.fitgoodies.util.DependencyManager;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * This fixture takes a table name as an argument, fetches its content and
 * compares it with the given table. It's possible to filter the resulting
 * table using an optional where clause.
 *

 * The credentials used to authenticate can be set in the code using the
 * {@link de.cologneintelligence.fitgoodies.database.SetupHelper} or using a
 * {@link de.cologneintelligence.fitgoodies.database.SetupFixture} in your HTML table.
 *

 * The following table would compare the columns "c1" and "c2"
 * of the SQL table "tab1" with given the HTML table, if the value of
 * c1 is bigger than 10:
 *

 *

 * fitgoodies.database.TableFixture
 * table=tab1
 * where=c1 > 10
 *

 *
c1	c2

 *
15	20

 *
18	93

 *

 */
public class TableFixture extends RowFixture {
	private Connection connection;
	private ResultSetWrapper resultSetWrapper;
	private Statement statement;
	public String table;
    public String where;

	private void connect() {
		try {
			final SetupHelper helper = DependencyManager.getOrCreate(SetupHelper.class);
			connection = helper.getConnection();
		} catch (final SQLException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Closes the SQL statement.
	 *
	 * @throws Exception propagated to fit.
	 */
	@Override
	public void tearDown() throws Exception {
		if (statement != null) {
			statement.close();
			statement = null;
		}
		super.tearDown();
	}

	@Override
	protected void doRows(List<FitRow> rows) throws Exception {
        connect();
        resultSetWrapper = new ResultSetWrapper(getResultSet());
		super.doRows(rows);
	}

	/**
	 * Generates a {@code java.sql.ResultSet} by using the saved connection.
	 * This method queries the table {@code tableName} and appends
	 * {@code where} as an optional where clause.
	 *
	 * @return the {@code ResultSet} which the query returned
	 */
	protected ResultSet getResultSet() {
		if (table == null) {
			throw new IllegalArgumentException("missing parameter: table");
		}

		String whereClause = "";
		if (where != null) {
            whereClause = " WHERE " + where;
        }

        try {
			statement = connection.createStatement();
			return statement.executeQuery("SELECT * FROM " + table + whereClause);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Gets the type of the dynamic created target class.
	 *
	 * @return the type of the target class.
	 */
	@Override
	public final Class<?> getTargetClass() {
		return resultSetWrapper.getClazz();
	}

	/**
	 * Gets an array which represents the created ResultSet as an object array.
	 * The type of these objects can be determined via {@code getTargetClass()}.
	 *
	 * @throws java.lang.Exception Exception thrown while generating the actual results.
	 */
	@Override
	public final Object[] query() throws Exception {
		return resultSetWrapper.getRows();
	}

	/**
	 * Returns the extracted table name.
	 *
	 * @return the table name used by the fixture.
	 */
	public final String getTable() {
		return table;
	}

}


package js.container;

import js.lang.BugError;
import js.lang.ManagedPostConstruct;
import js.log.Log;
import js.log.LogFactory;

/**
 * Execute {@link ManagedPostConstruct#postConstruct()} on managed instance. Instance post-construction is executed after
 * initialization and configuration, of course only if managed instance implements {@link ManagedPostConstruct} interface.
 *
 * @author Iulian Rotaru
 * @version final
 */
final class PostConstructInstanceProcessor implements InstanceProcessor {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(PostConstructInstanceProcessor.class);

	/**
	 * Execute post-construct on managed instance. In order to perform instance post-construction, managed instance should
	 * implement {@link ManagedPostConstruct} interface.
	 *
	 * @param managedClass managed class,
	 * @param instance instance of given managed class.
	 * @throws BugError if instance post-construction fails due to exception of user defined logic.
	 */
	@Override
	public void postProcessInstance(ManagedClassSPI managedClass, Object instance) {
		if (!(instance instanceof ManagedPostConstruct)) {
			return;
		}
		ManagedPostConstruct managedInstance = (ManagedPostConstruct) instance;
		log.debug("Post-construct managed instance |%s|", managedInstance.getClass());
		try {
			managedInstance.postConstruct();
		} catch (Throwable t) {
			throw new BugError("Managed instance |%s| post-construct fail: %s", instance, t);
		}
	}
}


package js.net;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import js.annotation.Public;
import js.annotation.Remote;
import js.core.AppFactory;
import js.lang.Event;
import js.lang.ManagedPreDestroy;
import js.log.Log;
import js.log.LogFactory;
import js.util.Params;
import js.util.Strings;

/**
 * Implementation for event stream manager. This class implements event stream opening handshake and stream destroying, server
 * side logic. Basically client subscribes using {@link #subscribe(EventStreamConfig)} and got an unique session ID then send it
 * as parameter on URL used to connect the event stream service. Note that session ID is usable once and only for a short period
 * of time.
 *

 * When server gets a request for an event stream, {@link EventStreamServlet} extracts session ID from request URL and uses
 * {@link #createEventStream(String)} to create event stream. Then servlet blocks on {@link EventStream#loop()} till client
 * disconnects. Server uses {@link EventStream#push(Event)} to push events that client reads in a loop. When client disconnects,
 * servlet uses {@link #destroyEventStream(EventStream)} to release resources.
 *
 *

 * // on client, event stream manager is declared as remote managed class
 * EventStreamManager eventStreamManager = factory.getInstance(EventStreamManager.class);
 *
 * EventStreamConfig config = new EventStreamConfig();
 * config.setKeepAlivePeriod(KEEP_ALIVE_PERIOD);
 * String sessionID = eventStreamManager.subscribe(config);
 *
 * // prepare push service request URL based on session ID, e.g. http://server.com/service/1234.event
 * URL pushServiceURL = getEventStreamURL(sessionID);
 * HttpURLConnection connection = (HttpURLConnection) pushServiceURL.openConnection();
 *
 * // event stream servlet gets request, extracts session ID and uses stream manager to create event stream
 *
 * // read events from connection input stream,	 pushed by server event stream
 * EventReader eventReader = new EventReader(connection.getInputStream());
 * for (;;) {
 * 	Event event = eventReader.read();
 * 	if (event == null) {
 * 		break;
 * 	}
 * ...
 *

 *
 * @author Iulian Rotaru
 * @version final
 */
final class EventStreamManagerImpl implements EventStreamManager, ManagedPreDestroy, EventStreamManagerSPI {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(EventStreamManagerImpl.class);

	/**
	 * Event stream subscribe transaction TTL. It is the session ID validity period between
	 * {@link #subscribe(EventStreamConfig)} and {@link #createEventStream(String)}. When this period is exceeded session ID
	 * become stale and URL request for event stream is rejected, in which case {@link EventStreamServlet} responds with bad
	 * request, 400.
	 */
	private static int SUBSCRIBE_TTL = 10000;

	/**
	 * Storage for running event stream references. It is updated by {@link #createEventStream(String)} and
	 * {@link #destroyEventStream(EventStream)} and used by {@link #preDestroy()} to release event streams still opened at event
	 * stream manager destroy.
	 *

	 * This list is used from different threads and is synchronized.
	 */
	private final List<EventStream> eventStreams;

	/** Application factory. */
	private final AppFactory factory;

	/**
	 * Registered sessions for event stream opening handshake. This map content is volatile. It keeps session ID for a short
	 * period of time between {@link #subscribe(EventStreamConfig)} and {@link #createEventStream(String)}.
	 */
	private final Map<SessionID, EventStreamConfig> sessions;

	/**
	 * Construct event stream manager instance and inject application factory.
	 *
	 * @param factory application factory.
	 */
	public EventStreamManagerImpl(AppFactory factory) {
		this.eventStreams = Collections.synchronizedList(new ArrayList<EventStream>());
		this.factory = factory;
		this.sessions = new HashMap<>();
	}

	/** Closes all event streams still opened when event stream manager is destroyed. */
	@Override
	public void preDestroy() {
		if (eventStreams.isEmpty()) {
			return;
		}

		// EventStream#close signals stream loop that breaks
		// as a consequence, EventStreamServlet ends current request processing and call this#closeEventStream
		// this#closeEventStream removes event stream from this#eventStreams list resulting in concurrent change
		// to cope with this concurrent change uses a temporary array

		// toArray API is a little confusing for me regarding returned array
		// to be on safe side let toArray to determine array size
		// also I presume returned array is not altered by list updates

		for (EventStream eventStream : eventStreams.toArray(new EventStream[0])) {
			log.debug("Force close stale event stream |%s|.", eventStream);
			eventStream.close();
		}
	}

	/**
	 * Subscribes to events stream and returns session ID. This method is remote accessible and public. It returns a session ID
	 * with a short life time, for about 10 seconds.
	 *

	 * This method creates a new {@link SessionID} and stores given configuration object to {@link #sessions} map, with created
	 * session ID as key. Session storage is ephemere. It lasts only for {@link #SUBSCRIBE_TTL} period of time; after that
	 * session ID becomes stale.
	 *

	 * This method should be followed by {@link #createEventStream(String)}, with returned session ID as argument.
	 *
	 * @param config events stream configuration object.
	 * @return events stream session ID.
	 */
	@Remote
	@Public
	public String subscribe(EventStreamConfig config) {
		SessionID sessionID = new SessionID();
		log.debug("Store event stream parameters for session |%s|.", sessionID);
		sessions.put(sessionID, config);
		return sessionID.getValue();
	}

	@Override
	public EventStream createEventStream(String sessionID) {
		Params.notNullOrEmpty(sessionID, "Session ID");

		long staleTimestamp = System.currentTimeMillis() - SUBSCRIBE_TTL;
		EventStreamConfig config = null;

		synchronized (eventStreams) {
			Iterator<Map.Entry<SessionID, EventStreamConfig>> it = sessions.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<SessionID, EventStreamConfig> entry = it.next();
				if (entry.getKey().getTimestamp() < staleTimestamp) {
					log.warn("Stale event stream session |%s|. Remove it.", entry.getKey());
					it.remove();
					continue;
				}
				if (sessionID.equals(entry.getKey().getValue())) {
					config = entry.getValue();
					it.remove();
				}
			}
		}

		if (config == null) {
			log.warn("Ignore missing session ID |%s|. Most probably was remove because of life time expiration. It is also possible to be a forged event stream request. Check server logs for removed stale event stream sessions.", sessionID);
			return null;
		}

		// this will allow for multiple, named, event streams per application
		EventStream eventStream = factory.getInstance(EventStream.class);
		eventStream.config(config);
		eventStreams.add(eventStream);
		return eventStream;
	}

	@Override
	public void destroyEventStream(EventStream eventStream) {
		eventStreams.remove(eventStream);
	}

	// --------------------------------------------------------------------------------------------
	// UTILITY CLASSES

	/**
	 * Unique session ID used on event stream opening handshake. See {@link EventStreamManagerImpl} for event stream opening
	 * handshake.
	 *
	 * @author Iulian Rotaru
	 * @version final
	 */
	private static class SessionID {
		/** Seed for session ID sequential generation. */
		private final static AtomicInteger ID_SEED = new AtomicInteger();
		/** Session ID generated in sequence based on {@link #ID_SEED}. */
		private String id;
		/** Value is a string guaranteed to be unique, see {@link Strings#UUID()}. */
		private String value;
		/** Timestamp for session ID creation. */
		private long timestamp;

		/** Create new event stream session ID. */
		SessionID() {
			this.id = Integer.toString(ID_SEED.incrementAndGet());
			this.value = Strings.UUID();
			this.timestamp = System.currentTimeMillis();
		}

		/**
		 * UUID value of event stream session ID.
		 *
		 * @return session ID value.
		 * @see #value
		 */
		String getValue() {
			return value;
		}

		/**
		 * Session ID timestamp.
		 *
		 * @return session ID timestamp.
		 * @see #timestamp
		 */
		long getTimestamp() {
			return timestamp;
		}

		/** Instance string representation. */
		@Override
		public String toString() {
			return id;
		}
	}
}


package net.typedrest.vaadin.views;

import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.http.*;
import com.google.common.eventbus.EventBus;
import net.typedrest.BlobEndpoint;
import net.typedrest.vaadin.events.BlobUploadEvent;
import com.vaadin.data.Validator;
import com.vaadin.ui.*;
import com.vaadin.ui.themes.ValoTheme;
import org.vaadin.dialogs.ConfirmDialog;

/**
 * Base class for building view components operating on a {@link BlobEndpoint}.
 */
public abstract class AbstractBlobView extends AbstractEndpointView<BlobEndpoint> {

    protected final Button deleteButton = new Button("Delete", x -> delete()) {
        {
            addStyleName(ValoTheme.BUTTON_DANGER);
        }
    };

    protected AbstractBlobView(BlobEndpoint endpoint, EventBus eventBus) {
        super(endpoint, eventBus);
    }

    @Override
    protected void onLoad() throws IOException, IllegalArgumentException, IllegalAccessException, IllegalStateException {
        try {
            endpoint.probe();
        } catch (IOException | IllegalAccessException | RuntimeException ex) {
            // HTTP OPTIONS server-side implementation is optional
        }

        handleAllowedMethods();
    }

    /**
     * Enables or disabled buttons based on the "Allow" HTTP header.
     */
    protected void handleAllowedMethods() {
        endpoint.isDownloadAllowed().ifPresent(this::setDownloadEnabled);
        endpoint.isUploadAllowed().ifPresent(this::setUploadEnabled);
        endpoint.isDeleteAllowed().ifPresent(this::setDeleteEnabled);
    }

    /**
     * Controls whether a download button is shown.
     *
     * @param val Turns the feature on or off.
     */
    public abstract void setDownloadEnabled(boolean val);

    /**
     * Controls whether an upload button is shown.
     *
     * @param val Turns the feature on or off.
     */
    public abstract void setUploadEnabled(boolean val);

    /**
     * Called to upload new blob data to the server.
     */
    protected void upload() {
        try {
            onUpload();
            eventBus.post(new BlobUploadEvent(endpoint));
            Notification.show("Success", "Upload complete", Notification.Type.TRAY_NOTIFICATION);
        } catch (IOException | IllegalArgumentException | IllegalAccessException | IllegalStateException ex) {
            onError(ex);
        }
    }

    /**
     * Handler for uploading the blob.
     *
     * @throws IOException Network communication failed.
     * @throws IllegalArgumentException {@link HttpStatus#SC_BAD_REQUEST}
     * @throws IllegalAccessException {@link HttpStatus#SC_UNAUTHORIZED} or
     * {@link HttpStatus#SC_FORBIDDEN}
     * @throws FileNotFoundException {@link HttpStatus#SC_NOT_FOUND} or
     * {@link HttpStatus#SC_GONE}
     * @throws IllegalStateException The entity has changed since it was last
     * retrieved with {@link #onLoad()}. Your changes were rejected to prevent a
     * lost update.
     * @throws Validator.InvalidValueException The user input is invalid.
     * @throws RuntimeException Other non-success status code.
     */
    protected abstract void onUpload()
            throws IOException, IllegalArgumentException, IllegalAccessException, FileNotFoundException, IllegalStateException, Validator.InvalidValueException;

    /**
     * Controls whether a delete button is shown.
     *
     * @param val Turns the feature on or off.
     */
    public void setDeleteEnabled(boolean val) {
        deleteButton.setVisible(val);
    }

    /**
     * Deletes the blob.
     */
    protected void delete() {
        String question = "Are you sure you want to delete the data from the server?";
        ConfirmDialog.show(getUI(), question, new ConfirmDialog.Listener() {
            @Override
            public void onClose(ConfirmDialog cd) {
                if (cd.isConfirmed()) {
                    try {
                        endpoint.delete();
                        close();
                    } catch (IOException | IllegalArgumentException | IllegalAccessException | IllegalStateException ex) {
                        onError(ex);
                    } catch (RuntimeException ex) {
                        // Must explicitly send unhandled exceptions to error handler.
                        // Would otherwise get swallowed silently within callback handler.
                        getUI().getErrorHandler().error(new com.vaadin.server.ErrorEvent(ex));
                    }
                }
            }
        });
    }
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies.typehandler;

import de.cologneintelligence.fitgoodies.date.FitDateHelper;
import de.cologneintelligence.fitgoodies.util.DependencyManager;

import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Date;

/**
 * Parser which is able to convert a string into a {@code java.sql.Timestamp}.
 * It uses the {@link Timestamp#valueOf(String)} method first. If it fails, the
 * class uses the {@link FitDateHelper} to allow the user
 * to set individual formats.
 * <p>
 * If the cell is parameterized, the date format can be set individually.
 * The parameter must have the format "{@code locale, format}".
 * Example: "{@code en_US, MM/dd/yyyy}".
 */
public class SqlTimestampTypeHandler extends TypeHandler<Timestamp> {
	private final FitDateHelper dateFitDateHelper;

	/**
	 * Creates a new TypeAdapter which bases on {@code ta}.
	 *
	 * @param convertParameter a parameter in the format [locale, format] which
	 *                         represents the format to use
	 */
	public SqlTimestampTypeHandler(final String convertParameter) {
		super(convertParameter);
		this.dateFitDateHelper = DependencyManager.getOrCreate(FitDateHelper.class);
	}

	/**
	 * Returns the destination class which is managed by this parser.
	 *
	 * @return java.sql.Timestamp.class
	 */
	@Override
	public Class<Timestamp> getType() {
		return Timestamp.class;
	}

	/**
	 * Parses a string and converts it into a {@code java.sql.Timestamp} object.
	 *
	 * @param s {@code String} which will be converted
	 * @return {@code java.sql.Timestamp} object which is represented by {@code s}
	 * @throws ParseException if the date could not be parsed
	 */
	@Override
	public final Timestamp unsafeParse(final String s) throws ParseException {
		try {
			return Timestamp.valueOf(s);
		} catch (final IllegalArgumentException e) {
			return new Timestamp(dateFitDateHelper.parse(s, parameter).getTime());
		}
	}

	@Override
	public String toString(Timestamp s) {
		return dateFitDateHelper.toString(new Date(s.getTime()));
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies;

import de.cologneintelligence.fitgoodies.htmlparser.FitCell;
import de.cologneintelligence.fitgoodies.htmlparser.FitRow;
import de.cologneintelligence.fitgoodies.htmlparser.FitTable;
import de.cologneintelligence.fitgoodies.typehandler.TypeHandler;
import de.cologneintelligence.fitgoodies.typehandler.TypeHandlerFactory;
import de.cologneintelligence.fitgoodies.util.DependencyManager;
import de.cologneintelligence.fitgoodies.util.FitUtils;
import de.cologneintelligence.fitgoodies.valuereceivers.ValueReceiver;
import de.cologneintelligence.fitgoodies.valuereceivers.ValueReceiverFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Fixture {
    protected Map<String, String> args;

    protected TypeHandlerFactory typeHandlerFactory = DependencyManager.getOrCreate(TypeHandlerFactory.class);
    protected ValueReceiverFactory valueReceiverFactory = DependencyManager.getOrCreate(ValueReceiverFactory.class);
    protected Validator validator = DependencyManager.getOrCreate(Validator.class);

    /**
     * Sets the fixture parameters.
     *

     * Normally, these values are generated by reading the first line of the
     * table. This method is primary useful for debugging. You won't need it
     * otherwise.
     *
     * @param args parameters to store in {@code args}
     */
    public final void setParams(final Map<String, String> args) {
        this.args = args;
    }

    /**
     * Looks up a given parameter in the fixture's argument list.
     *
     * @param paramName the parameter name to look up
     * @return the parameter value, if it could be found, {@code null} otherwise
     * @see #getArg(String, String) {@link #getArg(String, String)}
     * @see #getArg(String, String)
     * {@link #getArg(String, String)}
     */
    public final String getArg(final String paramName) {
        return getArg(paramName, null);
    }

    /**
     * Finds an argument in an given argument list.
     *

     * The search for an argument is case-insensitive and whitespaces at the
     * beginning and the end are ignored. The argument's name and its value are
     * separated by an equal sign. All these inputs will result in
     * "world", if you look up "hello":
     *

     * "hello=world", " hello = world ",
     * "HeLLo = world".

     *

     * Note: the case of the value is unchanged.
     *
     * @param argName      the argument name to look up
     * @param defaultValue the result value if the argument does not exist
     * @return the argument's value without namespaces, or defaultValue
     * @see #getArgNames() getArgs
     * @see #copyParamsToFixture() copyParamsToFixture
     */
    public String getArg(final String argName, final String defaultValue) {
        if (args == null || !args.containsKey(argName)) {
            return defaultValue;
        } else {
            return validator.preProcess(args.get(argName));
        }
    }

    // Traversal //////////////////////////

    /**
     * Initializes the fixture arguments, call {@code setUp},
     * {@code fit.Fixture.doTable(Parse)} and {@code tearDown()}.
     *
     * @param table the table to be processed
     */
    public void doTable(FitTable table) {
        copyParamsToFixture();

        try {
            setUp();

            try {
                doRows(table.rows());
            } catch (Exception e) {
                table.exception(e);
            }

            tearDown();
        } catch (final Exception e) {
            table.exception(e);
        }
        table.finishExecution();
    }

    /**
     * extracts and removes parameters from a row.
     *
     * @param row row to process
     * @return extracted parameters
     */
    protected String[] extractColumnParameters(FitRow row) {
        final List<String> result = new ArrayList<>();

        for (FitCell cell : row.cells()) {
            result.add(FitUtils.extractCellParameter(cell));
        }

        return result.toArray(new String[result.size()]);
    }

    protected void doRows(List<FitRow> rows) throws Exception {
        for (FitRow row : rows) {
            doRow(row);
        }
    }

    protected void doRow(FitRow row) throws Exception {
        doCells(row.cells());
    }

    protected void doCells(List<FitCell> cells) {
        for (int i = 0; i < cells.size(); i++) {
            FitCell cell = cells.get(i);

            try {
                doCell(cell, i);
            } catch (Exception e) {
                cell.exception(e);
            }
        }
    }

    protected void doCell(FitCell cell, int columnNumber) throws Exception {
        cell.ignore();
    }

    /**
     * Does nothing. Override it to initialize the fixture.
     * The method is called before doTables.
     *
     * @throws Exception any kind of exception aborts the execution of this fixture
     */
    public void setUp() throws Exception {
    }

    /**
     * Does nothing. Override it to tear down the fixture.
     * The method is called after doTables.
     *
     * @throws Exception any kind of exception aborts the execution of this fixture
     */
    public void tearDown() throws Exception {
    }

    // Parameters ///////////////////////////////

    /**
     * Reads the argument list and copies all values in public members
     * with the same name.
     *

     * If these members do not exist, the argument is skipped. You can still
     * read the values using {@link #getArg(String, String)}.
     *
     * @see #getArg(String, String) getArg
     */
    public void copyParamsToFixture() {
        for (final String fieldName : getArgNames()) {
            ValueReceiver valueReceiver;
            try {
                valueReceiver = valueReceiverFactory.createReceiver(this, fieldName);
            } catch (NoSuchMethodException | NoSuchFieldException e) {
                continue;
            }

            TypeHandler handler = createTypeHandler(valueReceiver, null);

            String fieldValueString = getArg(fieldName, null);
            try {
                Object fieldValue = handler.parse(fieldValueString);
                valueReceiver.set(this, fieldValue);
            } catch (Exception ignored) {
            }
        }
    }

    /**
     * Returns all argument names.
     *
     * @return a list of all given argument names.
     * @see #getArg(String, String) getArg
     * @see #copyParamsToFixture()  copyParamsToFixture
     */
    protected String[] getArgNames() {
        if (args == null) {
            return new String[]{};
        }

        return args.keySet().toArray(new String[args.keySet().size()]);
    }

    // Utility //////////////////////////////////

    /**
     * Replacement of {@code check} which resolves cross-references
     * before calling the original check method of fit.
     *
     * @param cell                 the cell to check
     * @param valueReceiver        TypeAdapter to use
     * @param currentCellParameter parameter of the current cell
     */
    public void check(final FitCell cell, ValueReceiver valueReceiver, String currentCellParameter) {
        validator.process(cell, valueReceiver, currentCellParameter, typeHandlerFactory);
    }

    protected TypeHandler createTypeHandler(ValueReceiver valueReceiver, String cellParameter) {
        return typeHandlerFactory.getHandler(valueReceiver.getType(), cellParameter);
    }

    protected ValueReceiver createReceiver(Object target, String name) throws NoSuchMethodException, NoSuchFieldException {
        return valueReceiverFactory.createReceiver(target, name);
    }

    protected ValueReceiver createReceiver(Object target, Method method) throws NoSuchMethodException, NoSuchFieldException {
        return valueReceiverFactory.createReceiver(target, method);
    }
}


package js.container;

import java.util.HashMap;
import java.util.Map;
import java.util.TimerTask;

import js.core.Factory;
import js.lang.AsyncExceptionListener;
import js.lang.ManagedLifeCycle;
import js.log.Log;
import js.log.LogFactory;

/**
 * Scheduler for periodic and timeout tasks. This scheduler is a managed class and its instance if retrieved from factory, see
 * sample code. If task logic throws exception this scheduler takes care to invoke {@link AsyncExceptionListener}, of course if
 * application registers one.
 *

 * This class supports two kinds of tasks: executed periodic with a given period and execute once, after a certain
 * timeout expires. Any pending task can be canceled, see purge methods.
 *
 *

 * Timer timer = Factory.getInstance(Timer.class);
 * timer.period(this, period);
 * . . .
 * timer.purge(this);
 *

 *
 * @author Iulian Rotaru
 * @version draft
 */
public final class Timer implements ManagedLifeCycle {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(Timer.class);

	/** Java {@link java.util.Timer} instance. */
	private java.util.Timer timer;

	/** Pending user defined tasks mapped to Java {@link TimerTask}. */
	private Map<Object, TimerTask> tasks;

	@Override
	public synchronized void postConstruct() throws Exception {
		this.timer = new java.util.Timer();
		this.tasks = new HashMap<Object, TimerTask>();
	}

	@Override
	public synchronized void preDestroy() throws Exception {
		for (TimerTask task : this.tasks.values()) {
			task.cancel();
		}
		this.tasks.clear();
		this.timer.cancel();
	}

	/**
	 * Schedule periodic task execution.
	 *
	 * @param periodicTask periodic task instance,
	 * @param period requested execution period, milliseconds.
	 */
	public synchronized void period(final PeriodicTask periodicTask, long period) {
		TimerTask task = new PeriodicTaskImpl(periodicTask);
		this.tasks.put(periodicTask, task);
		this.timer.schedule(task, 0L, period);
	}

	/**
	 * Schedule timeout task, reseting timeout period if given timeout task is already pending.
	 *
	 * @param timeoutTask timeout task instance,
	 * @param timeout timeout value, milliseconds.
	 */
	public synchronized void timeout(final TimeoutTask timeoutTask, long timeout) {
		TimerTask task = this.tasks.get(timeoutTask);
		if (task != null) {
			task.cancel();
			this.tasks.values().remove(task);
		}
		task = new TimeoutTaskImpl(timeoutTask);
		this.tasks.put(timeoutTask, task);
		this.timer.schedule(task, timeout);
	}

	/**
	 * Purge periodic task. If given periodic task is not scheduled this method is NOP.
	 *
	 * @param periodicTask periodic task instance.
	 */
	public synchronized void purge(PeriodicTask periodicTask) {
		purgeTask(periodicTask);
	}

	/**
	 * Purge timeout task. If timeout task is not scheduled this method is NOP.
	 *
	 * @param timeoutTask timeout task instance.
	 */
	public synchronized void purge(TimeoutTask timeoutTask) {
		purgeTask(timeoutTask);
	}

	/**
	 * Purge task helper method. If given task is not scheduled this method does nothing.
	 *
	 * @param task pending user defined task.
	 */
	private void purgeTask(Object task) {
		TimerTask timerTask = this.tasks.get(task);
		if (timerTask != null) {
			timerTask.cancel();
			this.tasks.values().remove(timerTask);
		}
	}

	/**
	 * Prevent instantiation with new operator.
	 */
	private Timer() {
	}

	// ----------------------------------------------------
	// TASK CLASS IMPLEMENTATIONS

	/**
	 * Periodic task implementation.
	 *
	 * @author Iulian Rotaru
	 */
	private static class PeriodicTaskImpl extends TimerTask {
		private PeriodicTask periodicTask;

		public PeriodicTaskImpl(PeriodicTask periodicTask) {
			super();
			this.periodicTask = periodicTask;
		}

		@Override
		public void run() {
			try {
				this.periodicTask.onPeriod();
			} catch (Throwable throwable) {
				Timer.log.error("%s: %s: %s", this.periodicTask.getClass(), throwable.getClass(), throwable.getMessage());
				AsyncExceptionListener listener = Factory.getOptionalInstance(AsyncExceptionListener.class);
				if (listener != null) {
					listener.onAsyncException(throwable);
				}
			}
		}
	}

	/**
	 * Timeout task implementation.
	 *
	 * @author Iulian Rotaru
	 */
	private static class TimeoutTaskImpl extends TimerTask {
		private TimeoutTask timeoutTask;

		public TimeoutTaskImpl(TimeoutTask timeoutTask) {
			super();
			this.timeoutTask = timeoutTask;
		}

		@Override
		public void run() {
			try {
				this.timeoutTask.onTimeout();
			} catch (Throwable throwable) {
				Timer.log.error("%s: %s: %s", this.timeoutTask.getClass(), throwable.getClass(), throwable.getMessage());
				AsyncExceptionListener listener = Factory.getOptionalInstance(AsyncExceptionListener.class);
				if (listener != null) {
					listener.onAsyncException(throwable);
				}
			}
		}
	}
}


package js.mvc;

import java.io.IOException;
import java.util.Properties;

import js.core.Factory;
import js.lang.BugError;
import js.lang.Config;
import js.lang.ConfigBuilder;
import js.lang.ConfigException;
import js.lang.Configurable;
import js.log.Log;
import js.log.LogFactory;
import js.servlet.RequestContext;
import js.util.Classes;
import js.util.I18nFile;
import js.util.I18nPool;
import js.util.I18nRepository;
import js.util.Types;

/**
 * View manager implementation. At its core views manager has a pool of {@link ViewMeta} instances, one for each application
 * view. Views meta pool is populated from configuration object that describes templates repositories. Using meta data, view
 * manager creates and initializes view instances, see {@link #getView(String)}.
 *

 * Views repositories configuration. Views configuration is stored into views section from application descriptor.
 * There is a child element for every repository. A repository is stored in a directory and has optional files pattern. To
 * select all files uses '*' wild card.
 *
 *

 * <views>
 * 	<repository path="/var/www/vhosts/site/${language}" files-pattern="*.html" class="js.web.view.XspView" />
 * 	<repository path="/var/www/vhosts/site/${language}/fo" files-pattern="*.fo" class="js.web.view.PdfView">
 * 		<property name="producer" value="Phantom Co." />
 * 		<property name="creator" value="John Doe" />
 * 	</repository>
 * </views>
 *

 *
 * @author Iulian Rotaru
 * @version final
 */
final class ViewManagerImpl implements ViewManager, Configurable {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(ViewManager.class);

	/** Default view implementation is {@link XspView}. */
	private static final String DEF_IMPLEMENTATION = XspView.class.getName();

	/**
	 * Application global repository for views meta. Because all application views meta are stored in the same repository, view
	 * name should be unique across application. For example, a HTML view cannot have the same name with a PDF view, even
	 * templates are stored into different directories.
	 */
	private I18nPool<ViewMeta> viewsMetaPool;

	/**
	 * Create views meta pool from given managed view configuration object. Configuration object is described on class
	 * description.
	 *
	 * @param config view manager configuration object.
	 * @throws ConfigException if configuration object is not valid.
	 * @throws IOException if views meta pool cannot be created.
	 */
	@Override
	public void config(Config config) throws ConfigException, IOException {
		for (Config repositorySection : config.findChildren("repository")) {
			// view manager configuration section is named <views>
			// a <views> configuration section has one or many <repository> child sections
			// scan every repository files accordingly files pattern and add meta views to views meta pool

			// load repository view implementation class and perform insanity checks
			String className = repositorySection.getAttribute("class", DEF_IMPLEMENTATION);
			Class<?> implementation = Classes.forOptionalName(className);
			if (implementation == null) {
				throw new ConfigException("Unable to load view implementation |%s|.", className);
			}
			if (!Types.isKindOf(implementation, View.class)) {
				throw new ConfigException("View implementation |%s| is not of proper type.", className);
			}
			if (!Classes.isInstantiable(implementation)) {
				throw new ConfigException("View implementation |%s| is not instantiable. Ensure is not abstract or interface and have default constructor.", implementation);
			}
			@SuppressWarnings("unchecked")
			Class<? extends View> viewImplementation = (Class<? extends View>) implementation;

			// load repository path and files pattern and create I18N repository instance
			String repositoryPath = repositorySection.getAttribute("path");
			if (repositoryPath == null) {
				throw new ConfigException("Invalid views repository configuration. Missing <path> attribute.");
			}
			String filesPattern = repositorySection.getAttribute("files-pattern");
			if (filesPattern == null) {
				throw new ConfigException("Invalid views repository configuration. Missing <files-pattern> attribute.");
			}

			ConfigBuilder builder = new I18nRepository.ConfigBuilder(repositoryPath, filesPattern);
			I18nRepository repository = new I18nRepository(builder.build());
			if (viewsMetaPool == null) {
				// uses first repository to initialize i18n pool
				// limitation for this solution is that all repositories should be the kind: locale sensitive or not
				viewsMetaPool = repository.getPoolInstance();
			}
			Properties properties = repositorySection.getProperties();

			// traverses all files from I18N repository instance and register view meta instance
			// builder is used by view meta to load the document template
			for (I18nFile template : repository) {
				ViewMeta meta = new ViewMeta(template.getFile(), viewImplementation, properties);
				if (viewsMetaPool.put(meta.getName(), meta, template.getLocale())) {
					log.warn("Override view |%s|", meta);
				} else {
					log.debug("Register view |%s|", meta);
				}
			}
		}
	}

	/**
	 * Create view instance based on view meta data identified by name and request locale. View instance is created from view
	 * implementation class - {@link ViewMeta#getImplementation()}.
	 *
	 * @param viewName application unique view name.
	 * @return newly create view instance.
	 * @throws BugError if given view name does not designate an existing view.
	 */
	@Override
	public View getView(String viewName) {
		RequestContext context = Factory.getInstance(RequestContext.class);
		ViewMeta meta = viewsMetaPool.get(viewName, context.getLocale());
		if (meta == null) {
			throw new BugError("View |%s| not found. View name may be misspelled, forgot to add template file or template name doesn't match views files pattern.", viewName);
		}
		AbstractView view = (AbstractView) Classes.newInstance(meta.getImplementation());
		view.setMeta(meta);
		return view;
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies.file;

import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Browses a directory recursively for files which match a given filter.
 */
public class RecursiveFileSelector implements Iterator<File> {
	private final List<File> dirs = new LinkedList<>();
	private final String pattern;
	private File[] files;
	private int fileIndex = 0;

	/**
	 * Creates a new iterator.
	 *
	 * @param directory       directory to browse
	 * @param filenamePattern filter to use. Must be a regular expression.
	 */
	public RecursiveFileSelector(final File directory, final String filenamePattern) {
		this.pattern = filenamePattern;
		dirs.add(directory);
		cacheNext();
	}

	private boolean cacheNext() {
		fileIndex = 0;
		do {
			if (dirs.size() > 0) {
				File dir = dirs.remove(0);

				final File[] children = dir.listFiles();
				if (children != null) {
					for (File child : children) {
						if (child.isDirectory()) {
							dirs.add(child);
						}
					}
				}

				files = dir.listFiles(new SimpleRegexFilter(pattern));
			} else {
				return false;
			}
		} while (files == null || files.length == 0);

		return true;
	}

	/**
	 * Returns true if the iteration has more elements.
	 * (In other words, returns true if next would
	 * return an element rather than throwing an exception.)
	 *
	 * @return true if the iterator has more elements.
	 */
	@Override
	public final boolean hasNext() {
		if (files == null) {
			return false;
		} else if (fileIndex < files.length) {
			return true;
		} else {
			return cacheNext();
		}
	}

	/**
	 * Returns the next matching file.
	 *
	 * @return the next element in the iteration.
	 * @throws NoSuchElementException iteration has no more elements
	 */
	@Override
	public final File next() {
		if (files == null || fileIndex >= files.length) {
			if (!cacheNext()) {
				throw new NoSuchElementException();
			}
		}
		return files[fileIndex++];
	}

	/**
	 * Not implemented.
	 */
	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.cologneintelligence.fitgoodies.file;

import de.cologneintelligence.fitgoodies.Fixture;
import de.cologneintelligence.fitgoodies.util.DependencyManager;

import java.io.File;

/**
 * Fixture which allows a user to select a file from its arguments.
 *

 * A fixture which extends the {@code AbstractFileReaderFixture} can
 * easily access a selected file via {@link #getFile()}. The selected encoding
 * can be retrieved via {@link #getEncoding()}.
 *

 * There are two ways of selecting a file. Either by providing a full file path,
 * or by using a pattern. In the latter case, the first matching file is selected:
 *

 * MyFixture	file=/path/to/file	encoding=latin-1
 *

...

 *

 * MyFixture	dir=/path/to/	pattern=.*\.txt
 *

...

 *

 * Either {@code file} or {@code pattern} must be provided.
 * {@code dir} and {@code encoding} are retrieved from the
 * {@link FileFixtureHelper} if they are missing.
 */
public abstract class AbstractFileReaderFixture extends Fixture {
	private FileInformationWrapper wrapper;
	private FileInformation file;
	private String encoding;

	public AbstractFileReaderFixture() {
		this(new SimpleFileInformationWrapper());
	}

	AbstractFileReaderFixture(FileInformationWrapper wrapper) {
		this.wrapper = wrapper;
	}

	/**
	 * Reads the given parameters and initializes the values of
	 * {@link #getEncoding()} and {@link #getFile()}.
	 *
	 * @throws java.lang.Exception General Exception, e.g. the file cannot be opened.
	 */
	@Override
	public void setUp() throws Exception {
		super.setUp();

        FileFixtureHelper helper = DependencyManager.getOrCreate(FileFixtureHelper.class);
        if (encoding == null) {
            encoding = helper.getEncoding();
        }

		String fileName = getArg("file");
		if (fileName == null) {
			File provider = helper.getDirectory();

			String dir = getArg("dir");
			if (dir != null) {
				provider = new File(dir);
			}

			if (provider == null) {
				throw new RuntimeException("No directory selected");
			}

			String pattern = helper.getPattern();
			pattern = getArg("pattern", pattern);

			FileSelector fs = new FileSelector(provider, pattern);
			file = wrapper.wrap(fs.getFirstFile());
		} else {
			file = wrapper.wrap(new File(fileName).getAbsoluteFile());
		}
	}

	/**
	 * Gets the selected file.
	 *
	 * @return the matching file
	 */
	public FileInformation getFile() {
		return file;
	}

	/**
	 * Gets the selected encoding.
	 *
	 * @return the encoding name
	 */
	public String getEncoding() {
		return encoding;
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies.file;

import de.cologneintelligence.fitgoodies.file.readers.FixedLengthRecordReader;
import de.cologneintelligence.fitgoodies.htmlparser.FitCell;
import de.cologneintelligence.fitgoodies.htmlparser.FitRow;
import de.cologneintelligence.fitgoodies.typehandler.BooleanTypeHandler;
import de.cologneintelligence.fitgoodies.typehandler.TypeHandler;

import java.util.List;

/**
 * {@link AbstractFileRecordReaderFixture} implementation which processes
 * files with fixed record length using a
 * {@link de.cologneintelligence.fitgoodies.file.readers.FixedLengthRecordReader}.
 * There is one optional parameter which correspondents to the newLineAtEOR
 * parameter of
 * {@link FixedLengthRecordReader#FixedLengthRecordReader(java.io.BufferedReader, int[], boolean)}.
 * It is called "skipEOL".
 *

 * The first row must contain the field width of each field:
 *

 *
fitgoodies.file.FixedLengthFileRecordFixture
 * file=/myfile.txt

 *
3	10	25

 *
1	record 1	more content

 *
2	record 2	some text

 *

 */
public class FixedLengthFileRecordFixture extends AbstractFileRecordReaderFixture {
	private boolean noEol;

	@Override
	public void setUp() throws Exception {
		super.setUp();
		noEol = BooleanTypeHandler.parseBool(getArg("skipEOL", "0"));
	}

	@Override
	protected void doRows(List<FitRow> rows) throws Exception {
		int[] width = extractWidth(rows.get(0));

		if (width == null) {
			return;
		}

        setRecordReader(new FixedLengthRecordReader(
                getFile().openBufferedReader(getEncoding()),
                width, noEol));
        super.doRows(rows.subList(1, rows.size()));
    }

	/**
	 * Parses the first table row and generates an {@code int} array
	 * which contains the length of each record field.
	 *
	 * @param row row to process
	 * @return length of each field
	 */
	public final int[] extractWidth(FitRow row) {
		int[] width = new int[row.size()];

        int i = 0;
        for (FitCell cell : row.cells()) {
			try {
                String value = validator.preProcess(cell.getFitValue());
                TypeHandler handler = typeHandlerFactory.getHandler(Integer.class, null);
                width[i++] = (Integer) handler.parse(value);
			} catch (Exception e) {
				cell.exception(e);
				return null;
			}
		}
		return width;
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.cologneintelligence.fitgoodies.database;

import de.cologneintelligence.fitgoodies.ActionFixture;
import de.cologneintelligence.fitgoodies.util.DependencyManager;

/**
 * Enables a user to setup a database connection from HTML.
 *

 * Such a setup table could look like this (you need ojdbc*.jar in your
 * classpath):
 *

 *
fitgoodies.database.SetupFixture

 *
provider	oracle.jdbc.OracleDriver

 *
connectionString	jdbc:oracle:thin:@Server:Port:Database

 *
user	MY_USER

 *
password	My_PaSsWoRd

 *

 */
public class SetupFixture extends ActionFixture {
	/**
	 * Calls {@link #user(String)}, using the next cell as its parameter.
	 *
	 * @throws Exception propagated to fit
	 */
	public void user() throws Exception {
		transformAndEnter();
	}

	/**
	 * Sets the database username to {@code userName}.
	 * The username can be received using {@link SetupHelper#getUser()}.
	 *
	 * @param userName the database username
	 */
	public void user(final String userName) {
		SetupHelper helper = DependencyManager.getOrCreate(SetupHelper.class);
		helper.setUser(userName);
	}

	/**
	 * Calls {@link #password(String)}, using the next cell as its parameter.
	 *
	 * @throws Exception propagated to fit
	 */
	public void password() throws Exception {
		transformAndEnter();
	}

	/**
	 * Sets the database password to {@code password}.
	 * The password can be received using {@link SetupHelper#getPassword()}.
	 *
	 * @param password the database password
	 */
	public void password(final String password) {
		SetupHelper helper = DependencyManager.getOrCreate(SetupHelper.class);
		helper.setPassword(password);
	}

	/**
	 * Calls {@link #connectionString(String)}, using the next cell as its parameter.
	 *
	 * @throws Exception propagated to fit
	 */
	public void connectionString() throws Exception {
		transformAndEnter();
	}

	/**
	 * Sets the database connection string to {@code uri}.
	 * The connection string can be received using
	 * {@link SetupHelper#getConnectionString()}.
	 *
	 * @param uri the database connectoin string
	 */
	public void connectionString(final String uri) {
		SetupHelper helper = DependencyManager.getOrCreate(SetupHelper.class);
		helper.setConnectionString(uri);
	}

	/**
	 * Calls {@link #provider(String)}, using the next cell as its parameter.
	 *
	 * @throws Exception propagated to fit
	 */
	public void provider() throws Exception {
		transformAndEnter();
	}

	/**
	 * Sets the database provider. The {@code providerName} must be a
	 * fully qualified class name and the class must be in java's class path.
	 *
	 * @param providerName fully qualified class name of a java.sql.Driver.
	 * @throws Exception thrown if the class could not be found, initialized or
	 *                   casted. Propagated to fit.
	 */
	public void provider(final String providerName) throws Exception {
		SetupHelper.setProvider(providerName);
	}
}


package js.http.captcha;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpSession;

import js.annotation.Public;
import js.annotation.Remote;
import js.annotation.RequestPath;
import js.core.AppContext;
import js.http.NoSuchResourceException;
import js.http.Resource;
import js.lang.BugError;
import js.lang.Config;
import js.lang.ConfigException;
import js.lang.Configurable;
import js.log.Log;
import js.log.LogFactory;
import js.mvc.FileResource;
import js.mvc.ResourceServlet;
import js.servlet.RequestContext;
import js.util.Params;

/**
 * Implementation of Simple CAPTCHA service. This implementation provide services to captcha widget from script
 * library, see widget API.
 *

 * An application using Simple CAPTCHA needs to configure it via application and deployment descriptors.
 *
 * There are only two properties: images repository path and images set size. Images repository is not required to be public. In
 * fact is good practice to hide it on private file system, of course accessible by web server.
 *
 *

 * <captcha>
 * 	<property name="captcha.repository.path" value="/usr/share/captcha" />
 * 	<property name="captcha.set.size" value="5" />
 * </captcha>
 *

 *

 * Images repository is mandatory and should be an existing absolute path. Repository should contain only images related to
 * CAPTCHA. Sub-directories are not recommended, they are skipped anyway. Challenge set size is optional with default value 6.
 *

 * Challenge images are not loaded by client explicitly by name but via an unique token, usable only once. URL for images is
 * always of form .../captcha/image?token and is served by {@link ResourceServlet}. An application that used Simple
 * CAPTCHA need to enable resource servlet and route /captcha/* requests to it.
 *
 *

 * <servlet-mapping>
 * 	<servlet-name>xsp-servlet</servlet-name>
 * 	<url-pattern>/captcha/*</url-pattern>
 * </servlet-mapping>
 *

 *
 * @author Iulian Rotaru
 * @version final
 */
@RequestPath("captcha")
@Public
final class Captcha implements Configurable {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(Captcha.class);

	/** Web session key name for stored challenge instances. */
	private static final String CHALENGES_KEY = "challenges-key";

	/** Application context. */
	private final AppContext context;

	/** Challenge images directory configured from application descriptor. */
	private File imagesRepositoryDir;

	/** The number of challenge images that are displayed at once, default to 6. Configurable from application descriptor. */
	private int challengeSetSize;

	/**
	 * Create CAPTCHA manager instance with injected application context.
	 *
	 * @param context application context.
	 */
	public Captcha(AppContext context) {
		this.context = context;
	}

	/**
	 * Configure Simple CAPTCHA service provider. See class description for configuration object properties.
	 *

	 * Note that images repository should contain only images related to CAPTCHA; sub-directories are not scanned, only direct
	 * child files.
	 *
	 * @param config configuration section from application descriptor.
	 */
	@Override
	public void config(Config config) throws ConfigException {
		imagesRepositoryDir = config.getProperty("captcha.repository.path", File.class);
		if (imagesRepositoryDir == null) {
			throw new ConfigException("Missing <captcha.repository.path> property from CAPTCHA configuration.");
		}
		challengeSetSize = config.getProperty("captcha.set.size", int.class, 6);

		if (!imagesRepositoryDir.exists()) {
			throw new ConfigException("CAPTCHA images repository |%s| does not exist.", imagesRepositoryDir);
		}
		if (!imagesRepositoryDir.isDirectory()) {
			throw new ConfigException("CAPTCHA images repository |%s| is not a directory.", imagesRepositoryDir);
		}
		if (!imagesRepositoryDir.isAbsolute()) {
			throw new ConfigException("CAPTCHA images repository |%s| is not absolute path.", imagesRepositoryDir);
		}

		int imagesCount = imagesRepositoryDir.list().length;
		if (imagesCount == 0) {
			throw new ConfigException("CAPTCHA images repository |%s| is empty.", imagesRepositoryDir);
		}
		if (imagesCount <= challengeSetSize) {
			throw new ConfigException("Challenge set size is larger that avaliable images count from CAPTCHA repository.");
		}
	}

	/**
	 * Create a new challenge and store it on HTTP session. If HTTP session does not exist, create it. This challenge is used by
	 * client to update user interface accordingly. It is called on initial form rendering and every time user choose to load
	 * another challenge if current one cannot be solved.
	 *

	 * Client is free to display multiple Simple CAPTCHA instances but all instances should have distinct index, generated in
	 * sequence, starting from zero.
	 *
	 * @param captchaIndex CAPTCHA instance index,
	 * @return newly created CAPTCHA challenge.
	 * @throws BugError if this CAPTCHA manager is not properly initialized.
	 */
	@Remote
	public Challenge getChallenge(int captchaIndex) {
		if (imagesRepositoryDir == null) {
			log.debug("Simple CAPTCHA not properly initialized. Missing <captcha> section from application descriptor:\r\n" + //
					"\t<captcha>\r\n" + //
					"\t\t<property name=\"captcha.repository.path\" value=\"/path/to/captcha/images\" />\r\n" + //
					"\t\t<property name=\"captcha.set.size\" value=\"5\" />\r\n" + //
					"\t</captcha>");
			throw new BugError("Missing CAPTCHA images repository. Most probably <captcha> section is missing from application descriptor.");
		}

		Challenge challenge = new Challenge(imagesRepositoryDir, challengeSetSize);
		getChallenges().put(captchaIndex, challenge);
		return challenge;
	}

	/**
	 * Verify challenge response. If response is not correct returns a new challenge used by client to update user interface. It
	 * is highly recommended to refresh challenge on wrong answer to prevent response guessing.
	 *

	 * Instance index should be the same provided to {@link #getChallenge(int)}. This is critical and client should consider
	 * this constrain. Anyway, using wrong CAPTCHA instance index is not a security breach; it always consider response as
	 * invalid.
	 *
	 * @param captchaIndex CAPTCHA instance index,
	 * @param challengeResponse challenge response from client.
	 * @return null if response is correct or a new challenge.
	 * @throws IllegalArgumentException if given challenge response is null or empty.
	 * @throws IllegalStateException if there is no challenge on session.
	 */
	@Remote
	public Challenge verifyResponse(int captchaIndex, String challengeResponse) throws IllegalArgumentException, IllegalStateException {
		Params.notNullOrEmpty(challengeResponse, "Challenge response");
		Challenge challenge = getChallenges().get(captchaIndex);
		if (challenge == null) {
			throw new IllegalStateException("Invalid challenge on session.");
		}
		return challenge.verifyResponse(challengeResponse) ? null : getChallenge(captchaIndex);
	}

	/**
	 * Get the image identified by given token so that client is able to display it. In order to protect against recording
	 * attack, a challenge image is identified by a token that is valid only once.
	 *

	 * If given token does not identify a valid challenge image this method throws {@link NoSuchResourceException} and container
	 * responds with 404 Not Found.
	 *
	 * @param token challenge image identifier.
	 * @return requested challenge image.
	 * @throws IllegalArgumentException if token argument is null or empty.
	 * @throws NoSuchResourceException if there is no challenge on session or token does not identify a challenge image.
	 */
	@Remote
	@RequestPath("image")
	public Resource getImage(String token) throws IllegalArgumentException, NoSuchResourceException {
		Params.notNullOrEmpty(token, "Image token");
		for (Challenge challenge : getChallenges().values()) {
			File image = challenge.getImage(token);
			if (image != null) {
				return new FileResource(image);
			}
		}
		throw new NoSuchResourceException();
	}

	/**
	 * Get challenge instances storage bound to current HTTP request. Challenges storage is kept on HTTP session. It is legal to
	 * have multiple Simple CAPTCHA instances on a single page but client should use zero based numeric index to identify
	 * instances.
	 *

	 * This method has side effects: it creates HTTP session if is not already created.
	 *
	 * @return challenge instances storage.
	 * @throws IllegalStateException if attempt to create new session after response commit.
	 */
	private Map<Integer, Challenge> getChallenges() {
		// here is a circular package dependency that is hard to avoid
		// js.servlet package depends on js.http packages and this js.http.captcha package depends on js.servlet
		// as a consequence js.http.captcha package cannot be used externally without js.servlet
		HttpSession session = context.getInstance(RequestContext.class).getSession(true);

		@SuppressWarnings("unchecked")
		Map<Integer, Challenge> challenges = (Map<Integer, Challenge>) session.getAttribute(CHALENGES_KEY);
		if (challenges == null) {
			challenges = new HashMap<>();
			session.setAttribute(CHALENGES_KEY, challenges);
		}
		return challenges;
	}
}