Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Resolves MOBILE-35 #7

Closed
wants to merge 3 commits into from

2 participants

Scott Rossillo Roy Clarkson
Scott Rossillo

This should resolve MOBILE-35 complete with documentation updates. If anything needs to be tweaked before you can accept this pull request, please let me know and I'll be happy to work on it.

Roy Clarkson
Owner

I've modified the original pull request and submitted a new one for review.

#12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 2, 2012
  1. Scott Rossillo

    Fixes MOBILE-35 by creating a new DeviceAwareViewResolver extended fr…

    foo4u authored
    …om Spring MVC's UrlBasedViewResolver.
  2. Scott Rossillo

    Fixed documentation errors.

    foo4u authored
Commits on Jun 13, 2012
  1. Scott Rossillo
This page is out of date. Refresh to see the latest.
272 ...ile-device/src/main/java/org/springframework/mobile/device/web/servlet/view/DeviceAwareViewResolver.java
View
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * 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.springframework.mobile.device.web.servlet.view;
+
+import java.util.Locale;
+
+import org.springframework.mobile.device.Device;
+import org.springframework.mobile.device.DeviceUtils;
+import org.springframework.mobile.device.site.SitePreference;
+import org.springframework.mobile.device.site.SitePreferenceUtils;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.view.UrlBasedViewResolver;
+
+/**
+ * Provides a device aware extension of the
+ * {@link org.springframework.web.servlet.view.UrlBasedViewResolver} class,
+ * allowing for resolution of device specific view names without the need for
+ * a dedicated mapping to be defined for each view.
+ *
+ * <p>
+ * Device aware view names can be augmented by a specified normal prefix
+ * and/or a mobile prefix and take into account a base prefix and/or suffix
+ * specified via {@link #setPrefix(String)} and/or {@link #setSuffix(String)}.
+ * </p>
+ *
+ * <p>
+ * For example, prefix="/WEB-INF/jsp/", suffix=".jsp", normalPrefix="normal" mobilePrefix="mobile" viewname="test":
+ * </p>
+ *
+ * <p>
+ * When device is normal -&gt; "/WEB-INF/jsp/normal/test.jsp" <br />
+ * When device is mobile -&gt; "/WEB-INF/jsp/mobile/test.jsp"
+ * </p>
+ *
+ * <p>
+ * A prefix may be omitted for normal views, which may be useful if you
+ * are adding mobile views to an existing project, e.g.,
+ * prefix="/WEB-INF/jsp/", suffix=".jsp", mobilePrefix="mobile" viewname="test":
+ * </p>
+ *
+ * <p>
+ * When device is normal -&gt; "/WEB-INF/jsp/test.jsp" <br />
+ * When device is mobile -&gt; "/WEB-INF/jsp/mobile/test.jsp"
+ * </p>
+ *
+ *
+ * <p>
+ * This implementation supports all the features of
+ * {@link org.springframework.web.servlet.view.UrlBasedViewResolver}, such as
+ * redirect URLs and forward URLs specified via the "redirect:" and "forward:"
+ * prefixes.
+ * </p>
+ *
+ * <p>
+ * Note: This class does not support localized resolution, i.e. resolving a
+ * symbolic view name to different resources depending on the current locale.
+ * </p>
+ *
+ * @author Scott Rossillo
+ *
+ * @see #setNormalPrefix(String)
+ * @see #setMobilePrefix(String)
+ *
+ * @see org.springframework.web.servlet.view.UrlBasedViewResolver
+ */
+public class DeviceAwareViewResolver extends UrlBasedViewResolver implements ViewResolver {
+
+ private String mobilePrefix = null;
+ private String normalPrefix = null;
+
+ /**
+ * Creates a new device aware view resolver.
+ */
+ public DeviceAwareViewResolver() {
+ super();
+ }
+
+ /**
+ * Returns the device aware view name for the given device prefix and view name.
+ *
+ * @param prefix the path prefix for the device requesting the view
+ *
+ * @param viewName the name of the view before device resolution
+ *
+ * @return the device aware view name for the given device prefix and view name
+ */
+ protected String buildDeviceViewName(final String prefix, final String viewName) {
+
+ if (prefix == null) {
+ if (normalPrefix != null) {
+ return this.buildDeviceViewName(normalPrefix, viewName);
+ } else {
+ return viewName;
+ }
+ }
+
+ return (prefix + "/" + viewName);
+ }
+
+ /**
+ * This implementation returns only the device aware view name,
+ * as this view resolver doesn't support localized resolution.
+ *
+ * @see org.springframework.web.servlet.view.UrlBasedViewResolver#getCacheKey(java.lang.String, java.util.Locale)
+ * @see #resolveDeviceAwareViewName(String)
+ */
+ @Override
+ protected Object getCacheKey(String viewName, Locale locale) {
+ return this.resolveDeviceAwareViewName(viewName);
+ }
+
+ /**
+ * Returns the given prefix without a leading or trailing slash.
+ *
+ * @param prefix the prefix to normalize
+ *
+ * @return the given <code>prefix</code> without a trailing slash
+ */
+ private String normalizePrefix(final String prefix) {
+
+ String normalizedPrefix = prefix;
+
+ if (prefix == null) {
+ return null;
+ }
+
+ if(normalizedPrefix.startsWith("/")) {
+ normalizedPrefix = prefix.substring(1);
+ }
+
+ if (normalizedPrefix.endsWith("/") ) {
+ normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1);
+ }
+
+ return normalizedPrefix;
+ }
+
+ /**
+ * Overridden to support device aware view resolution.
+ * Delegates to {@link #createDeviceAwareView(String, Locale)}
+ * unless the view name contains special redirect URLs or forward URLs.
+ * If a redirect URL or forward URL is detected,
+ * {@link org.springframework.web.servlet.view.UrlBasedViewResolver#createView(java.lang.String, java.util.Locale)}
+ * is called instead.
+ *
+ * @see org.springframework.web.servlet.view.UrlBasedViewResolver#createView(java.lang.String, java.util.Locale)
+ */
+ @Override
+ protected View createView(final String viewName, final Locale locale) throws Exception {
+
+ // If this resolver is not supposed to handle the given view,
+ // return null to pass on to the next resolver in the chain.
+ if (!canHandle(viewName, locale)) {
+ return null;
+ }
+
+ // Check for special "redirect:" or "forward:" prefixes and delegate
+ // handling to the superclass.
+ if (viewName.startsWith(REDIRECT_URL_PREFIX) || viewName.startsWith(FORWARD_URL_PREFIX)) {
+ return super.createView(viewName, locale);
+ }
+
+ return this.createDeviceAwareView(viewName, locale);
+ }
+
+
+ /**
+ * Creates the device aware view object.
+ *
+ * <p>
+ * Resolves the device aware view name and then delegates to
+ * {@link #loadView(String, Locale)} to actually load the view object.
+ * </p>
+ *
+ * @param viewName the name of the view to retrieve
+ * @param locale the <code>Locale</code> to retrieve the view for
+ *
+ * @return the <code>View</code> instance, or <code>null</code> if not found
+ *
+ * @throws Exception if the view couldn't be resolved
+ */
+ protected View createDeviceAwareView(final String viewName, final Locale locale) throws Exception {
+ return super.loadView(resolveDeviceAwareViewName(viewName), locale);
+ }
+
+ /**
+ * Returns the device aware view name for the given view name.
+ * Resolves the device and site preference from the current request
+ * context and then delegates to
+ * {@link #resolveDeviceAwareViewName(String, Device, SitePreference)}.
+ *
+ * @param viewName the view name to resolve
+ *
+ * @return the device aware view name for the given <code>viewName</code>
+ */
+ protected String resolveDeviceAwareViewName(final String viewName) {
+
+ final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ final Device device = DeviceUtils.getCurrentDevice(requestAttributes);
+ final SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(requestAttributes);
+
+ return resolveDeviceAwareViewName(viewName, device, sitePreference);
+ }
+
+ /**
+ * Returns the device aware view name for the given view name, given device
+ * and given site preference.
+ *
+ * @param viewName the view name to resolve (required)
+ *
+ * @param device the <code>Device</code> for the current request (required)
+ *
+ * @param sitePreference the <code>SitePreference</code> for the
+ * current request or null if no site preference was specified
+ *
+ * @return the device aware view name for the given <code>viewName</code>,
+ * <code>device</code> and <code>sitePreference</code>
+ *
+ */
+ protected String resolveDeviceAwareViewName(
+ final String viewName,
+ final Device device,
+ final SitePreference sitePreference) {
+
+ String deviceAwareViewName;
+
+ if (device.isMobile() && (sitePreference == null || sitePreference.isMobile())) {
+ deviceAwareViewName = this.buildDeviceViewName(mobilePrefix, viewName);
+ } else {
+ deviceAwareViewName = this.buildDeviceViewName(normalPrefix, viewName);
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Resolved view with name [%s] for device [%s] preference [%s]%n",
+ deviceAwareViewName, device, sitePreference));
+ }
+
+ return deviceAwareViewName;
+ }
+
+ /**
+ * Sets the mobile prefix for this view resolver.
+ */
+ public void setMobilePrefix(String mobilePrefix) {
+ this.mobilePrefix = normalizePrefix(mobilePrefix);
+ }
+
+ /**
+ * Sets the normal prefix for this view resolver.
+ */
+ public void setNormalPrefix(String normalPrefix) {
+ this.normalPrefix = normalizePrefix(normalPrefix);
+ }
+
+}
4 spring-mobile-device/src/main/java/org/springframework/mobile/device/web/servlet/view/package-info.java
View
@@ -0,0 +1,4 @@
+/**
+ * Device aware view resolution for Spring MVC-based web apps.
+ */
+package org.springframework.mobile.device.web.servlet.view;
93 ...device/src/test/java/org/springframework/mobile/device/web/servlet/view/DeviceAwareViewResolverTest.java
View
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * 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.springframework.mobile.device.web.servlet.view;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.mobile.device.Device;
+import org.springframework.mobile.device.DeviceType;
+import org.springframework.mobile.device.StubDevice;
+import org.springframework.mobile.device.site.SitePreference;
+import org.springframework.mobile.device.web.servlet.view.DeviceAwareViewResolver;
+
+/**
+ * Device aware view resolver tests.
+ *
+ * @author Scott Rossillo
+ *
+ */
+public final class DeviceAwareViewResolverTest {
+
+ private final DeviceAwareViewResolver viewResolver = new DeviceAwareViewResolver();
+
+ private final String viewName = "home";
+
+ @Before
+ public void setUp() {
+ viewResolver.setPrefix("/WEB-INF/views/");
+ viewResolver.setSuffix(".jsp");
+ viewResolver.setNormalPrefix("/normal/");
+ viewResolver.setMobilePrefix("/mobile/");
+ }
+
+ @Test
+ public void testMobileViewResolution() throws Exception {
+
+ final Device device = new StubDevice(DeviceType.MOBILE);
+ final String resolvedViewName;
+
+ resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, null);
+
+ Assert.assertEquals("mobile/home", resolvedViewName);
+ }
+
+ @Test
+ public void testMobileViewResolutionWithMobileSitePreference() throws Exception {
+
+ final Device device = new StubDevice(DeviceType.MOBILE);
+ final SitePreference sitePreference = SitePreference.MOBILE;
+ final String resolvedViewName;
+
+ resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, sitePreference);
+
+ Assert.assertEquals("mobile/home", resolvedViewName);
+ }
+
+ @Test
+ public void testMobileViewResolutionWithNormalSitePreference() throws Exception {
+
+ final Device device = new StubDevice(DeviceType.MOBILE);
+ final SitePreference sitePreference = SitePreference.NORMAL;
+ final String resolvedViewName;
+
+ resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, sitePreference);
+
+ Assert.assertEquals("normal/home", resolvedViewName);
+ }
+
+ @Test
+ public void testNormalViewResolution() throws Exception {
+
+ final Device device = new StubDevice(DeviceType.NORMAL);
+ final String resolvedViewName;
+
+ resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, null);
+
+ Assert.assertEquals("normal/home", resolvedViewName);
+ }
+}
47 src/reference/docbook/device.xml
View
@@ -487,4 +487,51 @@ public class HomeController {
</section>
+ <section id="spring-mobile-device-aware-view-resolver">
+
+ <title>Device Aware View Resolution</title>
+
+ <para>While site switching allows applications to host their "mobile site" at a different domain from their "normal site," some applications may prefer to use the same URL and controller methods to process requests and simply render a different view depending on the device type and site preference.</para>
+
+ <para>Using this approach, controllers do not need to be aware of the device type for which they are providing a data model. This is particularly useful if the controller logic is the same for both the normal site and the mobile site.</para>
+
+ <section id="spring-mobile-device-aware-view-resolver-config">
+
+ <title>Configuring Device Aware View Resolution</title>
+
+ <para>Spring Mobile supports device aware view resolution by extending Spring MVC's <code>UrlBasedViewResolver</code> with a <code>DeviceAwareViewResolver</code>. This view resolver supports all the features of <code>UrlBasedViewResolver</code>, such as redirect URLs and forward URLs specified via the <code>redirect:</code> and <code>forward:</code> prefixes.</para>
+
+ <para>To add support for device aware view resolution to your application context, configure a device aware view resolver bean in place of your internal resource view resolver:</para>
+
+ <programlisting language="xml"><![CDATA[
+<bean class="org.springframework.mobile.device.web.servlet.view.DeviceAwareViewResolver">
+ <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView" />
+ <property name="prefix" value="/WEB-INF/views/" />
+ <property name="suffix" value=".jsp" />
+ <property name="mobilePrefix" value="mobile" />
+ <property name="normalPrefix" value="normal" />
+ <property name="cache" value="true" />
+</bean>]]>
+ </programlisting>
+
+ <note>
+ <para>For applications that already have their "normal site" views residing in the <code>prefix</code> path, the <code>normalPrefix</code> property may be omitted, allowing the normal site's views to remain in their current location. In this case, the device aware view resolver will only insert the <code>mobilePrefix</code> for mobile views.</para>
+ </note>
+
+ <para>The device aware view resolver depends on two handler interceptors to detect the client device type and site preference. These interceptors should be added to your interceptor configuration:</para>
+
+ <programlisting language="xml"><![CDATA[
+<mvc:interceptors>
+ <!-- Detect the client's Device -->
+ <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />
+ <!-- Detect the client's Site Preference -->
+ <bean class="org.springframework.mobile.device.site.SitePreferenceHandlerInterceptor" />
+</mvc:interceptors> ]]>
+ </programlisting>
+
+ <para>The site preference handler interceptor is only required for applications that wish to support site preference management. Only the device resolver handler interceptor is required for view resolution. For more information, refer to <xref linkend="spring-mobile-site-preference"/>.</para>
+
+ </section>
+
+ </section>
</chapter>
Something went wrong with that request. Please try again.