This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 6aa81ab  Swagger UI improvements.
6aa81ab is described below

commit 6aa81abfc100bd624921267ba9e7bb54ce0472a8
Author: JamesBognar <jamesbog...@apache.org>
AuthorDate: Fri Apr 13 19:19:50 2018 -0400

    Swagger UI improvements.
---
 .../java/org/apache/juneau/html/BasicHtmlTest.java |   2 +-
 .../org/apache/juneau/utils/StringUtilsTest.java   |  12 +
 .../java/org/apache/juneau/dto/LinkString.java     |  43 +--
 .../apache/juneau/dto/swagger/OperationMap.java    |  31 +-
 .../org/apache/juneau/html/HtmlParserSession.java  |   2 +-
 .../apache/juneau/html/HtmlSerializerSession.java  |   2 +-
 .../apache/juneau/html/annotation/HtmlLink.java    |   4 +-
 .../org/apache/juneau/internal/StringUtils.java    |  17 ++
 juneau-doc/src/main/javadoc/overview.html          |  23 +-
 juneau-examples/juneau-examples-rest/examples.cfg  |   2 +-
 .../juneau/examples/rest/DirectoryResource.java    | 305 -------------------
 .../examples/rest/PredefinedLabelsResource.java    |  19 +-
 .../examples/rest/SystemPropertiesResource.java    |  16 +-
 .../juneau/examples/rest/TempDirResource.java      |  13 +-
 .../examples/rest/TestMultiPartFormPostsTest.java  |   2 +-
 .../microservice/resources/DirectoryResource.java  | 327 ++++++++-------------
 .../my-microservice.cfg                            |   2 +-
 .../org/apache/juneau/rest/BasicRestConfig.java    |   2 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |   9 +
 ...ServletRoot.java => RedirectToServletRoot.java} |   6 +-
 .../apache/juneau/rest/vars/RequestPathVar.java    |   2 +
 21 files changed, 257 insertions(+), 584 deletions(-)

diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
index 10b9c5f..61d7f0d 100644
--- 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
@@ -2832,7 +2832,7 @@ public class BasicHtmlTest {
                }
        }
 
-       @HtmlLink(nameProperty="a",hrefProperty="b")
+       @HtmlLink(nameProperty="a",uriProperty="b")
        public static class LinkBean {
                public String a;
                public String b;
diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
index 1b9f080..44eaa14 100755
--- 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/StringUtilsTest.java
@@ -875,4 +875,16 @@ public class StringUtilsTest {
                assertEquals("1: foo\n", getNumberedLines("foo"));
                assertEquals("1: foo\n2: bar\n", getNumberedLines("foo\nbar"));
        }
+       
+       
//====================================================================================================
+       // compare(String,String)
+       
//====================================================================================================
+       @Test
+       public void testCompare() throws Exception {
+               assertTrue(compare("a","b") < 0);
+               assertTrue(compare("b","a") > 0);
+               assertTrue(compare(null,"b") < 0);
+               assertTrue(compare("b",null) > 0);
+               assertTrue(compare(null,null) == 0);
+       }       
 }
diff --git 
a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/LinkString.java 
b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/LinkString.java
index 0bb04d6..a91d85a 100644
--- a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/LinkString.java
+++ b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/LinkString.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.StringUtils.*;
 
 import java.text.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.httppart.*;
@@ -34,9 +35,11 @@ import org.apache.juneau.utils.*;
  * When encountered by the {@link HtmlSerializer} class, this object gets 
converted to a hyperlink.
  * All other serializers simply convert it to a simple bean.
  */
-@HtmlLink(nameProperty = "name", hrefProperty = "href")
+@HtmlLink
+@Bean(fluentSetters=true)
 public class LinkString implements Comparable<LinkString> {
-       private String name, href;
+       private String name;
+       private java.net.URI uri;
 
        /** No-arg constructor. */
        public LinkString() {}
@@ -45,12 +48,12 @@ public class LinkString implements Comparable<LinkString> {
         * Constructor.
         * 
         * @param name Corresponds to the text inside of the <xt>&lt;A&gt;</xt> 
element.
-        * @param href Corresponds to the value of the <xa>href</xa> attribute 
of the <xt>&lt;A&gt;</xt> element.
-        * @param hrefArgs Optional arguments for {@link MessageFormat} style 
arguments in the href.
+        * @param uri Corresponds to the value of the <xa>href</xa> attribute 
of the <xt>&lt;A&gt;</xt> element.
+        * @param uriArgs Optional arguments for {@link MessageFormat} style 
arguments in the href.
         */
-       public LinkString(String name, String href, Object...hrefArgs) {
-               setName(name);
-               setHref(href, hrefArgs);
+       public LinkString(String name, String uri, Object...uriArgs) {
+               name(name);
+               uri(uri, uriArgs);
        }
 
 
@@ -76,48 +79,48 @@ public class LinkString implements Comparable<LinkString> {
         * @param name The new value for the <property>name</property> property 
on this bean.
         * @return This object (for method chaining).
         */
-       public LinkString setName(String name) {
+       public LinkString name(String name) {
                this.name = name;
                return this;
        }
 
        /**
-        * Bean property getter:  <property>href</property>.
+        * Bean property getter:  <property>uri</property>.
         * 
         * <p>
         * Corresponds to the value of the <xa>href</xa> attribute of the 
<xt>&lt;A&gt;</xt> element.
         * 
         * @return The value of the <property>href</property> property on this 
bean, or <jk>null</jk> if it is not set.
         */
-       public String getHref() {
-               return href;
+       public java.net.URI getUri() {
+               return uri;
        }
 
        /**
-        * Bean property setter:  <property>href</property>.
+        * Bean property setter:  <property>uri</property>.
         * 
-        * @param href The new value for the <property>href</property> property 
on this bean.
+        * @param uri The new value for the <property>href</property> property 
on this bean.
         * @return This object (for method chaining).
         */
-       public LinkString setHref(String href) {
-               setHref(href, new Object[0]);
+       public LinkString uri(String uri) {
+               uri(uri, new Object[0]);
                return this;
        }
 
        /**
-        * Bean property setter:  <property>href</property>.
+        * Bean property setter:  <property>uri</property>.
         * 
         * <p>
-        * Same as {@link #setHref(String)} except allows for {@link 
MessageFormat} style arguments.
+        * Same as {@link #uri(String)} except allows for {@link MessageFormat} 
style arguments.
         * 
-        * @param href The new href.
+        * @param uri The new href.
         * @param args Optional {@link MessageFormat}-style arguments.
         * @return This object (for method chaining).
         */
-       public LinkString setHref(String href, Object...args) {
+       public LinkString uri(String uri, Object...args) {
                for (int i = 0; i < args.length; i++)
                        args[i] = 
SimpleUonPartSerializer.DEFAULT.serialize(HttpPartType.PATH, args[i]);
-               this.href = format(href, args);
+               this.uri = java.net.URI.create(format(uri, args));
                return this;
        }
 
diff --git 
a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/OperationMap.java
 
b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/OperationMap.java
index 8221758..017d0c1 100644
--- 
a/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/OperationMap.java
+++ 
b/juneau-core/juneau-dto/src/main/java/org/apache/juneau/dto/swagger/OperationMap.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.StringUtils.*;
 
 import java.util.*;
 
+import org.apache.juneau.internal.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -38,24 +39,24 @@ public class OperationMap extends TreeMap<String,Operation> 
{
        private static final long serialVersionUID = 1L;
 
        private static final Comparator<String> OP_SORTER = new 
Comparator<String>() {
-               private final Map<String,Integer> methods = new 
AMap<String,Integer>()
-                       .append("get",7)
-                       .append("put",6)
-                       .append("post",5)
-                       .append("delete",4)
-                       .append("options",3)
-                       .append("head",2)
-                       .append("patch",1);
+               private final Map<String,String> methods = new 
AMap<String,String>()
+                       .append("get","0")
+                       .append("put","1")
+                       .append("post","2")
+                       .append("delete","3")
+                       .append("options","4")
+                       .append("head","5")
+                       .append("patch","6");
 
                @Override
                public int compare(String o1, String o2) {
-                       Integer i1 = methods.get(o1);
-                       Integer i2 = methods.get(o2);
-                       if (i1 == null)
-                               i1 = 0;
-                       if (i2 == null)
-                               i2 = 0;
-                       return i2.compareTo(i1);
+                       String s1 = methods.get(o1);
+                       String s2 = methods.get(o2);
+                       if (s1 == null)
+                               s1 = o1;
+                       if (s2 == null)
+                               s2 = o2;
+                       return StringUtils.compare(s1, s2);
                }
        };
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
index 993b603..72b3be3 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
@@ -315,7 +315,7 @@ public final class HtmlParserSession extends 
XmlParserSession {
                if (beanClass.isAnnotationPresent(HtmlLink.class)) {
                        HtmlLink h = beanClass.getAnnotation(HtmlLink.class);
                        BeanMap<T> m = newBeanMap(beanClass);
-                       m.put(h.hrefProperty(), href);
+                       m.put(h.uriProperty(), href);
                        m.put(h.nameProperty(), name);
                        return m.getBean();
                }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 5bb1124..94871d3 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -378,7 +378,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
                                Class<?> c = o.getClass();
                                if (c.isAnnotationPresent(HtmlLink.class)) {
                                        HtmlLink h = 
o.getClass().getAnnotation(HtmlLink.class);
-                                       Object urlProp = 
m.get(h.hrefProperty());
+                                       Object urlProp = m.get(h.uriProperty());
                                        Object nameProp = 
m.get(h.nameProperty());
                                        out.oTag("a").attrUri("href", 
urlProp).append('>').text(nameProp).eTag("a");
                                        cr = CR_MIXED;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlLink.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlLink.java
index 735e970..7a4873c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlLink.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/annotation/HtmlLink.java
@@ -44,10 +44,10 @@ public @interface HtmlLink {
        /**
         * The bean property whose value becomes the name in the hyperlink.
         */
-       String nameProperty() default "";
+       String nameProperty() default "name";
 
        /**
         * The bean property whose value becomes the url in the hyperlink.
         */
-       String hrefProperty() default "";
+       String uriProperty() default "uri";
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
index 58af833..694a01b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -2149,4 +2149,21 @@ public final class StringUtils {
                        sb.append(String.format("%0"+digits+"d", 
start++)).append(": ").append(l).append("\n");
                return sb.toString();
        }
+
+       /**
+        * Compares two strings, but gracefully handles <jk>nulls</jk>.
+        * 
+        * @param s1 The first string.
+        * @param s2 The second string.
+        * @return The same as {@link String#compareTo(String)}.
+        */
+       public static int compare(String s1, String s2) {
+               if (s1 == null && s2 == null)
+                       return 0;
+               if (s1 == null)
+                       return Integer.MIN_VALUE;
+               if (s2 == null)
+                       return Integer.MAX_VALUE;
+               return s1.compareTo(s2);
+       }
 }
diff --git a/juneau-doc/src/main/javadoc/overview.html 
b/juneau-doc/src/main/javadoc/overview.html
index e0f54db..7f3922d 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -15443,14 +15443,23 @@
        }
                </p>                    
                <p>
-                       This example renders the following consisting of a list 
of hyperlinks:
+                       The {@link org.apache.juneau.dto.LinkString Action} 
bean is a predefined <ja>@HtmlLink</ja> bean provided
+                       to simplify specifying actions.
+                       <br>The following is equivalent to above.
+               </p>
+               <p class='bcode w800'>
+       <ja>@RestMethod</ja>(name=<jsf>GET</jsf>, path=<js>"/htmlLinks"</js>)
+       <jk>public</jk> Action[] htmlLinks() {
+               <jk>return new</jk> Action[] {
+                       <jk>new</jk> Action(<js>"apache"</js>, 
<js>"http://apache.org";</js>),
+                       <jk>new</jk> Action(<js>"juneau"</js>, 
<js>"http://juneau.apache.org";</js>)
+               };
+       }
+               </p>                    
+               <p>
+                       Both examples render the following consisting of a list 
of hyperlinks:
                </p>
                <img class='bordered' 
src='doc-files/juneau-rest-server.PredefinedLabelBeans.3.png' 
style='width:92px'/>
-               
-               <h5 class='section'>See Also:</h5>
-               <ul>
-                       <li class='jc'>{@link org.apache.juneau.dto.LinkString}
-               </ul>
        </div>
        
        <!-- 
========================================================================================================
 -->
@@ -21392,7 +21401,7 @@
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.ReaderResource}
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.ReaderResourceBuilder}
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.Redirect}
-                                               <li class='jc'>{@link 
org.apache.juneau.rest.helper.RedirectServletRoot}
+                                               <li class='jc'>{@link 
org.apache.juneau.rest.helper.RedirectToServletRoot}
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.ResourceDescription}
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.StreamResource}
                                                <li class='jc'>{@link 
org.apache.juneau.rest.helper.StreamResourceBuilder}
diff --git a/juneau-examples/juneau-examples-rest/examples.cfg 
b/juneau-examples/juneau-examples-rest/examples.cfg
index 5336293..e869a35 100755
--- a/juneau-examples/juneau-examples-rest/examples.cfg
+++ b/juneau-examples/juneau-examples-rest/examples.cfg
@@ -55,7 +55,7 @@ headerLink = http://juneau.apache.org
 footerIcon = servlet:/htdocs/images/asf.png
 footerLink = http://www.apache.org
 
-icon = $C{REST/headerIcon}
+favicon = $C{REST/headerIcon}
 header = <a href='$U{$C{REST/headerLink}}'><img src='$U{$C{REST/headerIcon}}' 
style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/></a>
 footer = <a href='$U{$C{REST/footerLink}}'><img 
style='float:right;padding-right:20px;height:32px' 
src='$U{$C{REST/footerIcon}}'>
 
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
deleted file mode 100644
index 069da14..0000000
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/DirectoryResource.java
+++ /dev/null
@@ -1,305 +0,0 @@
-// 
***************************************************************************************************************************
-// * 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.juneau.examples.rest;
-
-import static java.util.logging.Level.*;
-import static org.apache.juneau.html.HtmlSerializer.*;
-import static org.apache.juneau.http.HttpMethodName.*;
-import static org.apache.juneau.rest.annotation.HookEvent.*;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.logging.*;
-
-import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.converters.*;
-import org.apache.juneau.rest.exception.*;
-import org.apache.juneau.rest.helper.*;
-import org.apache.juneau.rest.widget.*;
-import org.apache.juneau.utils.*;
-
-/**
- * Sample REST resource for exploring local file systems.
- */
-@RestResource(
-       messages="nls/DirectoryResource",
-       htmldoc=@HtmlDoc(
-               widgets={
-                       ContentTypeMenuItem.class,
-                       ThemeMenuItem.class
-               },
-               navlinks={
-                       "up: request:/..",
-                       "options: servlet:/?method=OPTIONS",
-                       "$W{ContentTypeMenuItem}",
-                       "$W{ThemeMenuItem}",
-                       "source: 
$C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
-               }
-       ),
-       allowedMethodParams="*",
-       properties={
-               @Property(name=HTML_uriAnchorText, value="PROPERTY_NAME"),
-               @Property(name="rootDir", value="$S{java.io.tmpdir}"),
-               @Property(name="allowViews", value="false"),
-               @Property(name="allowDeletes", value="false"),
-               @Property(name="allowPuts", value="false")
-       },
-       swagger={
-               "info: {",
-                       "contact:{name:'Juneau 
Developer',email:'d...@juneau.apache.org'},",
-                       "license:{name:'Apache 
2.0',url:'http://www.apache.org/licenses/LICENSE-2.0.html'},",
-                       "version:'2.0',",
-                       "termsOfService:'You are on your own.'",
-               "},",
-               "externalDocs:{description:'Apache 
Juneau',url:'http://juneau.apache.org'}"
-       }
-)
-public class DirectoryResource extends BasicRestServlet {
-       private static final long serialVersionUID = 1L;
-
-       private File rootDir;     // The root directory
-
-       // Settings enabled through servlet init parameters
-       boolean allowDeletes, allowPuts, allowViews;
-
-       private static Logger logger = 
Logger.getLogger(DirectoryResource.class.getName());
-
-       @RestHook(INIT)
-       public void init(RestContextBuilder builder) throws Exception {
-               RestContextProperties p = builder.getProperties();
-               rootDir = new File(p.getString("rootDir"));
-               allowViews = p.getBoolean("allowViews", false);
-               allowDeletes = p.getBoolean("allowDeletes", false);
-               allowPuts = p.getBoolean("allowPuts", false);
-       }
-
-       /** Returns the root directory defined by the 'rootDir' init parameter 
*/
-       protected File getRootDir() {
-               if (rootDir == null) {
-                       rootDir = new 
File(getProperties().getString("rootDir"));
-                       if (! rootDir.exists())
-                               if (! rootDir.mkdirs())
-                                       throw new RuntimeException("Could not 
create root dir");
-               }
-               return rootDir;
-       }
-
-       @RestMethod(
-               name=GET, 
-               path="/*",
-               summary="Get file or directory information",
-               description="Returns information about a file or directory.",
-               converters={Queryable.class},
-               swagger={
-                       "parameters:[",
-                                Queryable.SWAGGER_PARAMS,
-                       "]"
-               }
-       )
-       public Object doGet(RestRequest req, RequestProperties properties) 
throws NotFound, InternalServerError {
-
-               String pathInfo = req.getPathInfo();
-               File f = pathInfo == null ? rootDir : new 
File(rootDir.getAbsolutePath() + pathInfo);
-
-               if (!f.exists())
-                       throw new NotFound("File not found");
-
-               properties.put("path", f.getAbsolutePath());
-
-               try {
-                       if (f.isDirectory()) {
-                               List<FileResource> l = new LinkedList<>();
-                               File[] lfc = f.listFiles();
-                               if (lfc != null) {
-                                       for (File fc : lfc) {
-                                               URL fUrl = new 
URL(req.getRequestURL().append("/").append(fc.getName()).toString());
-                                               l.add(new FileResource(fc, 
fUrl));
-                                       }
-                               }
-                               return l;
-                       }
-
-                       return new FileResource(f, new 
URL(req.getRequestURL().toString()));
-                       
-               } catch (MalformedURLException e) {
-                       throw new InternalServerError(e);
-               }
-       }
-
-       @RestMethod(
-               name=DELETE, 
-               path="/*", 
-               summary="Delete file",
-               description="Delete a file on the file system.",
-               guards=AdminGuard.class
-       )
-       public Object doDelete(RestRequest req) throws MethodNotAllowed, 
Forbidden {
-
-               if (! allowDeletes)
-                       throw new MethodNotAllowed("DELETE not enabled");
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-               deleteFile(f);
-
-               if (req.getHeader("Accept").contains("text/html"))
-                       return new Redirect();
-               
-               return "File deleted";
-       }
-
-       @RestMethod(
-               name=PUT, 
-               path="/*", 
-               summary="Upload file",
-               description="Uploads a file to the file system.",
-               guards=AdminGuard.class
-       )
-       public Object doPut(RestRequest req) throws MethodNotAllowed, 
InternalServerError, Forbidden {
-
-               if (! allowPuts)
-                       throw new MethodNotAllowed("PUT not enabled");
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-               String parentSubPath = 
f.getParentFile().getAbsolutePath().substring(rootDir.getAbsolutePath().length());
-               
-               try (InputStream is = req.getInputStream(); OutputStream os = 
new BufferedOutputStream(new FileOutputStream(f))) {
-                       IOPipe.create(is, os).run();
-               } catch (IOException e) {
-                       throw new InternalServerError(e);
-               }
-               
-               if (req.getContentType().contains("html"))
-                       return new Redirect(parentSubPath);
-               
-               return "File added";
-       }
-
-       /** VIEW request handler (overloaded GET for viewing file contents) */
-       @SuppressWarnings("resource")
-       @RestMethod(
-               name="VIEW", 
-               path="/*",
-               summary="View file",
-               description="Views the contents of a file as plain text."
-       )
-       public void doView(RestRequest req, RestResponse res) throws 
MethodNotAllowed, NotFound {
-
-               if (! allowViews)
-                       throw new MethodNotAllowed("VIEW not enabled");
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-
-               if (f.isDirectory())
-                       throw new MethodNotAllowed("VIEW not available on 
directories");
-
-               try {
-                       res.setOutput(new 
FileReader(f)).setContentType("text/plain");
-               } catch (FileNotFoundException e) {
-                       throw new NotFound("File not found");
-               }
-       }
-
-       /** DOWNLOAD request handler (overloaded GET for downloading file 
contents) */
-       @SuppressWarnings("resource")
-       @RestMethod(
-               name="DOWNLOAD",
-               path="/*",
-               summary="Download file",
-               description="Download the contents of a file as an octet 
stream."
-       )
-       public void doDownload(RestRequest req, RestResponse res) throws 
MethodNotAllowed, NotFound {
-
-               if (! allowViews)
-                       throw new MethodNotAllowed("DOWNLOAD not enabled");
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-
-               if (f.isDirectory())
-                       throw new MethodNotAllowed("DOWNLOAD not available on 
directories");
-
-               try {
-                       res.setOutput(new 
FileReader(f)).setContentType("application");
-               } catch (FileNotFoundException e) {
-                       throw new NotFound("File not found");
-               }
-       }
-
-       /** File POJO */
-       public class FileResource {
-               private File f;
-               private URL url;
-
-               /** Constructor */
-               public FileResource(File f, URL url) {
-                       this.f = f;
-                       this.url = url;
-               }
-
-               // Bean property getters
-
-               public URL getUrl() {
-                       return url;
-               }
-
-               public String getType() {
-                       return (f.isDirectory() ? "dir" : "file");
-               }
-
-               public String getName() {
-                       return f.getName();
-               }
-
-               public long getSize() {
-                       return f.length();
-               }
-
-               public Date getLastModified() {
-                       return new Date(f.lastModified());
-               }
-
-               public URL getView() throws Exception {
-                       if (allowViews && f.canRead() && ! f.isDirectory())
-                               return new URL(url + "?method=VIEW");
-                       return null;
-               }
-
-               public URL getDownload() throws Exception {
-                       if (allowViews && f.canRead() && ! f.isDirectory())
-                               return new URL(url + "?method=DOWNLOAD");
-                       return null;
-               }
-
-               public URL getDelete() throws Exception {
-                       if (allowDeletes && f.canWrite())
-                               return new URL(url + "?method=DELETE");
-                       return null;
-               }
-       }
-
-       /** Utility method */
-       private void deleteFile(File f) {
-               try {
-                       if (f.isDirectory()) {
-                               File[] lfc = f.listFiles();
-                               if (lfc != null)
-                                       for (File fc : lfc)
-                                               deleteFile(fc);
-                       }
-                       f.delete();
-               } catch (Exception e) {
-                       logger.log(WARNING, "Cannot delete file '" + 
f.getAbsolutePath() + "'", e);
-               }
-       }
-}
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
index af80312..da0befb 100644
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
+++ 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PredefinedLabelsResource.java
@@ -14,8 +14,8 @@ package org.apache.juneau.examples.rest;
 
 import static org.apache.juneau.http.HttpMethodName.*;
 
+import org.apache.juneau.dto.*;
 import org.apache.juneau.examples.addressbook.*;
-import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.helper.*;
@@ -68,19 +68,10 @@ public class PredefinedLabelsResource extends 
BasicRestServlet {
        }       
        
        @RestMethod(name=GET, path="/htmlLinks")
-       public ALink[] htmlLinks() throws Exception {
-               return new ALink[] {
-                       new ALink("apache", "http://apache.org";),
-                       new ALink("juneau", "http://juneau.apache.org";)
+       public LinkString[] htmlLinks() throws Exception {
+               return new LinkString[] {
+                       new LinkString("apache", "http://apache.org";),
+                       new LinkString("juneau", "http://juneau.apache.org";)
                };
        }
-       
-       @HtmlLink(nameProperty="n", hrefProperty="l")
-       public static class ALink {
-               public String n, l;
-               public ALink(String n, String l) {
-                       this.n = n;
-                       this.l = l;
-               }
-       }
 }
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
index f381e63..591d042 100644
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
+++ 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SystemPropertiesResource.java
@@ -134,13 +134,13 @@ public class SystemPropertiesResource extends 
BasicRestServlet {
                description="Sets a new value for the specified system 
property.",
                guards=AdminGuard.class
        )
-       public RedirectServletRoot setSystemProperty(
+       public RedirectToServletRoot setSystemProperty(
                        @Path(description="The system property name") String 
propertyName, 
                        @Body(description="The new system property value") 
String value
                ) throws UserNotAdminException, NotAcceptable, 
UnsupportedMediaType {
                
                System.setProperty(propertyName, value);
-               return RedirectServletRoot.INSTANCE;
+               return RedirectToServletRoot.INSTANCE;
        }
 
        @RestMethod(
@@ -149,12 +149,12 @@ public class SystemPropertiesResource extends 
BasicRestServlet {
                description="Takes in a map of key/value pairs and creates a 
set of new system properties.",
                guards=AdminGuard.class
        )
-       public RedirectServletRoot setSystemProperties(
+       public RedirectToServletRoot setSystemProperties(
                        @Body(description="The new system property values", 
example="{key1:'val1',key2:123}") java.util.Properties newProperties
                ) throws UserNotAdminException, NotAcceptable, 
UnsupportedMediaType {
                
                System.setProperties(newProperties);
-               return RedirectServletRoot.INSTANCE;
+               return RedirectToServletRoot.INSTANCE;
        }
 
        @RestMethod(
@@ -163,12 +163,12 @@ public class SystemPropertiesResource extends 
BasicRestServlet {
                description="Deletes the specified system property.",
                guards=AdminGuard.class
        )
-       public RedirectServletRoot deleteSystemProperty(
+       public RedirectToServletRoot deleteSystemProperty(
                        @Path(description="The system property name", 
example="PATH") String propertyName
                ) throws UserNotAdminException, NotAcceptable {
                
                System.clearProperty(propertyName);
-               return RedirectServletRoot.INSTANCE;
+               return RedirectToServletRoot.INSTANCE;
        }
 
        @RestMethod(
@@ -207,13 +207,13 @@ public class SystemPropertiesResource extends 
BasicRestServlet {
                description="Accepts a simple form post of a system property 
name/value pair.",
                guards=AdminGuard.class
        )
-       public RedirectServletRoot formPagePost(
+       public RedirectToServletRoot formPagePost(
                        @FormData("name") String name, 
                        @FormData("value") String value
                ) throws UserNotAdminException, NotAcceptable, 
UnsupportedMediaType {
                
                System.setProperty(name, value);
-               return RedirectServletRoot.INSTANCE;
+               return RedirectToServletRoot.INSTANCE;
        }
        
        
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
index a3a3bf7..79d3fb3 100644
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
+++ 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/TempDirResource.java
@@ -22,6 +22,7 @@ import org.apache.commons.fileupload.*;
 import org.apache.commons.fileupload.servlet.*;
 import org.apache.commons.io.*;
 import org.apache.juneau.dto.html5.*;
+import org.apache.juneau.microservice.resources.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.helper.*;
@@ -56,10 +57,10 @@ import org.apache.juneau.utils.*;
                }
        ),
        properties={
-               @Property(name="rootDir", 
value="$C{TempDirResource/dir,$S{java.io.tmpdir}}"),
-               @Property(name="allowViews", value="true"),
-               @Property(name="allowDeletes", value="true"),
-               @Property(name="allowPuts", value="false")
+               @Property(name="DirectoryResource.rootDir", 
value="$C{TempDirResource/dir,$S{java.io.tmpdir}}"),
+               @Property(name="DirectoryResource.allowViews", value="true"),
+               @Property(name="DirectoryResource.allowDeletes", value="true"),
+               @Property(name="DirectoryResource.allowPuts", value="false")
        },
        swagger={
                "info: {",
@@ -115,7 +116,7 @@ public class TempDirResource extends DirectoryResource {
                },
                matchers=TempDirResource.MultipartFormDataMatcher.class
        )
-       public Redirect uploadFile(RestRequest req) throws Exception {
+       public RedirectToServletRoot uploadFile(RestRequest req) throws 
Exception {
                ServletFileUpload upload = new ServletFileUpload();
                FileItemIterator iter = upload.getItemIterator(req);
                while (iter.hasNext()) {
@@ -127,7 +128,7 @@ public class TempDirResource extends DirectoryResource {
                                }
                        }
                }
-               return new Redirect(); // Redirect to the servlet root.
+               return RedirectToServletRoot.INSTANCE; 
        }
 
        /** Causes a 404 if POST isn't multipart/form-data */
diff --git 
a/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/TestMultiPartFormPostsTest.java
 
b/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/TestMultiPartFormPostsTest.java
index 55dc99b..7f43edd 100644
--- 
a/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/TestMultiPartFormPostsTest.java
+++ 
b/juneau-examples/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/TestMultiPartFormPostsTest.java
@@ -41,7 +41,7 @@ public class TestMultiPartFormPostsTest extends RestTestcase {
                HttpEntity entity = 
MultipartEntityBuilder.create().addBinaryBody(f.getName(), f).build();
                client.doPost(URL + "/upload", entity);
 
-               String downloaded = client.doGet(URL + '/' + f.getName() + 
"?method=VIEW").getResponseAsString();
+               String downloaded = client.doGet(URL + "/file/" + f.getName() + 
"?method=VIEW").getResponseAsString();
                assertEquals("test!", downloaded);
        }
 }
\ No newline at end of file
diff --git 
a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
 
b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
index 679b50d..e5be768 100755
--- 
a/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
+++ 
b/juneau-microservice/juneau-microservice-server/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
@@ -15,15 +15,16 @@ package org.apache.juneau.microservice.resources;
 import static java.util.logging.Level.*;
 import static org.apache.juneau.html.HtmlDocSerializer.*;
 import static org.apache.juneau.http.HttpMethodName.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.rest.annotation.HookEvent.*;
 
 import java.io.*;
-import java.net.*;
+import java.net.URI;
 import java.util.*;
 import java.util.logging.*;
 
-import javax.servlet.*;
-
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.dto.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.converters.*;
@@ -60,7 +61,6 @@ import org.apache.juneau.utils.*;
  */
 @RestResource(
        title="File System Explorer",
-       description="Contents of $RA{path}",
        messages="nls/DirectoryResource",
        htmldoc=@HtmlDoc(
                navlinks={
@@ -74,6 +74,7 @@ import org.apache.juneau.utils.*;
                @Property(name="DirectoryResource.rootDir", value="")
        }
 )
+@SuppressWarnings("javadoc")
 public class DirectoryResource extends BasicRestServlet {
        private static final long serialVersionUID = 1L;
 
@@ -84,9 +85,9 @@ public class DirectoryResource extends BasicRestServlet {
 
        private static Logger logger = 
Logger.getLogger(DirectoryResource.class.getName());
 
-       @Override /* Servlet */
-       public void init() throws ServletException {
-               RestContextProperties p = getProperties();
+       @RestHook(INIT)
+       public void init(RestContextBuilder b) throws Exception { 
+               RestContextProperties p = b.getProperties();
                rootDir = new File(p.getString("DirectoryResource.rootDir"));
                allowViews = p.getBoolean("DirectoryResource.allowViews", 
false);
                allowDeletes = p.getBoolean("DirectoryResource.allowDeletes", 
false);
@@ -111,257 +112,189 @@ public class DirectoryResource extends BasicRestServlet 
{
                return rootDir;
        }
 
-       /**
-        * [GET /*] - On directories, returns a directory listing.  On files, 
returns information about the file.
-        * 
-        * @param req The HTTP request.
-        * @return Either a FileResource or list of FileResources depending on 
whether it's a
-        *      file or directory.
-        * @throws NotFound If file was not found.
-        * @throws MalformedURLException 
-        */
-       @RestMethod(name=GET, path="/*",
-               description="On directories, returns a directory listing.\nOn 
files, returns information about the file.",
+       @RestMethod(
+               name=GET, 
+               path="/*",
+               summary="View files on directory",
+               description="Returns a listing of all files in the specified 
directory.",
                converters={Queryable.class}
        )
-       public Object doGet(RestRequest req) throws NotFound, 
MalformedURLException {
-               checkAccess(req);
-
-               String pathInfo = req.getPathInfo();
-               File f = pathInfo == null ? rootDir : new 
File(rootDir.getAbsolutePath() + pathInfo);
-
-               if (!f.exists())
-                       throw new NotFound("File not found");
-
-               req.setAttribute("path", f.getAbsolutePath());
-
-               if (f.isDirectory()) {
-                       List<FileResource> l = new LinkedList<>();
-                       File[] files = f.listFiles();
-                       if (files != null) {
-                               for (File fc : files) {
-                                       URL fUrl = new 
URL(req.getRequestURL().append("/").append(fc.getName()).toString());
-                                       l.add(new FileResource(fc, fUrl));
-                               }
-                       }
-                       return l;
-               }
-
-               return new FileResource(f, new 
URL(req.getRequestURL().toString()));
+       public FileListing listFiles(@PathRemainder String path) throws 
NotFound, Exception {
+               FileListing l = new FileListing();
+               for (File fc : getDir(path).listFiles()) 
+                       l.add(new FileResource(fc, (path != null ? (path + '/') 
: "") + urlEncode(fc.getName())));
+               return l;
+       }
+       
+       @RestMethod(
+               name=GET, 
+               path="/file/*",
+               summary="View information about file",
+               description="Returns detailed information about the specified 
file."
+       )
+       public FileResource getFileInfo(@PathRemainder String path) throws 
NotFound, Exception {
+               return new FileResource(getFile(path), path);
        }
 
-       /**
-        * [DELETE /*] - Delete a file on the file system.
-        * 
-        * @param req The HTTP request.
-        * @return The message <js>"File deleted"</js> if successful.
-        * @throws Exception If file could not be read or access was not 
granted.
-        */
-       @RestMethod(name=DELETE, path="/*",
+       @RestMethod(
+               name=DELETE, 
+               path="/file/*",
+               summary="Delete file",
                description="Delete a file on the file system."
        )
-       public Object doDelete(RestRequest req) throws Exception {
-               checkAccess(req);
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-               deleteFile(f);
-
-               if (req.getHeader("Accept").contains("text/html"))
-                       return new Redirect();
-               return "File deleted";
+       public RedirectToRoot deleteFile(@PathRemainder String path) throws 
MethodNotAllowed {
+               if (! allowDeletes)
+                       throw new MethodNotAllowed("DELETE not enabled");
+               deleteFile(getFile(path));
+               return new RedirectToRoot();
        }
 
-       /**
-        * [PUT /*] - Add or overwrite a file on the file system.
-        * 
-        * @param req The HTTP request.
-        * @return The message <js>"File added"</js> if successful.
-        * @throws InternalServerError If file could not be read or access was 
not granted.
-        */
-       @RestMethod(name=PUT, path="/*",
+       @RestMethod(
+               name=PUT, 
+               path="/file/*",
+               summary="Add or replace file",
                description="Add or overwrite a file on the file system."
        )
-       public Object doPut(RestRequest req) throws InternalServerError {
-               checkAccess(req);
+       public RedirectToRoot updateFile(
+               @Body(schema="{type:'string',format:'binary'}") InputStream is, 
+               @PathRemainder String path
+       ) throws InternalServerError {
+               
+               if (! allowPuts)
+                       throw new MethodNotAllowed("PUT not enabled");
 
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-               String parentSubPath = 
f.getParentFile().getAbsolutePath().substring(rootDir.getAbsolutePath().length());
-               try (InputStream is = req.getInputStream(); OutputStream os = 
new BufferedOutputStream(new FileOutputStream(f))) {
+               File f = getFile(path);
+               
+               try (OutputStream os = new BufferedOutputStream(new 
FileOutputStream(f))) {
                        IOPipe.create(is, os).run();
                } catch (IOException e) {
                        throw new InternalServerError(e);
                }
-               if (req.getContentType().contains("html"))
-                       return new Redirect(parentSubPath);
-               return "File added";
+               
+               return new RedirectToRoot();
        }
 
-       /**
-        * [VIEW /*] - View the contents of a file.  
-        * 
-        * <p>
-        * Applies to files only.
-        * 
-        * @param req The HTTP request.
-        * @param res The HTTP response.
-        * @return A Reader containing the contents of the file.
-        * @throws NotFound File not found.
-        * @throws MethodNotAllowed Method not allowed on directories.
-        */
-       @RestMethod(name="VIEW", path="/*",
-               description="View the contents of a file.\nApplies to files 
only."
+       @RestMethod(
+               name="VIEW", 
+               path="/file/*",
+               summary="View contents of file",
+               description="View the contents of a file."
        )
-       public Reader doView(RestRequest req, RestResponse res) throws 
NotFound, MethodNotAllowed {
-               checkAccess(req);
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-
-               if (f.isDirectory())
-                       throw new MethodNotAllowed("VIEW not available on 
directories");
+       public FileContents doView(RestResponse res, @PathRemainder String 
path) throws NotFound, MethodNotAllowed {
+               if (! allowViews)
+                       throw new MethodNotAllowed("VIEW not enabled");
 
                res.setContentType("text/plain");
                try {
-                       return new FileReader(f);
+                       return new FileContents(getFile(path));
                } catch (FileNotFoundException e) {
                        throw new NotFound("File not found");
                }
        }
-
-       /**
-        * [DOWNLOAD /*] - Download the contents of a file.
-        * 
-        * <p>
-        * Applies to files only.
-        * 
-        * @param req The HTTP request.
-        * @param res The HTTP response.
-        * @return A Reader containing the contents of the file.
-        * @throws NotFound Found could not be found. 
-        * @throws MethodNotAllowed Cannot call on a directory.
-        */
-       @RestMethod(name="DOWNLOAD", path="/*",
-               description="Download the contents of a file.\nApplies to files 
only."
+       
+       @RestMethod(
+               name="DOWNLOAD", 
+               path="/file/*",
+               summary="Download file",
+               description="Download the contents of a file"
        )
-       public Reader doDownload(RestRequest req, RestResponse res) throws 
NotFound, MethodNotAllowed {
-               checkAccess(req);
-
-               File f = new File(rootDir.getAbsolutePath() + 
req.getPathInfo());
-
-               if (f.isDirectory())
-                       throw new MethodNotAllowed("DOWNLOAD not available on 
directories");
+       public FileContents doDownload(RestResponse res, @PathRemainder String 
path) throws NotFound, MethodNotAllowed {
+               if (! allowViews)
+                       throw new MethodNotAllowed("DOWNLOAD not enabled");
 
-               res.setContentType("application");
+               res.setContentType("application/octet-stream");
                try {
-                       return new FileReader(f);
+                       return new FileContents(getFile(path));
                } catch (FileNotFoundException e) {
                        throw new NotFound("File not found");
                }
        }
 
-       /**
-        * Verify that the specified request is allowed.
-        * 
-        * <p>
-        * Subclasses can override this method to provide customized behavior.
-        * Method should throw a {@link RestException} if the request should be 
disallowed.
-        * 
-        * @param req The HTTP request.
-        * @throws MethodNotAllowed Thrown if specified method is not allowed.
-        */
-       protected void checkAccess(RestRequest req) throws MethodNotAllowed {
-               String method = req.getMethod();
-               if (method.equals("VIEW") && ! allowViews)
-                       throw new MethodNotAllowed("VIEW not enabled");
-               if (method.equals("PUT") && ! allowPuts)
-                       throw new MethodNotAllowed("PUT not enabled");
-               if (method.equals("DELETE") && ! allowDeletes)
-                       throw new MethodNotAllowed("DELETE not enabled");
-               if (method.equals("DOWNLOAD") && ! allowViews)
-                       throw new MethodNotAllowed("DOWNLOAD not enabled");
+       private File getFile(String path) throws NotFound {
+               File f = new File(rootDir.getAbsolutePath() + '/' + path);
+               if (f.exists() && f.isFile())
+                       return f;
+               throw new NotFound("File not found.");
+       }
+       
+       private File getDir(String path) throws NotFound {
+               if (path == null)
+                       return rootDir;
+               File f = new File(rootDir.getAbsolutePath() + '/' + path);
+               if (f.exists() && f.isDirectory())
+                       return f;
+               throw new NotFound("Directory not found.");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Helper beans
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @SuppressWarnings("serial")
+       @ResponseInfo(description="Directory listing")
+       static class FileListing extends ArrayList<FileResource> {}
+       
+       @ResponseInfo(schema="{schema:{type:'string',format:'binary'}}", 
description="Contents of file")
+       static class FileContents extends FileReader {
+               public FileContents(File file) throws FileNotFoundException {
+                       super(file);
+               }
+       }
+       
+       @ResponseInfo(description="Redirect to root page on success")
+       static class RedirectToRoot extends RedirectToServletRoot {}
+
+       @ResponseInfo(description="File action")
+       public static class Action extends LinkString {
+               public Action(String name, String uri, Object...uriArgs) {
+                       super(name, uri, uriArgs);
+               }
        }
 
-       /** File POJO */
+       @ResponseInfo(description="File or directory details")
        public class FileResource {
                private File f;
-               private URL url;
+               private String path;
 
-               /**
-                * Constructor.
-                * 
-                * @param f The file.
-                * @param url The URL of the file resource.
-                */
-               public FileResource(File f, URL url) {
+               public FileResource(File f, String path) {
                        this.f = f;
-                       this.url = url;
+                       this.path = path;
                }
 
                // Bean property getters
 
-               /**
-                * @return The URL of the file resource.
-                */
-               public URL getUrl() {
-                       return url;
+               public URI getUri() {
+                       if (f.isDirectory())
+                               return URI.create("servlet:/"+path);
+                       return URI.create("servlet:/file/"+path);
                }
 
-               /**
-                * @return The file type.
-                */
                public String getType() {
                        return (f.isDirectory() ? "dir" : "file");
                }
 
-               /**
-                * @return The file name.
-                */
                public String getName() {
                        return f.getName();
                }
 
-               /**
-                * @return The file size.
-                */
                public long getSize() {
-                       return f.length();
+                       return f.isDirectory() ? f.listFiles().length : 
f.length();
                }
 
-               /**
-                * @return The file last modified timestamp.
-                */
                @Swap(DateSwap.ISO8601DTP.class)
                public Date getLastModified() {
                        return new Date(f.lastModified());
                }
 
-               /**
-                * @return A hyperlink to view the contents of the file.
-                * @throws Exception If access is not allowed.
-                */
-               public URL getView() throws Exception {
-                       if (allowViews && f.canRead() && ! f.isDirectory())
-                               return new URL(url + "?method=VIEW");
-                       return null;
-               }
-
-               /**
-                * @return A hyperlink to download the contents of the file.
-                * @throws Exception If access is not allowed.
-                */
-               public URL getDownload() throws Exception {
-                       if (allowViews && f.canRead() && ! f.isDirectory())
-                               return new URL(url + "?method=DOWNLOAD");
-                       return null;
-               }
-
-               /**
-                * @return A hyperlink to delete the file.
-                * @throws Exception If access is not allowed.
-                */
-               public URL getDelete() throws Exception {
-                       if (allowDeletes && f.canWrite())
-                               return new URL(url + "?method=DELETE");
-                       return null;
+               public List<Action> getActions() throws Exception {
+                       List<Action> l = new ArrayList<>();
+                       if (allowViews && f.canRead() && ! f.isDirectory()) {
+                               l.add(new Action("view", getUri().toString() + 
"?method=VIEW"));
+                               l.add(new Action("download", 
getUri().toString() + "?method=DOWNLOAD"));
+                       }
+                       if (allowDeletes && f.canWrite() && ! f.isDirectory())
+                               l.add(new Action("delete", getUri().toString() 
+ "?method=DELETE"));
+                       return l;
                }
        }
 
diff --git 
a/juneau-microservice/juneau-microservice-template/my-microservice.cfg 
b/juneau-microservice/juneau-microservice-template/my-microservice.cfg
index 3c8aea5..10c9306 100755
--- a/juneau-microservice/juneau-microservice-template/my-microservice.cfg
+++ b/juneau-microservice/juneau-microservice-template/my-microservice.cfg
@@ -55,7 +55,7 @@ headerLink = http://juneau.apache.org
 footerIcon = servlet:/htdocs/images/asf.png
 footerLink = http://www.apache.org
 
-icon = $C{REST/headerIcon}
+favicon = $C{REST/headerIcon}
 header = <a href='$U{$C{REST/headerLink}}'><img src='$U{$C{REST/headerIcon}}' 
style='position:absolute;top:5;right:5;background-color:transparent;height:30px'/></a>
 footer = <a href='$U{$C{REST/footerLink}}'><img 
style='float:right;padding-right:20px;height:32px' 
src='$U{$C{REST/footerIcon}}'>
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
index fdfce10..0828de5 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
@@ -75,7 +75,7 @@ import org.apache.juneau.xmlschema.*;
                },
                stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}",
                head={
-                       "<link rel='icon' href='$U{$C{REST/icon}}'/>"
+                       "<link rel='icon' href='$U{$C{REST/favicon}}'/>"
                },
                footer="$C{REST/footer}"
        ),
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index f992917..ca8e06b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -650,6 +650,15 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
                return getPathMatch().get(name);
        }
 
+       /**
+        * Shortcut for calling <code>getPathMatch().getRemainder()</code>.
+        * 
+        * @return The path remainder value, or <jk>null</jk> if not found.
+        */
+       public String getPathRemainder() {
+               return getPathMatch().getRemainder();
+       }
+
        
//--------------------------------------------------------------------------------
        // Body methods
        
//--------------------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectServletRoot.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectToServletRoot.java
similarity index 91%
rename from 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectServletRoot.java
rename to 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectToServletRoot.java
index 71f6f2a..b1acdeb 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectServletRoot.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/RedirectToServletRoot.java
@@ -18,17 +18,17 @@ import org.apache.juneau.rest.annotation.*;
  * Convenience subclass of {@link Redirect} for redirecting a response to the 
servlet root.
  */
 @ResponseInfo(description="Redirect to servlet root")
-public class RedirectServletRoot extends Redirect {
+public class RedirectToServletRoot extends Redirect {
        
        /**
         * Reusable instance.
         */
-       public static final RedirectServletRoot INSTANCE = new 
RedirectServletRoot();
+       public static final RedirectToServletRoot INSTANCE = new 
RedirectToServletRoot();
        
        /**
         * Constructor.
         */
-       public RedirectServletRoot() {
+       public RedirectToServletRoot() {
                super("servlet:/");
        }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
index 508ef22..7c6cb4b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/RequestPathVar.java
@@ -77,6 +77,8 @@ public class RequestPathVar extends MultipartResolvingVar {
        @Override /* Parameter */
        public String resolve(VarResolverSession session, String key) {
                RestRequest req = session.getSessionObject(RestRequest.class, 
SESSION_req);
+               if ("REMAINDER".equals(key))
+                       return req.getPathRemainder();
                return req.getPath(key);
        }
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
jamesbog...@apache.org.

Reply via email to