diff --git a/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxEnabledView.java b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxEnabledView.java new file mode 100644 index 0000000..0ca8b50 --- /dev/null +++ b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxEnabledView.java @@ -0,0 +1,82 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed 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.thymeleaf.spring6.view; + +import org.springframework.web.servlet.View; +import org.springframework.webflow.context.servlet.AjaxHandler; + + +// Copied from thymeleaf-spring5. +// Temporarily here until available in thymeleaf-spring6. + + +/** + *

+ * Interface defining getter and setter methods for an + * {@code ajaxHandler} property in Views, so that they can + * be used in Spring AJAX environments. + *

+ * + * @author Daniel Fernández + * + * @since 3.0.3 + * + */ +public interface AjaxEnabledView extends View { + + + + /** + *

+ * Return the AJAX handler (from Spring Javascript) used + * to determine whether a request is an AJAX request or not. + *

+ *

+ * Views implementing this interface should be used with an instance of + * {@link AjaxThymeleafViewResolver} or any of its subclasses, + * so that {@link #setAjaxHandler(AjaxHandler)} can be called by + * the resolver when resolving the view, setting the default + * AJAX handler being used. + *

+ * + * @return the AJAX handler. + */ + public AjaxHandler getAjaxHandler(); + + + /** + *

+ * Sets the AJAX handler (from Spring Javascript) used + * to determine whether a request is an AJAX request or not. + *

+ *

+ * Views implementing this interface should be used with an instance of + * {@link AjaxThymeleafViewResolver} or any of its subclasses, + * so that this method can be called by + * the resolver when resolving the view, setting the default + * AJAX handler being used. + *

+ * + * @param ajaxHandler the AJAX handler. + */ + public void setAjaxHandler(final AjaxHandler ajaxHandler); + + +} \ No newline at end of file diff --git a/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafView.java b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafView.java new file mode 100644 index 0000000..2016db2 --- /dev/null +++ b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafView.java @@ -0,0 +1,144 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed 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.thymeleaf.spring6.view; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.webflow.context.servlet.AjaxHandler; +import org.thymeleaf.exceptions.ConfigurationException; + + +// Copied from thymeleaf-spring5. +// Temporarily here until available in thymeleaf-spring6. + + +/** + *

+ * Subclass of {@link ThymeleafView} adding compatibility with AJAX events in + * Spring JavaScript (part of Spring WebFlow). This allows this View implementation + * to be able to return only fragments of the page. + *

+ *

+ * These rendering of fragments is used, for example, in Spring WebFlow's <render> + * instructions (though not only). + *

+ *

+ * This view searches for a comma-separated list of markup selectors in a request + * parameter called {@code fragments}. + *

+ * + * @author Daniel Fernández + * + * @since 3.0.3 + * + */ +public class AjaxThymeleafView extends ThymeleafView implements AjaxEnabledView { + + private static final Logger vlogger = LoggerFactory.getLogger(AjaxThymeleafView.class); + + private static final String FRAGMENTS_PARAM = "fragments"; + + + private AjaxHandler ajaxHandler = null; + + + + public AjaxThymeleafView() { + super(); + } + + + + public AjaxHandler getAjaxHandler() { + return this.ajaxHandler; + } + + + public void setAjaxHandler(final AjaxHandler ajaxHandler) { + this.ajaxHandler = ajaxHandler; + } + + + + + @Override + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + + + final AjaxHandler templateAjaxHandler = getAjaxHandler(); + + if (templateAjaxHandler == null) { + throw new ConfigurationException("[THYMELEAF] AJAX Handler set into " + + AjaxThymeleafView.class.getSimpleName() + " instance for template " + + getTemplateName() + " is null."); + } + + if (templateAjaxHandler.isAjaxRequest(request, response)) { + + final Set fragmentsToRender = getRenderFragments(model, request, response); + if (fragmentsToRender == null || fragmentsToRender.size() == 0) { + vlogger.warn("[THYMELEAF] An Ajax request was detected, but no fragments were specified to be re-rendered. " + + "Falling back to full page render. This can cause unpredictable results when processing " + + "the ajax response on the client."); + super.render(model, request, response); + return; + } + + super.renderFragment(fragmentsToRender, model, request, response); + + } else { + + super.render(model, request, response); + + } + + } + + + + + @SuppressWarnings({ "rawtypes", "unused" }) + protected Set getRenderFragments( + final Map model, final HttpServletRequest request, final HttpServletResponse response) { + final String fragmentsParam = request.getParameter(FRAGMENTS_PARAM); + final String[] renderFragments = StringUtils.commaDelimitedListToStringArray(fragmentsParam); + if (renderFragments.length == 0) { + return null; + } + if (renderFragments.length == 1) { + return Collections.singleton(renderFragments[0]); + } + return new HashSet(Arrays.asList(renderFragments)); + } + + + +} \ No newline at end of file diff --git a/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafViewResolver.java b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafViewResolver.java new file mode 100644 index 0000000..c654551 --- /dev/null +++ b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/AjaxThymeleafViewResolver.java @@ -0,0 +1,192 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed 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.thymeleaf.spring6.view; + +import java.io.IOException; +import java.util.Locale; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.thymeleaf.exceptions.ConfigurationException; + +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.RedirectView; +import org.springframework.webflow.context.servlet.AjaxHandler; +import org.springframework.webflow.context.servlet.DefaultAjaxHandler; + + +// Copied from thymeleaf-spring5. +// Temporarily here until available in thymeleaf-spring6. + + +/** + *

+ * Subclass of {@link ThymeleafViewResolver} adding compatibility with AJAX-based events + * (redirects) in Spring WebFlow. + *

+ *

+ * Important: Spring WebFlow dependencies are OPTIONAL. If you are not using WebFlow + * in your application, then you should be using {@link ThymeleafViewResolver} directly. + *

+ * + * @author Daniel Fernández + * + * @since 3.0.3 + * + */ +public class AjaxThymeleafViewResolver + extends ThymeleafViewResolver { + + + private static final Logger vrlogger = LoggerFactory.getLogger(AjaxThymeleafViewResolver.class); + + + private AjaxHandler ajaxHandler = new DefaultAjaxHandler(); + + + + public AjaxThymeleafViewResolver() { + super(); + } + + + /** + *

+ * Return the AJAX handler (from Spring Javascript) used + * to determine whether a request is an AJAX request or not + * in views resolved by this resolver. + *

+ *

+ * An instance of {@link DefaultAjaxHandler} is set by default. + *

+ * + * @return the AJAX handler. + */ + public AjaxHandler getAjaxHandler() { + return this.ajaxHandler; + } + + + /** + *

+ * Sets the AJAX handler (from Spring Javascript) used + * to determine whether a request is an AJAX request or not + * in views resolved by this resolver. + *

+ *

+ * An instance of {@link DefaultAjaxHandler} is set by default. + *

+ * + * @param ajaxHandler the AJAX handler. + */ + public void setAjaxHandler(final AjaxHandler ajaxHandler) { + this.ajaxHandler = ajaxHandler; + } + + + + + @Override + protected View createView(final String viewName, final Locale locale) throws Exception { + + if (!canHandle(viewName, locale)) { + return null; + } + + if (this.ajaxHandler == null) { + throw new ConfigurationException("[THYMELEAF] AJAX Handler set into " + + AjaxThymeleafViewResolver.class.getSimpleName() + " instance is null."); + } + + // Check for special "redirect:" prefix. + if (viewName.startsWith(REDIRECT_URL_PREFIX)) { + vrlogger.trace( + "[THYMELEAF] View {} is a redirect. An AJAX-enabled RedirectView implementation will " + + "be handling the request.", viewName); + final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); + return new AjaxRedirectView( + this.ajaxHandler, redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); + } + + final View view = super.createView(viewName, locale); + + if (view instanceof AjaxEnabledView) { + // Set the AJAX handler into view, if it is an AjaxThymeleafView. + + final AjaxEnabledView ajaxEnabledView = (AjaxEnabledView) view; + + if (ajaxEnabledView.getAjaxHandler() == null && getAjaxHandler() != null) { + ajaxEnabledView.setAjaxHandler(getAjaxHandler()); + } + + } + + return view; + + } + + + + + + private static class AjaxRedirectView extends RedirectView { + + private static final Logger vlogger = LoggerFactory.getLogger(AjaxRedirectView.class); + + private AjaxHandler ajaxHandler = new DefaultAjaxHandler(); + + AjaxRedirectView(final AjaxHandler ajaxHandler, final String redirectUrl, + final boolean redirectContextRelative, final boolean redirectHttp10Compatible) { + super(redirectUrl, redirectContextRelative, redirectHttp10Compatible); + this.ajaxHandler = ajaxHandler; + } + + @Override + protected void sendRedirect(final HttpServletRequest request, final HttpServletResponse response, + final String targetUrl, final boolean http10Compatible) + throws IOException { + + if (this.ajaxHandler == null) { + throw new ConfigurationException("[THYMELEAF] AJAX Handler set into " + + AjaxThymeleafViewResolver.class.getSimpleName() + " instance is null."); + } + + if (this.ajaxHandler.isAjaxRequest(request, response)) { + if (vlogger.isTraceEnabled()) { + vlogger.trace( + "[THYMELEAF] RedirectView for URL \"{}\" is an AJAX request. AjaxHandler of class {} " + + "will be in charge of processing the request.", targetUrl, this.ajaxHandler.getClass().getName()); + } + this.ajaxHandler.sendAjaxRedirect(targetUrl, request, response, false); + } else { + vlogger.trace( + "[THYMELEAF] RedirectView for URL \"{}\" is not an AJAX request. Request will be handled " + + "as a normal redirect", targetUrl); + super.sendRedirect(request, response, targetUrl, http10Compatible); + } + } + + } + + + +} \ No newline at end of file diff --git a/booking-mvc/src/main/java/org/thymeleaf/spring6/view/FlowAjaxThymeleafView.java b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/FlowAjaxThymeleafView.java new file mode 100644 index 0000000..ed29e39 --- /dev/null +++ b/booking-mvc/src/main/java/org/thymeleaf/spring6/view/FlowAjaxThymeleafView.java @@ -0,0 +1,93 @@ +/* + * ============================================================================= + * + * Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org) + * + * Licensed 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.thymeleaf.spring6.view; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.webflow.execution.RequestContext; +import org.springframework.webflow.execution.RequestContextHolder; +import org.springframework.webflow.execution.View; + + +// Copied from thymeleaf-spring5. +// Temporarily here until available in thymeleaf-spring6. + + +/** + *

+ * Subclass of {@link AjaxThymeleafView} for Spring WebFlow, + * designed for obtaining the fragments to be rendered via + * AJAX in the way needed by Spring WebFlow. + *

+ *

+ * Most people will need to use this class instead of + * {@link AjaxThymeleafView} if you are using Spring WebFlow. + *

+ * + * @author Daniel Fernández + * + * @since 3.0.3 + * + */ +public class FlowAjaxThymeleafView extends AjaxThymeleafView { + + + + + + public FlowAjaxThymeleafView() { + super(); + } + + + + + + @Override + @SuppressWarnings("rawtypes") + protected Set getRenderFragments( + final Map model, final HttpServletRequest request, final HttpServletResponse response) { + + final RequestContext context = RequestContextHolder.getRequestContext(); + if (context == null) { + return super.getRenderFragments(model, request, response); + } + + final String[] fragments = (String[]) context.getFlashScope().get(View.RENDER_FRAGMENTS_ATTRIBUTE); + if (fragments == null || fragments.length == 0) { + return super.getRenderFragments(model, request, response); + } + if (fragments.length == 1) { + return Collections.singleton(fragments[0]); + } + return new HashSet(Arrays.asList(fragments)); + + } + + + +} \ No newline at end of file