You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2017/09/06 13:24:25 UTC

svn commit: r1807480 [3/5] - in /sling/whiteboard/microsling: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/microsling/ src/main/java/org/apache/sling/micros...

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/EspReader.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/EspReader.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/EspReader.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/EspReader.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,758 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.microsling.scripting.helpers;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>EspReader</code> is a <code>FilterReader</code> which takes
+ * JSP like input and produces plain ECMA script output. The filtering
+ * modifications done on the input comprise the following :
+ * <ul>
+ * <li>Template text (HTML) is wrapped by out.write(). At most one line of
+ * text is wrapped into a single write() call. Double quote characters in the
+ * template text (e.g. for HTML tag attribute values) are escaped.
+ * <li>ECMA code is written to the output as is.
+ * <li>ECMA slash star (/*) comments are also written as is.
+ * <li>ECMA slash slash (//) comments are written as is.
+ * <li>JSP style template comments (<%-- -->) are also removed from the
+ * stream. Lineendings (LFs and CRLFs) are written, though.
+ * <li>HTML comments (<!-- -->) are not treated specially. Rather they are
+ * handled as plain template text written to the output wrapped in
+ * out.write(). The consequence of this behavious is, that as in JSP ECMA
+ * expressions may be included within the comments.
+ * </ul>
+ * <p>
+ * The nice thing about this reader is, that the line numbers of the resulting
+ * stream match the line numbers of the matching contents of the input stream.
+ * Due to the insertion of write() calls, column numbers will not necessarily
+ * match, though. This is especially true if you mix ECMA code tags (<% %>)
+ * with template text on the same line.
+ * <p>
+ * For maximum performance it is advisable to not create the EspReader with a
+ * plain FileReader or InputStreamReader but rather with a BufferedReader based
+ * on one of the simpler Readers. The reasons for this is, that we call the base
+ * reader character by character. This in turn is not too performing if the base
+ * reader does not buffer its input.
+ */
+public class EspReader extends FilterReader {
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(EspReader.class);
+
+    /**
+     * Default parser state. This is the state the parser starts running in. In
+     * this state all text is treated as template text, which should be wrapped
+     * by out.write() line by line.
+     */
+    private static final byte PARSE_STATE_ESP = 1;
+
+    /**
+     * ECMA script reading state. When in this state everything upto to the next
+     * <code>%&gt;</code> is written to the output verbatim with three
+     * exceptions : ECMA slash star comments are handed over to handled by the
+     * {@link #PARSE_STATE_ECMA_COMMENT} state, quoted strings are handled in
+     * the {@link #PARSE_STATE_QUOTE} state and ECMA slash slash comments are
+     * handled in {@link #PARSE_STATE_ECMA_COMMENTL} state.
+     */
+    private static final byte PARSE_STATE_ECMA = 3;
+
+    /**
+     * ECMA script expression reading state. This state works exactly the same
+     * as the {@link #PARSE_STATE_ECMA} state with one exception: The whole
+     * code enclosed in the <code>&lt;%=</code> ... <code>%&gt;</code> tags
+     * is itself wrapped with a <code>out.write()</code> statement
+     * verbatim.
+     */
+    private static final byte PARSE_STATE_ECMA_EXPR = 4;
+
+    /**
+     * JSP comment reading state. When in this state everything upto the closing
+     * <code>--&gt;</code> tag is removed from the stream.
+     */
+    private static final byte PARSE_STATE_JSP_COMMENT = 5;
+
+    /**
+     * ECMA quoted string reading state. When in this state everything is
+     * written exactly as in the input stream upto the closing quote, which
+     * matches the opening quote.
+     */
+    private static final byte PARSE_STATE_QUOTE = 6;
+
+    /**
+     * Verbatim copy state. When in this state as many as verbatimChars
+     * characters are returned unchecked. As soon as this number of characters
+     * is returned, the last state is popped from the stack. This state is
+     * mainly used to (re-)inject static text into the output without further
+     * processing.
+     */
+    private static final byte PARSE_STATE_VERBATIM = 8;
+
+    /**
+     * ECMA Comment reading state. When in this state, an ECMA slash star
+     * comment is read (and completely returned).
+     */
+    private static final byte PARSE_STATE_ECMA_COMMENT = 9;
+
+    /**
+     * ECMA Comment reading state. When in this state, an ECMA slash slash
+     * comment is read (and completely returned).
+     */
+    private static final byte PARSE_STATE_ECMA_COMMENTL = 10;
+
+    /**
+     * To work with lookahead and character insertion, we use a PushbackReader.
+     */
+    private PushbackReader input;
+
+    /**
+     * Current parse state. This field contains one of the
+     * <code>PARSE_STATE</code> constants.
+     */
+    private byte state;
+
+    /**
+     * Stack of states. Whenever we enter a new state, the old state is pushed
+     * onto the stack. When a state is left, the previous one is popped from the
+     * stack.
+     *
+     * @see #pushState(byte)
+     * @see #popState()
+     * @see #state
+     */
+    private Stack<Byte> stateStack;
+
+    /**
+     * This value is set to true, if the parser is expected to insert a
+     * out.write() call into the input stream when in state
+     * {@link #PARSE_STATE_ESP}. When this field is true, it is not
+     * necessairily the case, that we are at the start of a real text line.
+     */
+    private boolean lineStart;
+
+    /**
+     * If characters are put into the pushback Stream that should be given back
+     * verbatim, this value is set to the number of such consecutive characters.
+     */
+    private int verbatimChars;
+
+    /**
+     * During String matching this is the character used for string quoting.
+     */
+    private char quoteChar;
+
+    /**
+     * Set to true if an escape character (\) has been encountered within a
+     * quoted string.
+     */
+    private boolean escape;
+
+    /**
+     * Whether the definition of the out variable has already been written or not.
+     * The initial value is <code>true</code> indicating it has still to be
+     * defined.
+     *
+     * @see #startWrite(String)
+     */
+    private boolean outUndefined = true;
+
+    /**
+     * Create an EspReader on top of the given <code>baseReader</code>. The
+     * constructor wraps the input reader with a <code>PushbackReader</code>,
+     * so that input stream modifications may be handled transparently by our
+     * {@link #doRead()} method.
+     */
+    public EspReader(Reader baseReader) {
+        super(baseReader);
+        this.input = new PushbackReader(baseReader, 100);
+        this.stateStack = new Stack<Byte>();
+        this.lineStart = true;
+        this.verbatimChars = -1;
+        this.quoteChar = 0;
+        this.escape = false;
+
+        // Start in ESP (template text) state
+        pushState(PARSE_STATE_ESP);
+    }
+
+    /**
+     * Check whether we may block at the next read() operation. We may be ready
+     * if and only if our input reader is ready. But this does not guarantee
+     * that we won't block, as due to filtering there may be more than one
+     * character needed from the input to return one.
+     *
+     * @return <code>true</code> if a character is available on the
+     *         <code>PushbackReader</code>.
+     * @throws IOException if the reader is not open
+     */
+    public boolean ready() throws IOException {
+        ensureOpen();
+        return input.ready();
+    }
+
+    /**
+     * Return the next filtered character. This need not be the next character
+     * of the input stream. It may be a character from the input reader, after
+     * having skipped filtered characters or it may be a character injected due
+     * to translation of template text to ECMA code.
+     *
+     * @return the next character after filtering or -1 at the end of the input
+     *         reader
+     * @throws IOException if the reader is not open
+     */
+    public int read() throws IOException {
+        ensureOpen();
+        return doRead();
+    }
+
+    /**
+     * Fill the given buffer with filtered or injected characters. This need not
+     * be the next characters of the input stream. It may be characters from the
+     * input reader, after having skipped filtered characters or it may be a
+     * characters injected due to translation of template text to ECMA code.
+     * This method is exactly the same as
+     * <code>read(cbuf, 0, cbuf.length)</code>.
+     *
+     * @param cbuf The character buffer to fill with (filtered) characters
+     * @return the number of characters filled in the buffer or -1 at the end of
+     *         the input reader.
+     * @throws IOException if the reader is not open
+     */
+    public int read(char[] cbuf) throws IOException {
+        return read(cbuf, 0, cbuf.length);
+    }
+
+    /**
+     * Fill the buffer from the offset with the number of characters given. This
+     * need not be the next characters of the input stream. It may be characters
+     * from the input reader, after having skipped filtered characters or it may
+     * be a characters injected due to translation of template text to ECMA
+     * code.
+     *
+     * @param cbuf The character buffer to fill with (filtered) characters
+     * @param off Offset from where to start in the buffer
+     * @param len The number of characters to fill into the buffer
+     * @return the number of characters filled in the buffer or -1 at the end of
+     *         the input reader.
+     * @throws IOException if the reader is not open
+     * @throws IndexOutOfBoundsException if len is negative, off is negative or
+     *             higher than the buffer length or off+len is negative or
+     *             beyond the buffer size.
+     */
+    public int read(char[] cbuf, int off, int len) throws java.io.IOException {
+        ensureOpen();
+
+        // Check lines (taken from InputStreamReader ;-)
+        if ((off < 0) || (off > cbuf.length) || (len < 0)
+            || ((off + len) > cbuf.length) || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return 0;
+        }
+
+        int i;
+        for (i = 0; i < len; i++, off++) {
+            int c = doRead();
+            if (c < 0) {
+                break;
+            }
+            cbuf[off] = (char) c;
+        }
+
+        // return EOF (-1) if none have been read, else return the number read
+        return (i == 0) ? -1 : i;
+    }
+
+    /**
+     * Skip the number of filtered characters. The skip method is the same as
+     * calling read() repeatedly for the given number of characters and throwing
+     * away the result. If the end of input reader is reached before having
+     * skipped the number of characters, the method returns the number
+     * characters skipped so far.
+     *
+     * @param n the number of (filtered) characters to skip
+     * @return the number of (filtered) characters actually skipped
+     * @throws IllegalArgumentException if n is negative
+     * @throws IOException if the reading the characters throws
+     */
+    public long skip(long n) throws IOException {
+        if (n < 0L) {
+            throw new IllegalArgumentException("skip value is negative");
+        }
+
+        long i = -1;
+        while (++i < n) {
+            if (doRead() < 0) {
+                break;
+            }
+        }
+        return i;
+    }
+
+    /**
+     * Close the EspReader.
+     */
+    public void close() throws java.io.IOException {
+        if (input != null) {
+            input.close();
+            input = null;
+        }
+
+        // I dont' know what happens ??
+        super.close();
+    }
+
+    /**
+     * Mark the present position in the stream. The <code>mark</code> for
+     * class <code>EspReader</code> always throws an throwable.
+     *
+     * @param readAheadLimit The number of characters to read ahead
+     * @exception IOException Always, since mark is not supported
+     */
+    public void mark(int readAheadLimit) throws IOException {
+        throw new IOException("mark() not supported");
+    }
+
+    /**
+     * Tell whether this stream supports the mark() operation, which it does
+     * not.
+     *
+     * @return false Always, since mark is not supported
+     */
+    public boolean markSupported() {
+        return false;
+    }
+
+    /**
+     * Reset the stream. The <code>reset</code> method of
+     * <code>EspReader</code> always throws an throwable.
+     *
+     * @exception IOException Always, since reset is not supported
+     */
+    public void reset() throws IOException {
+        throw new IOException("reset() not supported");
+    }
+
+    /**
+     * Internal routine doing all the footwork of reading one character at a
+     * time from the <code>PushbackReader</code> and acting according to the
+     * current state.
+     * <p>
+     * This filter is implemented using a finite state machine using the states
+     * defined above with the <code>PARSE_STATE</code> constants. Each state
+     * may do a look ahead in certain situations to decide on further steps.
+     * Characters looked ahead may or may not be inserted back into the input
+     * stream depending on the concrete state.
+     *
+     * @return the next character from the input stream according to the current
+     *         state or -1 to indicate end of file.
+     * @throws IOException if the input <code>PushbackReader</code> throws it
+     */
+    private int doRead() throws IOException {
+
+        // we return out of the loop, if we find a character passing the filter
+        for (;;) {
+
+            // Get a character from the input, which may well have been
+            // injected using the unread() method
+            int c = input.read();
+
+            // catch EOF
+            if (c < 0) {
+
+                // if a template text line is still incomplete, inject
+                // proper line ending and continue until this has been returned
+                if (!lineStart && state == PARSE_STATE_ESP) {
+                    doVerbatim("\");"); // line ending injection
+                    lineStart = true; // mark the line having ended
+                    continue; // let's start read the injection
+                }
+
+                return c; // return the marker, we're done
+            }
+
+            // Do the finite state machine
+            switch (state) {
+
+                // NOTE :
+                // - continue means ignore current character, read next
+                // - break means return current character
+
+                // Template text state - text is wrapped in out.write()
+                case PARSE_STATE_ESP:
+
+                    // might start ECMA code/expr, ESP comment or JSP comment
+                    if (c == '<') {
+                        int c2 = input.read();
+                        int c3 = input.read();
+
+                        if (c2 == '%') {
+
+                            // ECMA or JSP comment
+
+                            if (c3 == '=') {
+
+                                // ECMA expression <%= ... %>
+                                pushState(PARSE_STATE_ECMA_EXPR);
+                                startWrite(null);
+                                if (!lineStart) {
+                                    doVerbatim("\");");
+                                }
+                                continue;
+
+                            } else if (c3 == '-') {
+
+                                // (Possible) JSP Comment <%-- ... --%>
+                                int c4 = input.read();
+                                if (c4 == '-') {
+                                    pushState(PARSE_STATE_JSP_COMMENT);
+                                    continue;
+                                }
+                                input.unread(c4);
+
+                            }
+
+                            // We only get here if we are sure about ECMA
+
+                            // ECMA code <% ... %>
+                            input.unread(c3);
+                            pushState(PARSE_STATE_ECMA);
+                            if (!lineStart) {
+                                doVerbatim("\");");
+                            }
+                            continue;
+
+                        }
+
+                        // Nothing special, push back read ahead
+                        input.unread(c3);
+                        input.unread(c2);
+
+                        // End of template text line
+                    } else if (c == '\r' || c == '\n') {
+                        String lineEnd; // will be injected
+
+                        // Check for real CRLF
+                        if (c == '\r') {
+                            int c2 = input.read();
+                            if (c2 != '\n') {
+                                input.unread(c2);
+                                lineEnd = "\\r";
+                            } else {
+                                lineEnd = "\\r\\n";
+                            }
+                        } else {
+                            lineEnd = "\\n";
+                        }
+
+                        // Only write line ending if not empty
+                        if (!lineStart) {
+                            doVerbatim("\");\n");
+                            doVerbatim(lineEnd);
+                            lineStart = true;
+
+                        } else { // if (lineEnd.length() > 1) {
+                            // no matter what line ending we have, make it LF
+                            doVerbatim("\");\n");
+                            doVerbatim(lineEnd);
+                            startWrite("\"");
+                        }
+
+                        continue;
+
+                        // template text is wrapped with double quotes, which
+                        // when occurring in the text must be escaped.
+                        // We also escape the escape character..
+                    } else if (c == '"' || c == '\\') {
+
+                        doVerbatim(String.valueOf((char) c));
+                        c = '\\';
+
+                    }
+
+                    // If in template text at the beginning of a line
+                    if (lineStart) {
+                        lineStart = false;
+                        startWrite("\"" + (char) c);
+                        continue;
+                    }
+
+                    break;
+
+                // Reading ECMA code or and ECMA expression
+                case PARSE_STATE_ECMA_EXPR:
+                case PARSE_STATE_ECMA:
+
+                    if (c == '%') {
+
+                        // might return to PARSE_STATE_ESP
+                        int c2 = input.read();
+                        if (c2 == '>') {
+
+                            // An expression is wrapped in out.write()
+                            if (popState() == PARSE_STATE_ECMA_EXPR) {
+                                doVerbatim(");");
+                            }
+
+                            // next ESP needs out.write(
+                            lineStart = true;
+
+                            continue;
+
+                        }
+
+                        // false alert, push back
+                        input.unread(c2);
+
+                    } else if (c == '/') {
+
+                        // might be ECMA Comment
+                        int c2 = input.read();
+                        if (c2 == '/') {
+                            // single line comment
+                            pushState(PARSE_STATE_ECMA_COMMENTL);
+                        } else if (c2 == '*') {
+                            // multiline comment
+                            pushState(PARSE_STATE_ECMA_COMMENT);
+                        }
+
+                        // false alert, push back
+                        input.unread(c2);
+
+                    } else if (c == '\'' || c == '"') {
+
+                        // an ECMA string
+                        escape = false; // start unescaped
+                        quoteChar = (char) c; // to recognize the end
+                        pushState(PARSE_STATE_QUOTE);
+
+                    }
+                    break;
+
+                // Reading a JSP comment, only returning line endings
+                case PARSE_STATE_JSP_COMMENT:
+
+                    // JSP comments end complexly with --%>
+                    if (c == '-') {
+                        int c2 = input.read();
+                        if (c2 == '-') {
+                            int c3 = input.read();
+                            if (c3 == '%') {
+                                int c4 = input.read();
+                                if (c4 == '>') {
+
+                                    // we really reached the end ...
+                                    popState();
+                                    continue;
+
+                                }
+                                input.unread(c4);
+                            }
+                            input.unread(c3);
+                        }
+                        input.unread(c2);
+
+                        // well, not definitely correct but reasonably accurate
+                        // ;-)
+                    } else if (c == '\r' || c == '\n') {
+
+                        // terminate an open template line
+                        if (!lineStart) {
+                            input.unread(c); // push back the character
+                            doVerbatim("\");"); // insert ");
+                            lineStart = true; // mark the line start
+                            continue; // Force read of the "
+                        }
+
+                        break;
+                    }
+
+                    // continue reading another character in the comment
+                    continue;
+
+                    // Read an ECMA string upto the ending quote character
+                case PARSE_STATE_QUOTE:
+
+                    // if unescaped quote character
+                    if (c == quoteChar && !escape) {
+                        popState();
+                    } else {
+                        // mark escape - only if not already escaped (bug 7079)
+                        escape = c == '\\' && !escape;
+                    }
+
+                    break;
+
+                // Return characters unfiltered
+                case PARSE_STATE_VERBATIM:
+
+                    // Go back to previous state if all characters read
+                    if (--verbatimChars < 0) {
+                        popState();
+                    }
+
+                    break;
+
+                // Return an ECMA multiline comment, ending with */
+                case PARSE_STATE_ECMA_COMMENT:
+
+                    // Might be the end of the comment
+                    if (c == '*') {
+                        int c2 = input.read();
+                        if (c2 == '/') {
+                            popState(); // back to previous
+                            doVerbatim("/"); // return slash verbatim
+                        } else {
+                            input.unread(c2);
+                        }
+                    }
+
+                    break;
+
+                // Return an ECMA single line comment, ending with end of line
+                case PARSE_STATE_ECMA_COMMENTL:
+
+                    // CRLF recognition
+                    if (c == '\r') {
+                        int c2 = input.read();
+                        if (c2 == '\n') {
+                            popState();
+                        }
+                        input.unread(c2);
+
+                        // LF only line end
+                    } else if (c == '\n') {
+                        popState();
+                    }
+
+                    break;
+
+                // What ???!!!
+                default:
+
+                    // we warn and go back to default state
+                    log.warn("doRead(): unknown state " + state);
+                    state = PARSE_STATE_ESP;
+
+                    break;
+
+            } // switch
+
+            // Exiting the switch normally we return the current character
+            return c;
+
+        } // for(;;)
+
+    }
+
+    /**
+     * Throw an IOException if the reader is not open
+     *
+     * @throws IOException if the reader is (already) closed
+     */
+    private void ensureOpen() throws IOException {
+        if (input == null) {
+            throw new IOException("Reader is closed");
+        }
+    }
+
+    /**
+     * Injects the call to write template text and checks whether the global
+     * <em>out</em> variable has also to be defined such that the writer is
+     * acquired on demand.
+     *
+     * @param startString Additional data to be injected as initial argument
+     *      to the <em>out.write</em> call written. If <code>null</code> just
+     *      the method call is injected.
+     *
+     * @throws IOException if the 'unreading' throws
+     */
+    private void startWrite(String startString) throws IOException {
+
+        // inject the out.write( part and the initial string
+        if (startString != null && startString.length() > 0) {
+            doVerbatim(startString);
+        }
+        doVerbatim("out.write(");
+
+        // if out is not set yet, we also acquire it now setting it
+        // globally
+        if (outUndefined) {
+            doVerbatim("out=response.writer;");
+            outUndefined = false;
+        }
+    }
+
+    /**
+     * Injects a string into the input stream, sets the number of characters to
+     * return verbatim and change state. The state change only happens if we are
+     * not in verbatim state already. Else the current string is simply
+     * prepended to the previous inhjection. This is simply a convenience method
+     * ;-)
+     *
+     * @param verbatimString The string to inject into the input stream
+     * @throws IOException if the 'unreading' throws
+     */
+    private void doVerbatim(String verbatimString) throws IOException {
+
+        // Push 'back' into PushbackReader
+        input.unread(verbatimString.toCharArray());
+
+        // Set the number of characters to return verbatim
+        verbatimChars += verbatimString.length();
+
+        // Change state if not already in verbatim state
+        if (state != PARSE_STATE_VERBATIM) {
+            pushState(PARSE_STATE_VERBATIM);
+        }
+    }
+
+    /**
+     * Push the current state on stack and set to <code>newState</code>. This
+     * new state is also returned.
+     *
+     * @param newState the new state to set
+     * @return the new state set according to <code>newState</code>
+     */
+    private byte pushState(byte newState) {
+        stateStack.push(state);
+        return state = newState;
+    }
+
+    /**
+     * Sets the current state to the state stored at the top of the stack. If
+     * the stack is empty prior to this call, the default template text state is
+     * set. The method returns the state prior to setting to the new state.
+     *
+     * @return the state prior to calling this method
+     */
+    private byte popState() {
+        byte oldState = state;
+        state = stateStack.isEmpty() ? PARSE_STATE_ESP : stateStack.pop();
+        return oldState;
+    }
+
+}
\ No newline at end of file

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/FileNodeRequestAttribute.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/FileNodeRequestAttribute.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/FileNodeRequestAttribute.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/FileNodeRequestAttribute.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.microsling.scripting.helpers;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.microsling.helpers.exceptions.MissingRequestAttributeException;
+
+/** Stores an nt:file Node in a request attribute,
+ *  and allows the file's inputStream or reader to
+ *  be accessed easily.
+ *  Used by scripting SlingServlets to store resolved script nodes
+ *  in between canProcess and doX method calls.
+ */ 
+public class FileNodeRequestAttribute {
+    
+    private final Node node;
+    public static final String REQ_ATTR_NAME = FileNodeRequestAttribute.class.getName();
+    
+    /** Store this as an attribute of req */
+    public FileNodeRequestAttribute(Node n,HttpServletRequest req) {
+        node = n;
+        req.setAttribute(REQ_ATTR_NAME,this);
+    }
+    
+    /** Retrieve a FileNodeRequestAttribute from given request */
+    public static FileNodeRequestAttribute getFromRequest(HttpServletRequest req) throws MissingRequestAttributeException {
+        final FileNodeRequestAttribute result = 
+            (FileNodeRequestAttribute)req.getAttribute(REQ_ATTR_NAME);
+        if(result==null) {
+            throw new MissingRequestAttributeException(REQ_ATTR_NAME);
+        }
+        return result;
+    }
+    
+    /** Return our nt:file node */
+    public Node getNode() {
+        return node;
+    }
+
+    /** Return an InputStream that provides our node's file content */
+    public InputStream getInputStream() throws RepositoryException {
+        // TODO need more robust checks
+        return node.getNode("jcr:content").getProperty("jcr:data").getStream();
+    }
+    
+    /** Return a Reader that provides our node's file content */
+    public Reader getReader() throws RepositoryException {
+        return new InputStreamReader(getInputStream());
+    }
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptFilenameBuilder.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptFilenameBuilder.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptFilenameBuilder.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptFilenameBuilder.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.microsling.scripting.helpers;
+
+import org.apache.sling.microsling.helpers.constants.HttpConstants;
+
+/** Builds the names of script files based on the current input:
+ *  <ul>
+ *    <li>HTTP request method name</li>
+ *    <li>Request selectors, if any</li>
+ *    <li>Desired response Content-Type</li>
+ *    <li>Desired script extension</li>
+ *  </ul>
+ *  
+ *  See ScriptFilenameBuilderTest for examples.
+ *  
+ *  Note that names can include partial paths, for example we return
+ *  "print/a4/html.js" for a GET request for an html document with
+ *  selectors "print.a4".
+ */
+public class ScriptFilenameBuilder {
+
+    /** @return a name like "html.js" or "print/a4/html.vlt" or "POST.js" */ 
+    public String buildScriptFilename(String methodName,String selectors,String contentType,String scriptExtension) {
+        final StringBuffer sb = new StringBuffer();
+        
+        // path before filename:
+        // add selectors in front of the filename if any, replacing dots in them by slashes
+        // so that print.a4 becomes print/a4/
+        if(selectors != null && selectors.length() > 0) {
+            sb.append(selectors.toLowerCase().replace('.','/'));
+            sb.append('/');
+        }
+        
+        // filename:
+        if(methodName==null || methodName.length() == 0) {
+            sb.append("NO_METHOD");
+            
+        } else if(HttpConstants.METHOD_GET.equalsIgnoreCase(methodName)) {
+            // for the GET method, use the simplified content-type, lowercased,
+            // as the filename.
+            // TODO: how to handle HEAD?
+            if(contentType == null || contentType.length() == 0) {
+                sb.append("NO_CONTENT_TYPE");
+            } else {
+                // keep only what follows slash in the content-type
+                final String [] splitContentType = contentType.split("/");
+                sb.append(splitContentType[splitContentType.length - 1].toLowerCase());
+            }
+            
+        } else {
+            // for other methods use the method name
+            sb.append(methodName.toUpperCase());
+        }
+        
+        // extension: use desired script extension 
+        sb.append(".");
+        if(scriptExtension == null || scriptExtension.length()==0) {
+            sb.append("NO_EXT");
+        } else {
+            sb.append(scriptExtension.toLowerCase());
+        }
+        return sb.toString();
+    }
+    
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptHelper.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptHelper.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptHelper.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/scripting/helpers/ScriptHelper.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,159 @@
+/*
+ * $Url: $
+ * $Id: $
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.sling.microsling.scripting.helpers;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/** Simple script helper providing access to the (wrapped) response, the
+ * on-demand writer and a simple API for request inclusion. Instances of this
+ * class are made available to the scripts as the global <code>sling</code>
+ * variable.
+ */
+public class ScriptHelper {
+
+    private final HttpServletRequest request;
+
+    private final HttpServletResponse response;
+
+    public ScriptHelper(HttpServletRequest request, HttpServletResponse response) {
+        this.request = request;
+        this.response = new OnDemandWriterResponse(response);
+    }
+
+    public HttpServletRequest getRequest() {
+        return request;
+    }
+
+    public HttpServletResponse getResponse() {
+        return response;
+    }
+
+    public void include(String path) throws ServletException, IOException {
+        RequestDispatcher dispatcher = getRequest().getRequestDispatcher(path);
+        if (dispatcher != null) {
+            dispatcher.include(getRequest(), getResponse());
+        }
+    }
+
+    /** Simple Response wrapper returning an on-demand writer when asked for
+     * a writer.
+     */
+    private static class OnDemandWriterResponse extends HttpServletResponseWrapper {
+
+        private PrintWriter writer;
+
+        OnDemandWriterResponse(HttpServletResponse delegatee) {
+            super(delegatee);
+        }
+
+        @Override
+        public PrintWriter getWriter() {
+            if (writer == null) {
+                writer = new PrintWriter(new OnDemandWriter(getResponse()));
+            }
+
+            return writer;
+        }
+    }
+
+    /** A writer acquiring the actual writer to delegate to on demand when the
+     * first data is to be written. */
+    private static class OnDemandWriter extends Writer {
+
+        private final ServletResponse response;
+        private Writer delegatee;
+
+        OnDemandWriter(ServletResponse response) {
+            this.response = response;
+        }
+
+        private Writer getWriter() throws IOException {
+            if (delegatee == null) {
+                delegatee = response.getWriter();
+            }
+
+            return delegatee;
+        }
+
+        @Override
+        public void write(int c) throws IOException {
+            synchronized (lock) {
+                getWriter().write(c);
+            }
+        }
+
+        @Override
+        public void write(char[] cbuf) throws IOException {
+            synchronized (lock) {
+                getWriter().write(cbuf);
+            }
+        }
+
+        @Override
+        public void write(char[] cbuf, int off, int len) throws IOException {
+            synchronized (lock) {
+                getWriter().write(cbuf, off, len);
+            }
+        }
+
+        @Override
+        public void write(String str) throws IOException {
+            synchronized (lock) {
+                getWriter().write(str);
+            }
+        }
+
+        @Override
+        public void write(String str, int off, int len) throws IOException {
+            synchronized (lock) {
+                getWriter().write(str, off, len);
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            synchronized (lock) {
+                Writer writer = delegatee;
+                if (writer != null) {
+                    writer.flush();
+                }
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            synchronized (lock) {
+                // flush and close the delegatee if existing, otherwise ignore
+                Writer writer = delegatee;
+                if (writer != null) {
+                    writer.flush();
+                    writer.close();
+
+                    // drop the delegatee now
+                    delegatee = null;
+                }
+            }
+        }
+    }
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/services/MicroslingServiceLocator.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/services/MicroslingServiceLocator.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/services/MicroslingServiceLocator.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/services/MicroslingServiceLocator.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.microsling.services;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.microsling.api.ResourceResolver;
+import org.apache.sling.microsling.api.ServiceLocator;
+import org.apache.sling.microsling.resource.MicroslingResourceResolver;
+
+/** Poor man's ServiceLocator (no, poorer than that) which uses a 
+ *  static list of services. This is mostly meant to introduce
+ *  the ServiceLocator interface in microsling.
+ *  See Sling OSGi for the real McCoy.
+ */
+
+public class MicroslingServiceLocator implements ServiceLocator {
+
+    protected final Map <String,Object> services = new HashMap <String, Object> ();
+    
+    public MicroslingServiceLocator() {
+        // initialize our services (pure rocket science, isn't it? ;-)
+        services.put(ResourceResolver.class.getName(),new MicroslingResourceResolver());
+    }
+    
+    public Object getService(String serviceName) {
+        return services.get(serviceName);
+    }
+    
+    
+    public Object getRequiredService(String serviceName) throws ServiceNotAvailableException {
+        final Object result = services.get(serviceName);
+        if(result== null) {
+            throw new ServiceNotAvailableException("Service '" + serviceName + "' is not available");
+        }
+        return result;
+    }
+
+    public Object[] getServices(String serviceName, String filter) {
+        // we don't have this feature
+        return null;
+    }
+
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroSlingFilterHelper.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroSlingFilterHelper.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroSlingFilterHelper.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroSlingFilterHelper.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.microsling.servlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.microsling.servlet.MicroslingMainServlet;
+
+/** This is helper code, not very interesting to study (but it's
+ *  not in an "helpers" package as that would require too much public
+ *  stuff).
+ * 
+ *  Manages the microsling chain of servlet Filters:
+ *  stores the list, calls them when processing a request
+ *  and calls microSlingServlet.doService after that.
+ */
+class MicroSlingFilterHelper {
+
+    private MicroslingMainServlet microSling;
+
+    private List<Filter> requestFilterList = new LinkedList<Filter>();
+
+    private Filter[] requestFilters;
+
+    MicroSlingFilterHelper(MicroslingMainServlet microSling) {
+        this.microSling = microSling;
+    }
+
+    void destroy() {
+        Filter[] filters = getFilters();
+
+        // clean up
+        requestFilterList.clear();
+        requestFilters = null;
+
+        // destroy the filters
+        for (int i=0; i < filters.length; i++) {
+            try {
+                filters[i].destroy();
+            } catch (Throwable t) {
+                // TODO: some logging would probably be usefull
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest,
+     *      javax.servlet.ServletResponse)
+     */
+    void service(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException {
+
+        MicroSlingFilterChain filterChain = new MicroSlingFilterChain(
+            microSling, getFilters());
+        filterChain.doFilter(request, response);
+
+    }
+
+    /** return our Filters as a (lazily created) array */
+    private Filter[] getFilters() {
+        if (requestFilters == null) {
+            requestFilters = requestFilterList.toArray(new Filter[requestFilterList.size()]);
+        }
+        return requestFilters;
+    }
+
+    /** Add a Filter at the end of our current chain */
+    void addFilter(final Filter filter) throws ServletException {
+        FilterConfig config = new FilterConfig() {
+            public String getFilterName() {
+                return filter.getClass().getName();
+            }
+
+            public String getInitParameter(String arg0) {
+                // no parameters for now
+                return null;
+            }
+
+            public Enumeration<?> getInitParameterNames() {
+                // no parameters for now
+                return Collections.enumeration(Collections.emptyList());
+            }
+
+            public ServletContext getServletContext() {
+                return microSling.getServletContext();
+            }
+        };
+
+        // initialize the filter and add it to the list
+        filter.init(config);
+        requestFilterList.add(filter);
+
+        // force recreation of filter list
+        requestFilters = null;
+    }
+
+    /** A FilterChain that applies all Filters in an array and calls
+     *  MicroSlingServlet.doFilter when done
+     */
+    private static class MicroSlingFilterChain implements FilterChain {
+
+        private final MicroslingMainServlet microSlingServlet;
+
+        private final Filter[] requestFilters;
+
+        private int currentFilter;
+
+        private MicroSlingFilterChain(MicroslingMainServlet microSlingServlet,
+                Filter[] requestFilters) {
+            this.microSlingServlet = microSlingServlet;
+            this.requestFilters = requestFilters;
+            this.currentFilter = -1;
+        }
+
+        public void doFilter(ServletRequest request, ServletResponse response)
+                throws IOException, ServletException {
+
+            currentFilter++;
+
+            if (currentFilter < requestFilters.length) {
+                // call the next filter
+                requestFilters[currentFilter].doFilter(request, response, this);
+
+            } else {
+                // done with filters, call microsling servlet
+                HttpServletRequest hReq = null;
+                HttpServletResponse hRep = null;
+                try {
+                    hReq = (HttpServletRequest)request;
+                    hRep = (HttpServletResponse)response;
+                } catch(ClassCastException cce) {
+                    throw new ServletException("Expected an HttpServletRequest and HttpServletResponse",cce);
+                }
+                microSlingServlet.doService(hReq,hRep);
+            }
+
+        }
+    }
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroslingMainServlet.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroslingMainServlet.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroslingMainServlet.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/servlet/MicroslingMainServlet.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.microsling.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.GenericServlet;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.microsling.api.Resource;
+import org.apache.sling.microsling.api.ServiceLocator;
+import org.apache.sling.microsling.api.SlingRequestContext;
+import org.apache.sling.microsling.api.exceptions.HttpStatusCodeException;
+import org.apache.sling.microsling.api.exceptions.SlingException;
+import org.apache.sling.microsling.contenttype.ResponseContentTypeResolverFilter;
+import org.apache.sling.microsling.request.MicroslingRequestContext;
+import org.apache.sling.microsling.scripting.SlingScriptResolver;
+import org.apache.sling.microsling.services.MicroslingServiceLocator;
+import org.apache.sling.microsling.slingservlets.DefaultSlingServlet;
+import org.apache.sling.microsling.slingservlets.StreamServlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The main microsling servlet: apply Filters to the request using our
+ * MicroSlingFilterHelper, select and delegate to a SlingServlet to process the
+ * request.
+ */
+public class MicroslingMainServlet extends GenericServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    private MicroSlingFilterHelper filterChain;
+
+    private ServiceLocator serviceLocator;
+
+    private Map<String, Servlet> servlets;
+
+    private SlingScriptResolver scriptResolver;
+
+    private DefaultSlingServlet defaultSlingServlet;
+    
+    private static final Logger log = LoggerFactory.getLogger(MicroslingMainServlet.class);
+
+    @Override
+    public void init() throws ServletException {
+        super.init();
+        servlets = new HashMap<String, Servlet>();
+        initFilterChain();
+        initServlets();
+        initServiceLocator();
+        initScriptResolver();
+    }
+    
+    /** init our filter chain */
+    protected void initFilterChain() throws ServletException {
+        filterChain = new MicroSlingFilterHelper(this);
+        addFilter(new ResponseContentTypeResolverFilter());
+    }
+    
+    /** init our servlets */
+    protected void initServlets() throws ServletException {
+        // TODO use a utility class to map nt:file to the magic NODETYPES path 
+        addServlet("NODETYPES/nt/file", new StreamServlet());
+        defaultSlingServlet = new DefaultSlingServlet();
+    }
+
+    /** init our serviceLocator */
+    protected void initServiceLocator() throws ServletException {
+        serviceLocator = new MicroslingServiceLocator();
+    }
+    
+    /** init our scriptResolver */
+    protected void initScriptResolver() throws ServletException {
+        scriptResolver = new SlingScriptResolver();
+    }
+    
+    /**
+     * Execute our Filters via MicroSlingFilterHelper, which calls our doService
+     * method after executing the filters
+     */
+    public void service(ServletRequest req, ServletResponse resp)
+            throws ServletException, IOException {
+
+        // our filters might need the SlingRequestContext to store info in it
+        new MicroslingRequestContext(getServletContext(), req, resp, serviceLocator);
+        filterChain.service(req, resp);
+    }
+
+    @Override
+    public void destroy() {
+        // just for completeness, we have to take down our filters
+        if (filterChain != null) {
+            filterChain.destroy();
+            filterChain = null;
+        }
+
+        // destroy registered servlets
+        Servlet[] servletList = servlets.values().toArray(
+            new Servlet[servlets.size()]);
+        for (Servlet servlet : servletList) {
+            try {
+                servlet.destroy();
+            } catch (Throwable t) {
+                getServletContext().log(
+                    "Unexpected problem destroying servlet " + servlet, t);
+            }
+        }
+        servlets.clear();
+
+        // destroy base class at the end
+        super.destroy();
+    }
+
+    /**
+     * Called by
+     * {@link MicroSlingFilterHelper#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+     * after all filters have been processed.
+     */
+    void doService(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+
+        try {
+            // Select a SlingServlet and delegate the actual request processing
+            // to it
+            final Servlet selectedServlet = selectSlingServlet(req);
+            if (selectedServlet != null) {
+                delegateToSlingServlet(selectedServlet, req, resp);
+            } else {
+                // no typed servlet, so lets try scripting
+                boolean scriptExists = scriptResolver.evaluateScript(req, resp);
+                if (!scriptExists) {
+                    if(log.isDebugEnabled()) {
+                        final SlingRequestContext ctx = SlingRequestContext.getFromRequest(req);
+                        final Resource r = ctx.getResource();
+                        log.debug("No specific Servlet or script found for Resource " + r + ", using default Servlet");
+                    }
+                    delegateToSlingServlet(defaultSlingServlet, req, resp);
+                }
+            }
+
+        } catch (HttpStatusCodeException hts) {
+            resp.sendError(hts.getStatusCode(), hts.getMessage());
+
+        } catch (Exception e) {
+            final StringWriter sw = new StringWriter();
+            e.printStackTrace(new PrintWriter(sw, true));
+            resp.sendError(500, e.getMessage() + "\n" + sw.toString());
+        }
+    }
+
+    /** Select a SlingServlet to process the given request */
+    protected Servlet selectSlingServlet(HttpServletRequest req)
+            throws SlingException {
+
+        // use the resource type to select a servlet
+        final SlingRequestContext ctx = SlingRequestContext.getFromRequest(req);
+        final Resource r = ctx.getResource(); 
+        String type = (r == null ? null : r.getResourceType());
+        final Servlet result = (type != null) ? servlets.get(type) : null;
+        
+        if(log.isDebugEnabled()) {
+            if(result==null) {
+                log.debug("No Servlet found for resource type " + type);
+            } else {
+                log.debug("Using Servlet class " + result.getClass().getSimpleName() + " for resource type " + type);
+             }
+        }
+        
+        return result;
+    }
+
+    /** Delegate to the given SlingServlet, based on the request HTTP method */
+    protected void delegateToSlingServlet(Servlet s, HttpServletRequest req,
+            HttpServletResponse resp) throws ServletException, IOException {
+        s.service(req, resp);
+    }
+
+    /** Add a filter to our MicroSlingFilterHelper */
+    protected void addFilter(Filter filter) throws ServletException {
+        filterChain.addFilter(filter);
+    }
+
+    /** Add servlets by resource type */
+    protected void addServlet(final String resourceType, Servlet servlet) {
+
+        try {
+            ServletConfig config = new ServletConfig() {
+                public String getInitParameter(String name) {
+                    return MicroslingMainServlet.this.getInitParameter(name);
+                }
+                public Enumeration<?> getInitParameterNames() {
+                    return MicroslingMainServlet.this.getInitParameterNames();
+                }
+                public ServletContext getServletContext() {
+                    return MicroslingMainServlet.this.getServletContext();
+                }
+                public String getServletName() {
+                    return resourceType;
+                }
+            };
+            servlet.init(config);
+
+            // only register if initialization succeeds
+            servlets.put(resourceType, servlet);
+        } catch (Throwable t) {
+            getServletContext().log("Failed initializing servlet " + servlet + " for type " + resourceType, t);
+        }
+
+    }
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/DefaultSlingServlet.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/DefaultSlingServlet.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/DefaultSlingServlet.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/DefaultSlingServlet.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.microsling.slingservlets;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.microsling.api.Resource;
+import org.apache.sling.microsling.api.SlingRequestContext;
+import org.apache.sling.microsling.api.exceptions.HttpStatusCodeException;
+import org.apache.sling.microsling.helpers.servlets.SlingAllMethodsServlet;
+
+/**
+ * The default SlingServlet, used if no other SlingServlet wants to process the
+ * current request.
+ */
+public class DefaultSlingServlet extends SlingAllMethodsServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        resp.setContentType("text/plain");
+
+        final SlingRequestContext ctx = SlingRequestContext.getFromRequest(req);
+        final Resource  r = ctx.getResource();
+        if (r == null) {
+            throw new HttpStatusCodeException(404, "Resource not found: "
+                + req.getPathInfo());
+        }
+
+        final Item data = r.getItem();
+        if (data != null) {
+            final PrintWriter pw = resp.getWriter();
+            try {
+                if (data.isNode()) {
+                    dump(pw, r, (Node) data);
+                } else {
+                    dump(pw, r, (Property) data);
+                }
+            } catch (RepositoryException re) {
+                throw new ServletException("Cannot dump contents of "
+                    + ctx.getResource().getURI(), re);
+            }
+        } else {
+            throw new HttpStatusCodeException(501,
+                "Not implemented: resource " + ctx.getResource().getURI()
+                    + " cannot be dumped by " + getClass().getSimpleName());
+        }
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+            throws ServletException, IOException {
+        final SlingRequestContext ctx = SlingRequestContext.getFromRequest(req);
+        String redirectPath = req.getPathInfo();
+
+        Session s = null;
+        try {
+            s = ctx.getSession();
+            Node current = (ctx.getResource() == null ? null : (Node)ctx.getResource().getItem());
+
+            // Decide whether to create or update a node
+            // TODO: this is a simplistic way of deciding, for now: if we have
+            // no Resource or if the Node that it points to already has child nodes,
+            // we create a new node. Else we update the current node.
+            if(current == null || current.hasNodes()) {
+                final String parentPath = (current == null ? req.getPathInfo() : current.getPath());
+                final String newNodePath = parentPath + "/" + System.currentTimeMillis();
+                current = deepCreateNode(s, newNodePath);
+            }
+
+            // Copy request parameters to node properties and save
+            setPropertiesFromRequest(current, req);
+            s.save();
+            redirectPath = current.getPath();
+
+        } catch (RepositoryException re) {
+            throw new ServletException("Failed to modify content: "
+                + re.getMessage(), re);
+
+        } finally {
+            try {
+                if (s != null && s.hasPendingChanges()) {
+                    s.refresh(false);
+                }
+            } catch (RepositoryException re) {
+                // TODO: might want to log, but don't further care
+            }
+        }
+
+        // redirect to the created node, so that it is displayed using a user-supplied extension
+        String redirectExtension = req.getParameter("slingDisplayExtension");
+        final String redirectUrl =
+            req.getContextPath() + req.getServletPath() + redirectPath
+            + (redirectExtension == null ? "" : "." + redirectExtension)
+        ;
+        resp.sendRedirect(redirectUrl);
+    }
+
+    /** Set node properties from current request (only handles Strings for now) */
+    protected void setPropertiesFromRequest(Node n, HttpServletRequest req)
+            throws RepositoryException {
+        // TODO ignore sling-specific properties like slingDisplayExtension
+        for (Enumeration e = req.getParameterNames(); e.hasMoreElements();) {
+            final String name = (String) e.nextElement();
+            final String[] values = req.getParameterValues(name);
+            if (values.length==1) {
+            	n.setProperty(name, values[0]);
+            } else {
+            	n.setProperty(name, values);
+            }
+        }
+    }
+
+    /**
+     * Deep creates a node, parent-padding with nt:unstructured nodes
+     *
+     * @param path absolute path to node that needs to be deep-created
+     */
+    protected Node deepCreateNode(Session s, String path)
+            throws RepositoryException {
+        String[] pathelems = path.substring(1).split("/");
+        int i = 0;
+        String mypath = "";
+        Node parent = s.getRootNode();
+        while (i < pathelems.length) {
+            String name = pathelems[i];
+            mypath += "/" + name;
+            if (!s.itemExists(mypath)) {
+                parent.addNode(name);
+            }
+            parent = (Node) s.getItem(mypath);
+            i++;
+        }
+        return (parent);
+    }
+
+    protected void dump(PrintWriter pw, Resource r, Node n) throws RepositoryException {
+        pw.println("** Node dumped by " + getClass().getSimpleName() + "**");
+        pw.println("Node path:" + n.getPath());
+        pw.println("Resource metadata: " + r.getMetadata());
+
+        pw.println("\n** Node properties **");
+        for (PropertyIterator pi = n.getProperties(); pi.hasNext();) {
+            final Property p = pi.nextProperty();
+            printPropertyValue(pw, p);
+        }
+    }
+
+    protected void dump(PrintWriter pw, Resource r, Property p) throws RepositoryException {
+        pw.println("** Property dumped by " + getClass().getSimpleName() + "**");
+        pw.println("Property path:" + p.getPath());
+        pw.println("Resource metadata: " + r.getMetadata());
+
+        printPropertyValue(pw, p);
+    }
+
+    protected void printPropertyValue(PrintWriter pw, Property p)
+            throws RepositoryException {
+
+        pw.print(p.getName() + ": ");
+
+        if (p.getDefinition().isMultiple()) {
+            Value[] values = p.getValues();
+            pw.print('[');
+            for (int i = 0; i < values.length; i++) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                pw.print(values[i].getString());
+            }
+            pw.println(']');
+        } else {
+            pw.println(p.getValue().getString());
+        }
+    }
+
+}

Added: sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/StreamServlet.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/StreamServlet.java?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/StreamServlet.java (added)
+++ sling/whiteboard/microsling/src/main/java/org/apache/sling/microsling/slingservlets/StreamServlet.java Wed Sep  6 13:24:23 2017
@@ -0,0 +1,225 @@
+/*
+ * $Url: $
+ * $Id: $
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.sling.microsling.slingservlets;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.microsling.api.SlingRequestContext;
+import org.apache.sling.microsling.api.exceptions.HttpStatusCodeException;
+import org.apache.sling.microsling.helpers.constants.HttpConstants;
+import org.apache.sling.microsling.helpers.servlets.SlingSafeMethodsServlet;
+
+/**
+ * The <code>StreamServlet</code> handles requests for nodes which may just be
+ * streamed out to the response. If the requested JCR Item is an
+ * <em>nt:file</em> whose <em>jcr:content</em> child node is of type
+ * <em>nt:resource</em>, the response content type, last modification time and
+ * charcter encoding are set according to the resource node. In addition if
+ * the <em>If-Modified-Since</em> header is set, the resource will only be
+ * spooled if the last modification time is later than the header. Otherwise
+ * a 304 (Not Modified) status code is sent.
+ * <p>
+ * If the requested item is not an <em>nt:file</em>/<em>nt:resource</em> tuple,
+ * the item is just resolved by following the primary item trail according to
+ * the algorithm
+ * <pre>
+ *     while (item.isNode) {
+ *         item = ((Node) item).getPrimaryItem();
+ *     }
+ * </pre>
+ * Until a property is found or the primary item is either not defined or not
+ * existing in which case an exception is thrown and the request fails with
+ * a 404 (Not Found) status.
+ */
+public class StreamServlet extends SlingSafeMethodsServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+
+        final SlingRequestContext ctx = SlingRequestContext.getFromRequest(request);
+
+
+        try {
+            Item item = ctx.getResource().getItem();
+
+            // otherwise handle nt:file/nt:resource specially
+            Node node = (Node) item;
+            if (node.isNodeType("nt:file")) {
+                Node content = node.getNode("jcr:content");
+                if (content.isNodeType("nt:resource")) {
+
+                    // check for if last modified
+                    long ifModified = request.getDateHeader(HttpConstants.HEADER_IF_MODIFIED_SINCE);
+                    long lastModified = getLastModified(content);
+                    if (ifModified < 0 || lastModified > ifModified) {
+
+                        String contentType = getMimeType(content);
+                        if (contentType == null) {
+                            contentType = ctx.getPreferredResponseContentType();
+                        }
+
+                        spool(response,
+                            content.getProperty(JcrConstants.JCR_DATA),
+                            contentType, getEncoding(content),
+                            getLastModified(content));
+                    } else {
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    }
+
+                    return;
+                }
+            }
+
+            // just spool, the property to which the item resolves through
+            // the primary item trail
+            // the item is a property, spool and forget
+            spool(response, findDataProperty(item), null, null, -1);
+
+        } catch (RepositoryException re) {
+            throw new HttpStatusCodeException(500, "RepositoryException in StreamServlet.doGet(): " + re.getMessage());
+        }
+    }
+
+    /**
+     * Spool the property value to the response setting the content type,
+     * character set, last modification data and content length header
+     */
+    private void spool(HttpServletResponse response, Property prop,
+            String mimeType, String encoding, long lastModified)
+            throws RepositoryException, IOException {
+
+        if (mimeType != null) {
+            response.setContentType(mimeType);
+        }
+
+        if (encoding != null) {
+            response.setCharacterEncoding(encoding);
+        }
+
+        if (lastModified > 0) {
+            response.setDateHeader(HttpConstants.HEADER_LAST_MODIFIED, lastModified);
+        }
+
+        // only set the content length if the property is a binary
+        if (prop.getType() == PropertyType.BINARY) {
+            response.setContentLength((int) prop.getLength());
+        }
+
+        InputStream ins = prop.getStream();
+        OutputStream out = null;
+        try {
+            ins = prop.getStream();
+            out = response.getOutputStream();
+
+            byte[] buf = new byte[2048];
+            int num;
+            while ((num = ins.read(buf)) >= 0) {
+                out.write(buf, 0, num);
+            }
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    }
+
+    /** Find the Property that contains the data to spool, under parent */ 
+    private Property findDataProperty(final Item parent) throws RepositoryException, HttpStatusCodeException {
+        Property result = null;
+        
+        // Following the path of primary items until we find a property
+        // should provide us with the file data of the parent
+        try {
+            Item item = parent;
+            while(item!=null && item.isNode()) {
+                item = ((Node) item).getPrimaryItem();
+            }
+            result = (Property)item;
+        } catch(ItemNotFoundException ignored) {
+            // TODO: for now we use an alternate method if this fails,
+            // there might be a better way (see jackrabbit WebDAV server code?)
+        }
+        
+        if(result==null && parent.isNode()) {
+            // primary path didn't work, try the "usual" path to the data Property
+            try {
+                final Node parentNode = (Node)parent;
+                result = parentNode.getNode("jcr:content").getProperty("jcr:data");
+            } catch(ItemNotFoundException e) {
+                throw new HttpStatusCodeException(404,parent.getPath() + "/jcr:content" + "/jcr:data");
+            }
+        }
+        
+        if(result==null) {
+            throw new HttpStatusCodeException(500, "Unable to find data property for parent item " + parent.getPath());
+        }
+        
+        return result;
+    }
+
+    /** return the jcr:lastModified property value or null if property is missing */
+    private long getLastModified(Node resourceNode) throws RepositoryException {
+        Property lastModifiedProp = getProperty(resourceNode,
+            JcrConstants.JCR_LASTMODIFIED);
+        return (lastModifiedProp != null) ? lastModifiedProp.getLong() : -1;
+    }
+
+    /** return the jcr:mimeType property value or null if property is missing */
+    private String getMimeType(Node resourceNode) throws RepositoryException {
+        Property mimeTypeProp = getProperty(resourceNode,
+            JcrConstants.JCR_MIMETYPE);
+        return (mimeTypeProp != null) ? mimeTypeProp.getString() : null;
+    }
+
+    /** return the jcr:encoding property value or null if property is missing */
+    private String getEncoding(Node resourceNode) throws RepositoryException {
+        Property encodingProp = getProperty(resourceNode,
+            JcrConstants.JCR_ENCODING);
+        return (encodingProp != null) ? encodingProp.getString() : null;
+    }
+
+    /** Return the named property or null if not existing or node is null */
+    private Property getProperty(Node node, String relPath)
+            throws RepositoryException {
+        if (node != null && node.hasProperty(relPath)) {
+            return node.getProperty(relPath);
+        }
+
+        return null;
+    }
+}

Added: sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.dtd
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.dtd?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.dtd (added)
+++ sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.dtd Wed Sep  6 13:24:23 2017
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!-- Authors: Chris Taylor, Ceki Gulcu. -->
+
+<!-- Version: 1.2 -->
+
+<!-- A configuration element consists of optional renderer
+elements,appender elements, categories and an optional root
+element. -->
+
+<!ELEMENT log4j:configuration (renderer*, appender*, logger*, root?)>
+
+<!-- The "threshold" attribute takes a level value such that all -->
+<!-- logging statements with a level equal or below this value are -->
+<!-- disabled. -->
+
+<!-- Setting the "debug" enable the printing of internal log4j logging   -->
+<!-- statements.                                                         -->
+
+<!-- By default, debug attribute is "null", meaning that we not do touch -->
+<!-- internal log4j logging settings. The "null" value for the threshold -->
+<!-- attribute can be misleading. The threshold field of a repository	 -->
+<!-- cannot be set to null. The "null" value for the threshold attribute -->
+<!-- simply means don't touch the threshold field, the threshold field   --> 
+<!-- keeps its old value.                                                -->
+     
+<!ATTLIST log4j:configuration
+  xmlns:log4j              CDATA #FIXED "http://jakarta.apache.org/log4j/" 
+  threshold                (all|debug|info|warn|error|fatal|off|null) "null"
+  debug                    (true|false|null)  "null"
+>
+
+<!-- renderer elements allow the user to customize the conversion of  -->
+<!-- message objects to String.                                       -->
+
+<!ELEMENT renderer EMPTY>
+<!ATTLIST renderer
+  renderedClass  CDATA #REQUIRED
+  renderingClass CDATA #REQUIRED
+>
+
+<!-- Appenders must have a name and a class. -->
+<!-- Appenders may contain an error handler, a layout, optional parameters -->
+<!-- and filters. They may also reference (or include) other appenders. -->
+<!ELEMENT appender (errorHandler?, param*, layout?, filter*, appender-ref*)>
+<!ATTLIST appender
+  name 		ID 	#REQUIRED
+  class 	CDATA	#REQUIRED
+>
+
+<!ELEMENT layout (param*)>
+<!ATTLIST layout
+  class		CDATA	#REQUIRED
+>
+
+<!ELEMENT filter (param*)>
+<!ATTLIST filter
+  class		CDATA	#REQUIRED
+>
+
+<!-- ErrorHandlers can be of any class. They can admit any number of -->
+<!-- parameters. -->
+
+<!ELEMENT errorHandler (param*, root-ref?, logger-ref*,  appender-ref?)> 
+<!ATTLIST errorHandler
+   class        CDATA   #REQUIRED 
+>
+
+<!ELEMENT root-ref EMPTY>
+
+<!ELEMENT logger-ref EMPTY>
+<!ATTLIST logger-ref
+  ref IDREF #REQUIRED
+>
+
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+  name		CDATA   #REQUIRED
+  value		CDATA	#REQUIRED
+>
+
+
+<!-- The priority class is org.apache.log4j.Level by default -->
+<!--
+    DEPRECATED
+
+<!ELEMENT priority (param*)>
+<!ATTLIST priority
+  class   CDATA	#IMPLIED
+  value	  CDATA #REQUIRED
+>
+-->
+
+<!-- The level class is org.apache.log4j.Level by default -->
+<!ELEMENT level (param*)>
+<!ATTLIST level
+  class   CDATA	#IMPLIED
+  value	  CDATA #REQUIRED
+>
+
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named category. -->
+<!--
+    DEPRECATED
+
+<!ELEMENT category (param*,(priority|level)?,appender-ref*)>
+<!ATTLIST category
+  class         CDATA   #IMPLIED
+  name		CDATA	#REQUIRED
+  additivity	(true|false) "true"  
+>
+-->
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named logger. -->
+<!ELEMENT logger (level?, appender-ref*)>
+<!ATTLIST logger
+  class         CDATA   #IMPLIED
+  name		ID	#REQUIRED
+  additivity	(true|false) "true"  
+>
+
+<!--
+    DEPRECATED
+
+<!ELEMENT categoryFactory (param*)>
+<!ATTLIST categoryFactory 
+   class        CDATA #REQUIRED>
+-->
+
+<!ELEMENT appender-ref EMPTY>
+<!ATTLIST appender-ref
+  ref IDREF #REQUIRED
+>
+
+<!-- If no priority element is specified, then the configurator MUST not -->
+<!-- touch the priority of root. -->
+<!-- The root category always exists and cannot be subclassed. -->
+<!ELEMENT root (param*, level?, appender-ref*)>
+
+
+<!-- ==================================================================== -->
+<!--                       A logging event                                -->
+<!-- ==================================================================== -->
+<!ELEMENT log4j:eventSet (log4j:event*)>
+<!ATTLIST log4j:eventSet
+  xmlns:log4j             CDATA #FIXED "http://jakarta.apache.org/log4j/" 
+  version                (1.1|1.2) "1.2" 
+  includesLocationInfo   (true|false) "true"
+>
+
+
+
+<!ELEMENT log4j:event (log4j:message, log4j:NDC?, log4j:throwable?, 
+                       log4j:locationInfo?) >
+
+<!-- The timestamp format is application dependent. -->
+<!ATTLIST log4j:event
+    logger     CDATA #REQUIRED
+    level      CDATA #REQUIRED
+    thread     CDATA #REQUIRED
+    timestamp  CDATA #REQUIRED
+>
+
+<!ELEMENT log4j:message (#PCDATA)>
+<!ELEMENT log4j:NDC (#PCDATA)>
+
+<!ELEMENT log4j:throwable (#PCDATA)>
+
+<!ELEMENT log4j:locationInfo EMPTY>
+<!ATTLIST log4j:locationInfo
+  class  CDATA	#REQUIRED
+  method CDATA	#REQUIRED
+  file   CDATA	#REQUIRED
+  line   CDATA	#REQUIRED
+>

Added: sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.xml
URL: http://svn.apache.org/viewvc/sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.xml?rev=1807480&view=auto
==============================================================================
--- sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.xml (added)
+++ sling/whiteboard/microsling/src/main/webapp/WEB-INF/log4j.xml Wed Sep  6 13:24:23 2017
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+    <!-- ====================================================================== -->
+    <!-- A P P E N D E R S                                                      -->
+    <!-- ====================================================================== -->
+
+    <!-- console -->
+    <appender name="console" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%d{dd.MM.yyyy HH:mm:ss} *%-5p* %c{1}: %m (%F, line %L)%n"/>
+        </layout>
+    </appender>
+
+    <!-- log file -->
+    <appender name="file" class="org.apache.log4j.FileAppender">
+        <param name="File" value="logs/microsling.log"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%d{dd.MM.yyyy HH:mm:ss} *%-5p* %c{1}: %m (%F, line %L)%n"/>
+        </layout>
+    </appender>
+    
+    <!-- ====================================================================== -->
+    <!-- C A T E G O R I E S                                                    -->
+    <!-- ====================================================================== -->
+     <category name="org.apache.sling">
+      <priority value="debug"/>
+      <appender-ref ref="console"/>
+      <appender-ref ref="file"/>
+    </category>
+
+</log4j:configuration>