implements simple request mapper

Sun, 17 Dec 2017 01:45:28 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2017 01:45:28 +0100
changeset 11
737ab27e37b3
parent 10
89e3e6e28b69
child 12
005d27918b57

implements simple request mapper

src/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/Constants.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/Functions.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/HttpMethod.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/LightPITModule.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/Menu.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/MenuEntry.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/ModuleManager.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/RequestMapping.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/modules/LanguageModule.java file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/resources/localization/language.properties file | annotate | diff | comparison | revisions
src/java/de/uapcore/lightpit/resources/localization/language_de.properties file | annotate | diff | comparison | revisions
web/WEB-INF/jsp/full.jsp file | annotate | diff | comparison | revisions
web/WEB-INF/view/full.jsp file | annotate | diff | comparison | revisions
web/lightpit.css file | annotate | diff | comparison | revisions
--- a/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Sun Dec 17 01:45:28 2017 +0100
@@ -29,6 +29,12 @@
 package de.uapcore.lightpit;
 
 import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiConsumer;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -43,9 +49,23 @@
 public abstract class AbstractLightPITServlet extends HttpServlet {
     
     private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
+    
+    /**
+     * Store a reference to the annotation for quicker access.
+     */
+    private Optional<LightPITModule> moduleInfo = Optional.empty();
 
+    /**
+     * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
+     */
+    private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
     
     /**
+     * Invocation mapping gathered from the {@link RequestMapping} annotations.
+     */
+    private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>();
+
+    /**
      * Gives implementing modules access to the {@link ModuleManager}.
      * @return the module manager
      */
@@ -53,25 +73,114 @@
         return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
     }
     
-    private void addPathInformation(HttpServletRequest req) {
-        final String path = req.getServletPath()+"/"+req.getPathInfo();
-        req.setAttribute(Constants.REQ_ATTR_PATH, path);
+    private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) {
+        try {
+            LOG.debug("invoke {}", method.getName());
+            method.invoke(this, req, resp);
+        } catch (ReflectiveOperationException ex) {
+            LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
+        }
+    }
+
+    @Override
+    public void init() throws ServletException {
+        moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
+        moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
+        
+        if (moduleInfo.isPresent()) {
+            Method[] methods = getClass().getDeclaredMethods();
+            for (Method method : methods) {
+                Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
+                if (mapping.isPresent()) {
+                    if (!Modifier.isPublic(method.getModifiers())) {
+                        LOG.warn("{} is annotated with {} but is not public",
+                                method.getName(), RequestMapping.class.getSimpleName()
+                        );
+                        continue;
+                    }
+                    if (Modifier.isAbstract(method.getModifiers())) {
+                        LOG.warn("{} is annotated with {} but is abstract",
+                                method.getName(), RequestMapping.class.getSimpleName()
+                        );
+                        continue;
+                    }
+                    
+                    Class<?>[] params = method.getParameterTypes();
+                    if (params.length == 2
+                            && HttpServletRequest.class.isAssignableFrom(params[0])
+                            && HttpServletResponse.class.isAssignableFrom(params[1])) {
+                        
+                        if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()).
+                                putIfAbsent(mapping.get().requestPath(),
+                                        (req, resp) -> invokeMapping(method, req, resp)) != null) {
+                            LOG.warn("{} {} has multiple mappings",
+                                    mapping.get().method(),
+                                    mapping.get().requestPath()
+                            );
+                        }
+                        
+                        LOG.info("{} {} maps to {}",
+                                mapping.get().method(),
+                                mapping.get().requestPath(),
+                                method.getName()
+                        );
+                    } else {
+                        LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required",
+                                method.getName(), RequestMapping.class.getSimpleName()
+                        );
+                    }
+                }
+            }
+        }
+        
+        LOG.trace("{} initialized", getServletName());
+    }
+
+    @Override
+    public void destroy() {
+        mappings.clear();
+        LOG.trace("{} destroyed", getServletName());
+    }
+    
+    
+    /**
+     * Sets several requests attributes, that can be used by the JSP.
+     * 
+     * @param req the servlet request object
+     * @see Constants#REQ_ATTR_PATH
+     * @see Constants#REQ_ATTR_MODULE_CLASSNAME
+     * @see Constants#REQ_ATTR_MODULE_INFO
+     */
+    private void setGenericRequestAttributes(HttpServletRequest req) {
+        req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));
+
+        req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());
+
+        moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
     }
     
     private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
             throws IOException, ServletException {
         
-        addPathInformation(req);
-        
-        final ModuleManager mm = getModuleManager();
-        req.setAttribute(Constants.REQ_ATTR_MENU, mm.getMainMenu());
-        
+        setGenericRequestAttributes(req);
+        req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
         req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
     }
     
+    private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) {
+        return Optional.ofNullable(mappings.get(method)).map(
+                (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
+        );
+    }
+    
     @Override
     protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
+        
+        findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp));
+        
+        // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction)
+        // TODO: we should call a default handler, if no specific mapping could be found
         forwardToFullView(req, resp);
     }
 
@@ -79,6 +188,8 @@
     protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
         
+        findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp));
+        
         forwardToFullView(req, resp);
     }
 }
--- a/src/java/de/uapcore/lightpit/Constants.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/Constants.java	Sun Dec 17 01:45:28 2017 +0100
@@ -28,16 +28,33 @@
  */
 package de.uapcore.lightpit;
 
-import static de.uapcore.lightpit.Functions.fullyQualifiedName;
+import static de.uapcore.lightpit.Functions.fqn;
 
 /**
  * Contains all constants used by the this application.
  */
 public final class Constants {
-    public static final String JSP_PATH_PREFIX = "/WEB-INF/view/";
+    public static final String JSP_PATH_PREFIX = "/WEB-INF/jsp/";
+    
+    /**
+     * Key for the request attribute containing the class name of the currently dispatching module.
+     */
+    public static final String REQ_ATTR_MODULE_CLASSNAME = fqn(AbstractLightPITServlet.class, "moduleClassname");
     
-    public static final String REQ_ATTR_MENU = fullyQualifiedName(AbstractLightPITServlet.class, "mainMenu");
-    public static final String REQ_ATTR_PATH = fullyQualifiedName(AbstractLightPITServlet.class, "path");
+    /**
+     * Key for the request attribute containing the {@link LightPITModule} information of the currently dispatching module.
+     */
+    public static final String REQ_ATTR_MODULE_INFO = fqn(AbstractLightPITServlet.class, "moduleInfo");
+    
+    /**
+     * Key for the request attribute containing the menu list.
+     */
+    public static final String REQ_ATTR_MENU = fqn(AbstractLightPITServlet.class, "mainMenu");
+    
+    /**
+     * Key for the request attribute containing the full path information (servlet path + path info).
+     */
+    public static final String REQ_ATTR_PATH = fqn(AbstractLightPITServlet.class, "path");
     
     /**
      * This class is not instantiatable.
--- a/src/java/de/uapcore/lightpit/Functions.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/Functions.java	Sun Dec 17 01:45:28 2017 +0100
@@ -28,6 +28,8 @@
  */
 package de.uapcore.lightpit;
 
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,12 +44,27 @@
         return Constants.JSP_PATH_PREFIX + filename;
     }
     
-    public static String fullyQualifiedName(String base, String name) {
+    public static String fqn(String base, String name) {
         return base+"."+name;
     }
     
-    public static String fullyQualifiedName(Class clazz, String name) {
-        return fullyQualifiedName(clazz.getName(), name);
+    public static String fqn(Class clazz, String name) {
+        return fqn(clazz.getName(), name);
+    }
+    
+    public static String fullPath(LightPITModule module, RequestMapping mapping) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(module.modulePath());
+        sb.append('/');
+        if (!mapping.requestPath().isEmpty()) {
+            sb.append(mapping.requestPath().isEmpty());
+            sb.append('/');
+        }
+        return sb.toString();
+    }
+    
+    public static String fullPath(HttpServletRequest req) {
+        return req.getServletPath() + Optional.ofNullable(req.getPathInfo()).orElse("");
     }
     
     /**
@@ -60,15 +77,15 @@
      * @return the module path
      */
     public static String modulePathOf(Class<? extends AbstractLightPITServlet> clazz) {
-        LightPITModule moduleInfo = clazz.getAnnotation(LightPITModule.class);
-        if (moduleInfo == null) {
+        Optional<LightPITModule> moduleInfo = Optional.ofNullable(clazz.getAnnotation(LightPITModule.class));
+        if (moduleInfo.isPresent()) {
+            return moduleInfo.get().modulePath();
+        } else {
             LOG.warn(
                     "{} is a LightPIT Servlet but is missing the module annotation.",
                     clazz.getName()
             );
             return "/error/404.html";
-        } else {
-            return moduleInfo.modulePath();
         }
     }
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/de/uapcore/lightpit/HttpMethod.java	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,34 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * 
+ * Copyright 2017 Mike Becker. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ */
+package de.uapcore.lightpit;
+
+
+public enum HttpMethod {
+    GET, POST, PUT, DELETE, TRACE, HEAD, OPTIONS
+}
--- a/src/java/de/uapcore/lightpit/LightPITModule.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/LightPITModule.java	Sun Dec 17 01:45:28 2017 +0100
@@ -71,7 +71,60 @@
     
     /**
      * Returns the properties key for the menu label.
-     * @return the properties key relative to the base name
+     * @return the properties key
      */
     String menuKey() default "menuLabel";
+    
+    /**
+     * Returns the properties key for the page title.
+     * 
+     * By default this is the same as the menu label.
+     * 
+     * @return the properties key
+     */
+    String titleKey() default "menuLabel";
+    
+    /**
+     * Class representing the annotation.
+     * This is necessary, because the EL resolver cannot deal with
+     * annotation objects.
+     * 
+     * Note, that only the properties which are interesting for the JSP pages
+     * are proxied by this object.
+     */
+    public static class ELProxy {
+        private final String bundleBaseName, modulePath, menuKey, titleKey;
+        
+        public static ELProxy convert(LightPITModule annotation) {
+            return new ELProxy(
+                    annotation.bundleBaseName(),
+                    annotation.modulePath(),
+                    annotation.menuKey(),
+                    annotation.titleKey()
+            );
+        }
+
+        private ELProxy(String bundleBaseName, String modulePath, String menuKey, String titleKey) {
+            this.bundleBaseName = bundleBaseName;
+            this.modulePath = modulePath;
+            this.menuKey = menuKey;
+            this.titleKey = titleKey;
+        }
+
+        public String getBundleBaseName() {
+            return bundleBaseName;
+        }
+
+        public String getMenuKey() {
+            return menuKey;
+        }
+
+        public String getModulePath() {
+            return modulePath;
+        }
+
+        public String getTitleKey() {
+            return titleKey;
+        }
+    }
 }
--- a/src/java/de/uapcore/lightpit/Menu.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/Menu.java	Sun Dec 17 01:45:28 2017 +0100
@@ -44,12 +44,27 @@
     private final List<MenuEntry> entries = new ArrayList<>();
     private final List<MenuEntry> immutableEntries = Collections.unmodifiableList(entries);
     
+    /**
+     * Class name of the module for which this menu is built.
+     */
+    private String moduleClassName;
+    
+    
     public Menu() {
         super();
     }
     
-    public Menu(ResourceKey resourceKey, String pathName) {
+    public Menu(String moduleClassName, ResourceKey resourceKey, String pathName) {
         super(resourceKey, pathName);
+        this.moduleClassName = moduleClassName;
+    }
+    
+    public void setModuleClassName(String moduleClassName) {
+        this.moduleClassName = moduleClassName;
+    }
+
+    public String getModuleClassName() {
+        return moduleClassName;
     }
 
     /**
--- a/src/java/de/uapcore/lightpit/MenuEntry.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/MenuEntry.java	Sun Dec 17 01:45:28 2017 +0100
@@ -37,7 +37,14 @@
  */
 public class MenuEntry {
     
+    /**
+     * Resource key for the menu label.
+     */
     private ResourceKey resourceKey;
+    
+    /**
+     * Path name of the module, linked by this menu entry.
+     */
     private String pathName;
 
     public MenuEntry() {
@@ -48,38 +55,18 @@
         this.pathName = pathName;
     }
 
-    /**
-     * Sets the resource key, which is used to look up the menu label.
-     * 
-     * @param resourceKey the key for the resource bundle
-     */
     public void setResourceKey(ResourceKey resourceKey) {
         this.resourceKey = resourceKey;
     }
 
-    /**
-     * Retrieves the resource key.
-     * 
-     * @return the key for the resource bundle
-     */
     public ResourceKey getResourceKey() {
         return resourceKey;
     }
 
-    /**
-     * Sets the path name of the web page accessed via this menu entry.
-     * 
-     * @param pathName path name relative to the context path
-     */
     public void setPathName(String pathName) {
         this.pathName = pathName;
     }
 
-    /**
-     * Retrieves the path name of the web page accessed via this menu entry.
-     * 
-     * @return path name relative to the context path
-     */
     public String getPathName() {
         return pathName;
     }
--- a/src/java/de/uapcore/lightpit/ModuleManager.java	Sat Dec 16 20:19:28 2017 +0100
+++ b/src/java/de/uapcore/lightpit/ModuleManager.java	Sun Dec 17 01:45:28 2017 +0100
@@ -107,8 +107,9 @@
         }        
     }
     
-    private void addModuleToMenu(LightPITModule moduleInfo) {
+    private void addModuleToMenu(String moduleClassName, LightPITModule moduleInfo) {
         final Menu menu = new Menu(
+                moduleClassName,
                 new ResourceKey(moduleInfo.bundleBaseName(), moduleInfo.menuKey()),
                 moduleInfo.modulePath()
         );
@@ -118,7 +119,9 @@
     private void handleServletRegistration(String name, Registration reg) {
         final Optional<LightPITModule> moduleInfo = getModuleInfo(reg);
         if (moduleInfo.isPresent()) {
-            addModuleToMenu(moduleInfo.get());
+            
+            // TODO: remove this call and add the module to some dependency resolver, first
+            addModuleToMenu(reg.getClassName(), moduleInfo.get());
             
             LOG.info("Module detected: {}", name);
         } else {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/de/uapcore/lightpit/RequestMapping.java	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,76 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * 
+ * Copyright 2017 Mike Becker. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ */
+package de.uapcore.lightpit;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Maps requests to methods.
+ * 
+ * This annotation is used to annotate methods within classes which
+ * override {@link AbstractLightPITServlet}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RequestMapping {
+    
+    /**
+     * Specifies the HTTP method.
+     * 
+     * @return the HTTP method handled by the annotated Java method
+     */
+    HttpMethod method();
+
+    /**
+     * Specifies the request path relative to the module path.
+     * 
+     * If a menu key is specified, this is also the path, which is linked
+     * by the menu entry.
+     * 
+     * The path must be specified <em>without</em> a trailing slash.
+     * 
+     * @return the request path the annotated method should handle
+     */
+    String requestPath() default "";
+    
+    /**
+     * Returns the properties key for the (sub) menu label.
+     * 
+     * This should only be used for {@link HttpMethod#GET} requests.
+     * 
+     * @return the properties key
+     */
+    String menuKey() default "";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/de/uapcore/lightpit/modules/LanguageModule.java	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,54 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ * 
+ * Copyright 2017 Mike Becker. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * 
+ */
+package de.uapcore.lightpit.modules;
+
+import de.uapcore.lightpit.LightPITModule;
+import de.uapcore.lightpit.AbstractLightPITServlet;
+import de.uapcore.lightpit.HttpMethod;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import de.uapcore.lightpit.RequestMapping;
+
+
+@LightPITModule(
+        bundleBaseName = "de.uapcore.lightpit.resources.localization.language",
+        modulePath = "language"
+)
+@WebServlet(
+        name = "LanguageModule",
+        urlPatterns = "/language/*"
+)
+public final class LanguageModule extends AbstractLightPITServlet {
+    
+    @RequestMapping(method = HttpMethod.GET)
+    public void handle(HttpServletRequest req, HttpServletResponse resp) {
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/de/uapcore/lightpit/resources/localization/language.properties	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,24 @@
+# Copyright 2017 Mike Becker. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+
+menuLabel = Languages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java/de/uapcore/lightpit/resources/localization/language_de.properties	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,24 @@
+# Copyright 2017 Mike Becker. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+
+menuLabel = Sprache
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/WEB-INF/jsp/full.jsp	Sun Dec 17 01:45:28 2017 +0100
@@ -0,0 +1,78 @@
+<%-- 
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+Copyright 2017 Mike Becker. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+--%>
+<%@page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@page import="de.uapcore.lightpit.Constants" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+
+<%-- Define an alias for the main menu --%>
+<c:set scope="page" var="mainMenu" value="${requestScope[Constants.REQ_ATTR_MENU]}"/>
+
+<%-- Define an alias for the module info --%>
+<c:set scope="page" var="moduleInfo" value="${requestScope[Constants.REQ_ATTR_MODULE_INFO]}"/>
+
+<!DOCTYPE html>
+<html>
+    <head>
+        <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
+        <title>LightPIT -
+            <fmt:bundle basename="${moduleInfo.bundleBaseName}">
+                <fmt:message key="${moduleInfo.titleKey}" />
+            </fmt:bundle>
+        </title>
+        <meta charset="UTF-8">
+        <link rel="stylesheet" href="lightpit.css" type="text/css">
+    </head>
+    <body>
+        <div id="mainMenu">
+            <c:forEach var="menu" items="${mainMenu}">
+                <div class="menuEntry"
+                     <c:if test="${requestScope[Constants.REQ_ATTR_MODULE_CLASSNAME] eq menu.moduleClassName}">
+                         data-active
+                     </c:if>
+                >
+                    <a href="${menu.pathName}">
+                    <fmt:bundle basename="${menu.resourceKey.bundle}">
+                        <fmt:message key="${menu.resourceKey.key}" />
+                    </fmt:bundle>
+                    </a>
+                </div>
+            </c:forEach>
+            
+        </div>
+        <div id="subMenu">
+            
+        </div>
+        <div id="content-area">
+            <%-- Resource keys should be rooted in the specific bundle for this module. --%>
+            <fmt:bundle basename="${moduleInfo.bundleBaseName}">
+                TODO: load fragment
+            </fmt:bundle>
+        </div>
+    </body>
+</html>
--- a/web/WEB-INF/view/full.jsp	Sat Dec 16 20:19:28 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-<%-- 
-DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
-
-Copyright 2017 Mike Becker. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
---%>
-<%@page contentType="text/html" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
-<%@page import="de.uapcore.lightpit.Constants" %>
-<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-
-<%-- Define an alias for the main menu --%>
-<c:set scope="page" var="mainMenu" value="${requestScope[Constants.REQ_ATTR_MENU]}"/>
-
-<!DOCTYPE html>
-<html>
-    <head>
-        <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
-        <title>LightPIT - TODO: current menu</title>
-        <meta charset="UTF-8">
-        <link rel="stylesheet" href="lightpit.css" type="text/css">
-    </head>
-    <body>
-        
-        <div id="mainMenu">
-            <c:forEach var="menuEntry" items="${mainMenu}">
-                <div class="menuEntry"
-                     <c:if test="${fn:contains(requestScope[Constants.REQ_ATTR_PATH], menuEntry.pathName)}">
-                         data-active
-                     </c:if>
-                >
-                    <a href="${menuEntry.pathName}">
-                    <fmt:bundle basename="${menuEntry.resourceKey.bundle}">
-                        <fmt:message key="${menuEntry.resourceKey.key}" />
-                    </fmt:bundle>
-                    </a>
-                </div>
-            </c:forEach>
-            
-        </div>
-        <div id="subMenu">
-            
-        </div>
-        <div id="content-area">
-        TODO: content fragment
-        </div>
-    </body>
-</html>
--- a/web/lightpit.css	Sat Dec 16 20:19:28 2017 +0100
+++ b/web/lightpit.css	Sun Dec 17 01:45:28 2017 +0100
@@ -43,7 +43,7 @@
 }
 
 a {
-    color: #3030f8;
+    color: #3060f8;
     text-decoration: none;
 }
 
@@ -60,7 +60,7 @@
 #subMenu {
     background: #f7f7ff;
 
-    border-image: linear-gradient(to right, #606060, rgba(60,60,60,.1));
+    border-image: linear-gradient(to right, #606060, rgba(60,60,60,.25));
     border-image-slice: 1;
     border-top-style: solid;
     border-top-width: 1pt;

mercurial