From e3639ef805f1e525415c43cbda80ed71cc43a70c Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Sun, 9 Jan 2022 15:01:28 +0100 Subject: feature(core): add synch. and asynch. GUI builder implementation that use Spring MVC architecture --- build_reporting/pom.xml | 10 +- eaaf_core/checks/spotbugs-exclude.xml | 5 + .../impl/gui/builder/AsynchGuiFormBuilderImpl.java | 72 ++++++++ .../gui/builder/SpringMvcGuiFormBuilderImpl.java | 57 ++++++ .../AsynchSpringMvcGuiBuilderIntercepter.java | 63 +++++++ .../core/impl/gui/utils/MvcGuiRenderUtils.java | 199 +++++++++++++++++++++ 6 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/AsynchGuiFormBuilderImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/SpringMvcGuiFormBuilderImpl.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/interceptor/AsynchSpringMvcGuiBuilderIntercepter.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/utils/MvcGuiRenderUtils.java diff --git a/build_reporting/pom.xml b/build_reporting/pom.xml index 649e650a..9562fc72 100644 --- a/build_reporting/pom.xml +++ b/build_reporting/pom.xml @@ -48,7 +48,15 @@ at.gv.egiz.eaaf eaaf-springboot-utils - + + + + + org.springframework + spring-webmvc + provided + + diff --git a/eaaf_core/checks/spotbugs-exclude.xml b/eaaf_core/checks/spotbugs-exclude.xml index 70f27b81..b12ecc01 100644 --- a/eaaf_core/checks/spotbugs-exclude.xml +++ b/eaaf_core/checks/spotbugs-exclude.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/AsynchGuiFormBuilderImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/AsynchGuiFormBuilderImpl.java new file mode 100644 index 00000000..6ed50955 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/AsynchGuiFormBuilderImpl.java @@ -0,0 +1,72 @@ +package at.gv.egiz.eaaf.core.impl.gui.builder; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ViewResolver; + +import at.gv.egiz.eaaf.core.api.gui.IGuiBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; +import at.gv.egiz.eaaf.core.impl.gui.utils.MvcGuiRenderUtils; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AsynchGuiFormBuilderImpl implements ISpringMvcGuiFormBuilder { + + private @Autowired(required = false) ViewResolver[] viewResolvers; + private @Autowired(required = false) LocaleResolver localeResolver; + + @Override + public void build(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IGuiBuilderConfiguration config, final String loggerName) throws GuiBuildException { + build(httpReq, httpResp, config, null, loggerName); + + } + + @Override + public void build(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IGuiBuilderConfiguration config, final String contentType, final String loggerName) + throws GuiBuildException { + if (config.isWriteAsynch()) { + log.debug("{} injects GUI model: {} for asynch. rendering", loggerName, config.getViewName()); + MvcGuiRenderUtils.setMvcForAsynchRendering(httpReq, config); + + } else { + renderGuiDirectly(httpReq, httpResp, config, loggerName); + + } + + } + + @Override + public String evaluateResponseContentType(HttpServletRequest httpReq, IGuiBuilderConfiguration config, + String loggerName) throws GuiBuildException { + try { + return MvcGuiRenderUtils.evaluateResponseContentType(httpReq, config, viewResolvers, localeResolver); + + } catch (final Exception e) { + log.info("Can NOT evaluate contentType for response GUI: {}", loggerName, e); + throw new GuiBuildException(e.getMessage(), e); + + } + } + + private void renderGuiDirectly(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IGuiBuilderConfiguration config, final String loggerName) throws GuiBuildException { + try { + MvcGuiRenderUtils.render(MvcGuiRenderUtils.prepareSpringGuiModel(config), + httpReq, httpResp, viewResolvers, localeResolver); + + } catch (final Exception e) { + log.info("Can NOT generate GUI for: {}", loggerName, e); + throw new GuiBuildException(e.getMessage(), e); + + } + + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/SpringMvcGuiFormBuilderImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/SpringMvcGuiFormBuilderImpl.java new file mode 100644 index 00000000..9fae07e2 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/builder/SpringMvcGuiFormBuilderImpl.java @@ -0,0 +1,57 @@ +package at.gv.egiz.eaaf.core.impl.gui.builder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ViewResolver; + +import at.gv.egiz.eaaf.core.api.gui.IGuiBuilderConfiguration; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; +import at.gv.egiz.eaaf.core.impl.gui.utils.MvcGuiRenderUtils; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SpringMvcGuiFormBuilderImpl implements ISpringMvcGuiFormBuilder { + + private @Autowired(required = false) ViewResolver[] viewResolvers; + private @Autowired(required = false) LocaleResolver localeResolver; + + @Override + public void build(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IGuiBuilderConfiguration config, final String loggerName) throws GuiBuildException { + build(httpReq, httpResp, config, null, loggerName); + + } + + @Override + public void build(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final IGuiBuilderConfiguration config, final String contentType, final String loggerName) + throws GuiBuildException { + try { + MvcGuiRenderUtils.render(MvcGuiRenderUtils.prepareSpringGuiModel(config), + httpReq, httpResp, viewResolvers, localeResolver); + + } catch (final Exception e) { + log.info("Can NOT generate GUI for: {}", loggerName, e); + throw new GuiBuildException(e.getMessage(), e); + + } + } + + @Override + public String evaluateResponseContentType(HttpServletRequest httpReq, IGuiBuilderConfiguration config, + String loggerName) throws GuiBuildException { + try { + return MvcGuiRenderUtils.evaluateResponseContentType(httpReq, config, viewResolvers, localeResolver); + + } catch (final Exception e) { + log.info("Can NOT evaluate contentType for response GUI: {}", loggerName, e); + throw new GuiBuildException(e.getMessage(), e); + + } + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/interceptor/AsynchSpringMvcGuiBuilderIntercepter.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/interceptor/AsynchSpringMvcGuiBuilderIntercepter.java new file mode 100644 index 00000000..708e3b84 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/interceptor/AsynchSpringMvcGuiBuilderIntercepter.java @@ -0,0 +1,63 @@ +package at.gv.egiz.eaaf.core.impl.gui.interceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.ViewResolver; + +import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; +import at.gv.egiz.eaaf.core.impl.gui.utils.MvcGuiRenderUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * Intercepter to render GUI in response processing. + * + * @author tlenz + * + */ +@Slf4j +public class AsynchSpringMvcGuiBuilderIntercepter implements HandlerInterceptor { + + private @Autowired(required = false) ViewResolver[] viewResolvers; + private @Autowired(required = false) LocaleResolver localeResolver; + + /* + * (non-Javadoc) + * + * @see + * org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet. + * http. HttpServletRequest, javax.servlet.http.HttpServletResponse, + * java.lang.Object, org.springframework.web.servlet.ModelAndView) + */ + @Override + public void postHandle(final HttpServletRequest httpReq, final HttpServletResponse httpResp, + final Object handler, final ModelAndView modelAndView) throws Exception { + + final ModelAndView model = MvcGuiRenderUtils.getMvcForAsynchRendering(httpReq); + if (model != null) { + log.debug("Find GUI model: {} Starting model rendering ... ", model.getViewName()); + renderGui(model, httpReq, httpResp); + + } else { + log.trace("No GUI model skipping asynch model rendering"); + + } + + } + + private void renderGui(final ModelAndView mav, + final HttpServletRequest httpReq, final HttpServletResponse httpResp) throws GuiBuildException { + try { + MvcGuiRenderUtils.render(mav, httpReq, httpResp, viewResolvers, localeResolver); + + } catch (final Exception e) { + log.info("Can NOT generate GUI for illustration", e); + throw new GuiBuildException(e.getMessage(), e); + + } + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/utils/MvcGuiRenderUtils.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/utils/MvcGuiRenderUtils.java new file mode 100644 index 00000000..5a3f14cb --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/gui/utils/MvcGuiRenderUtils.java @@ -0,0 +1,199 @@ +package at.gv.egiz.eaaf.core.impl.gui.utils; + +import java.util.Locale; + +import javax.annotation.Nullable; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.ViewResolver; + +import at.gv.egiz.eaaf.core.api.gui.IGuiBuilderConfiguration; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +/** + * Utils GUI generation by using Spring MVC. + + * @author tlenz + * + */ +@Slf4j +public class MvcGuiRenderUtils { + + private static final String REQ_ATTR_MODEL_HOLDER = "GUI_MODEL_ASYNCH_RENDERING"; + + /** + * Build Spring MVC model from GUI configuration. + * + * @param config GUI configuration + * @return MVC model + */ + @NonNull + public static ModelAndView prepareSpringGuiModel(@NonNull final IGuiBuilderConfiguration config) { + final ModelAndView mav = new ModelAndView(config.getViewName()); + if (config.getViewParameters() != null) { + mav.addAllObjects(config.getViewParameters()); + + } + return mav; + + } + + + /** + * Inject GUI-builder configuration into http request to render it asynchronous in Spring intercepter. + * + * @param request Current HttpServletRequest + * @param config Configuration for GUI rendering + */ + public static void setMvcForAsynchRendering(@NonNull final HttpServletRequest request, + @NonNull final IGuiBuilderConfiguration config) { + request.setAttribute(REQ_ATTR_MODEL_HOLDER, prepareSpringGuiModel(config)); + + } + + /** + * Get GUI model for asynchronous rendering. + * + * @param request Current HttpServletRequest + * @return GUI model, or null of model was set + */ + @Nullable + public static ModelAndView getMvcForAsynchRendering(@NonNull final HttpServletRequest request) { + return (ModelAndView) request.getAttribute(REQ_ATTR_MODEL_HOLDER); + + } + + /** + * Analyze GUI template and HTTP request to evaluate ContentType of HTTP response generated by this builder. + * + * @param httpReq Current HttpServletRequest + * @param config GUI configuration + * @param viewResolvers Spring based view-resolvers + * @param localeResolver Spring based locale-resolvers + * @return Response content-type, or null if evaluation has no result + * @throws Exception In case of a GUI rendering error + */ + @Nullable + public static String evaluateResponseContentType(HttpServletRequest httpReq, + IGuiBuilderConfiguration config, @Nullable final ViewResolver[] viewResolvers, + @Nullable final LocaleResolver localeResolver) throws Exception { + return buildViewFromModel(viewResolvers, + getLocaleByRequest(localeResolver, httpReq), + prepareSpringGuiModel(config)).getContentType(); + + } + + /** + * Render a GUI by using Spring based ModelAndView (MVC) components. + * + * @param mv GUI model + * @param request Current HttpServletRequest + * @param response Current HttpServletResponse + * @param viewResolvers Spring based view-resolvers + * @param localeResolver Spring based locale-resolvers + * @throws Exception In case of a GUI rendering error + */ + public static void render(@NonNull final ModelAndView mv, @NonNull final HttpServletRequest request, + @NonNull final HttpServletResponse response, @Nullable final ViewResolver[] viewResolvers, + @Nullable final LocaleResolver localeResolver) throws Exception { + Assert.notNull(request, "http request object"); + Assert.notNull(response, "http response object"); + Assert.notNull(mv, "Model and view"); + + // Determine locale for request and apply it to the response. + final Locale locale = getLocaleByRequest(localeResolver, request); + response.setLocale(locale); + + // Determine view for response + View view = buildViewFromModel(viewResolvers, locale, mv); + + final HttpStatus modelStatus = mv.getStatus(); + if (modelStatus != null) { + response.setStatus(modelStatus.value()); + + } else { + log.trace("HttpStatus is 'null' Set {} as default", HttpServletResponse.SC_OK); + response.setStatus(HttpServletResponse.SC_OK); + + } + + // Delegate to the View object for rendering. + if (log.isTraceEnabled()) { + log.trace("Rendering view [{}] ", view); + } + + try { + view.render(mv.getModelMap(), request, response); + + } catch (final Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Error rendering view [{}]", view, null, ex); + } + + throw ex; + + } + + } + + private static Locale getLocaleByRequest(LocaleResolver localeResolver, @NonNull HttpServletRequest request) { + return localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale(); + + } + + private static View buildViewFromModel(ViewResolver[] viewResolvers, Locale locale, + @NonNull ModelAndView mv) throws Exception { + + final String viewName = mv.getViewName(); + View view; + if (viewName != null) { + // We need to resolve the view name. + view = resolveViewName(viewResolvers, viewName, locale); + + if (view == null) { + throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' "); + } + + } else { + // No need to lookup: the ModelAndView object contains the actual View object. + view = mv.getView(); + if (view == null) { + throw new ServletException( + "ModelAndView [" + mv + "] neither contains a view name nor a " + "View object "); + + } + } + + return view; + + } + + + private static View resolveViewName(@Nullable final ViewResolver[] viewResolvers, + final String viewName, final Locale locale) throws Exception { + if (viewResolvers != null) { + for (final ViewResolver viewResolver : viewResolvers) { + final View view = viewResolver.resolveViewName(viewName, locale); + if (view != null) { + return view; + } + + } + } + + return null; + } + + private MvcGuiRenderUtils() { + // private constructor for class with only static methods + } + +} -- cgit v1.2.3