Skip to content
This repository

Resolves MOBILE-35 #7

Closed
wants to merge 3 commits into from

2 participants

Scott Rossillo Roy Clarkson
Scott Rossillo
foo4u commented June 12, 2012

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
Collaborator

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

#12

Roy Clarkson royclarkson closed this December 19, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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
... ...
@@ -0,0 +1,272 @@
  1
+/*
  2
+ * Copyright 2012 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *      http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+package org.springframework.mobile.device.web.servlet.view;
  17
+
  18
+import java.util.Locale;
  19
+
  20
+import org.springframework.mobile.device.Device;
  21
+import org.springframework.mobile.device.DeviceUtils;
  22
+import org.springframework.mobile.device.site.SitePreference;
  23
+import org.springframework.mobile.device.site.SitePreferenceUtils;
  24
+import org.springframework.web.context.request.RequestAttributes;
  25
+import org.springframework.web.context.request.RequestContextHolder;
  26
+import org.springframework.web.servlet.View;
  27
+import org.springframework.web.servlet.ViewResolver;
  28
+import org.springframework.web.servlet.view.UrlBasedViewResolver;
  29
+
  30
+/**
  31
+ * Provides a device aware extension of the 
  32
+ * {@link org.springframework.web.servlet.view.UrlBasedViewResolver} class, 
  33
+ * allowing for resolution of device specific view names without the need for
  34
+ * a dedicated mapping to be defined for each view.
  35
+ * 
  36
+ * <p>
  37
+ * Device aware view names can be augmented by a specified normal prefix 
  38
+ * and/or a mobile prefix and take into account a base prefix and/or suffix 
  39
+ * specified via {@link #setPrefix(String)} and/or {@link #setSuffix(String)}.
  40
+ * </p>
  41
+ * 
  42
+ * <p>
  43
+ * For example, prefix="/WEB-INF/jsp/", suffix=".jsp", normalPrefix="normal" mobilePrefix="mobile" viewname="test": 
  44
+ * </p>
  45
+ * 
  46
+ * <p>
  47
+ * When device is normal -&gt; "/WEB-INF/jsp/normal/test.jsp" <br />
  48
+ * When device is mobile -&gt; "/WEB-INF/jsp/mobile/test.jsp"
  49
+ * </p>
  50
+ * 
  51
+ * <p>
  52
+ * A prefix may be omitted for normal views, which may be useful if you
  53
+ * are adding mobile views to an existing project, e.g.,
  54
+ * prefix="/WEB-INF/jsp/", suffix=".jsp", mobilePrefix="mobile" viewname="test":
  55
+ * </p>
  56
+ * 
  57
+ * <p>
  58
+ * When device is normal -&gt; "/WEB-INF/jsp/test.jsp" <br />
  59
+ * When device is mobile -&gt; "/WEB-INF/jsp/mobile/test.jsp"
  60
+ * </p>
  61
+ * 
  62
+ * 
  63
+ * <p>
  64
+ * This implementation supports all the features of
  65
+ * {@link org.springframework.web.servlet.view.UrlBasedViewResolver}, such as
  66
+ * redirect URLs and forward URLs specified via the "redirect:" and "forward:"
  67
+ * prefixes. 
  68
+ * </p>
  69
+ * 
  70
+ * <p>
  71
+ * Note: This class does not support localized resolution, i.e. resolving a
  72
+ * symbolic view name to different resources depending on the current locale.
  73
+ * </p>
  74
+ * 
  75
+ * @author Scott Rossillo
  76
+ * 
  77
+ * @see #setNormalPrefix(String)
  78
+ * @see #setMobilePrefix(String)
  79
+ *
  80
+ * @see org.springframework.web.servlet.view.UrlBasedViewResolver
  81
+ */
  82
+public class DeviceAwareViewResolver extends UrlBasedViewResolver implements ViewResolver {
  83
+
  84
+	private String mobilePrefix = null;
  85
+	private String normalPrefix = null;
  86
+
  87
+	/**
  88
+	 * Creates a new device aware view resolver.
  89
+	 */
  90
+	public DeviceAwareViewResolver() {
  91
+		super();
  92
+	}
  93
+
  94
+	/**
  95
+	 * Returns the device aware view name for the given device prefix and view name.
  96
+	 * 
  97
+	 * @param prefix the path prefix for the device requesting the view
  98
+	 * 
  99
+	 * @param viewName the name of the view before device resolution
  100
+	 * 
  101
+	 * @return the device aware view name for the given device prefix and view name
  102
+	 */
  103
+	protected String buildDeviceViewName(final String prefix, final String viewName) {
  104
+
  105
+		if (prefix == null) {
  106
+			if (normalPrefix != null) {
  107
+				return this.buildDeviceViewName(normalPrefix, viewName);
  108
+			} else {
  109
+				return viewName;
  110
+			}
  111
+		}
  112
+
  113
+		return (prefix + "/" + viewName);
  114
+	}
  115
+
  116
+	/**
  117
+	 * This implementation returns only the device aware view name, 
  118
+	 * as this view resolver doesn't support localized resolution.
  119
+	 * 
  120
+	 * @see org.springframework.web.servlet.view.UrlBasedViewResolver#getCacheKey(java.lang.String, java.util.Locale)
  121
+	 * @see #resolveDeviceAwareViewName(String)
  122
+	 */
  123
+	@Override
  124
+	protected Object getCacheKey(String viewName, Locale locale) {
  125
+		return this.resolveDeviceAwareViewName(viewName);
  126
+	}
  127
+
  128
+	/**
  129
+	 * Returns the given prefix without a leading or trailing slash.
  130
+	 * 
  131
+	 * @param prefix the prefix to normalize
  132
+	 * 
  133
+	 * @return the given <code>prefix</code> without a trailing slash
  134
+	 */
  135
+	private String normalizePrefix(final String prefix) {
  136
+
  137
+		String normalizedPrefix = prefix;
  138
+		
  139
+		if (prefix == null) {
  140
+			return null;
  141
+		}
  142
+		
  143
+		if(normalizedPrefix.startsWith("/")) {
  144
+			normalizedPrefix = prefix.substring(1);
  145
+		}
  146
+
  147
+		if (normalizedPrefix.endsWith("/") ) {
  148
+			normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1);
  149
+		}
  150
+
  151
+		return normalizedPrefix;
  152
+	}
  153
+
  154
+	/**
  155
+	 * Overridden to support device aware view resolution.
  156
+	 * Delegates to {@link #createDeviceAwareView(String, Locale)} 
  157
+	 * unless the view name contains special redirect URLs or forward URLs. 
  158
+	 * If a redirect URL or forward URL is detected, 
  159
+	 * {@link org.springframework.web.servlet.view.UrlBasedViewResolver#createView(java.lang.String, java.util.Locale)}
  160
+	 * is called instead.
  161
+	 * 
  162
+	 * @see org.springframework.web.servlet.view.UrlBasedViewResolver#createView(java.lang.String, java.util.Locale)
  163
+	 */
  164
+	@Override
  165
+	protected View createView(final String viewName, final Locale locale) throws Exception {
  166
+
  167
+		// If this resolver is not supposed to handle the given view,
  168
+		// return null to pass on to the next resolver in the chain.
  169
+		if (!canHandle(viewName, locale)) {
  170
+			return null;
  171
+		}
  172
+
  173
+		// Check for special "redirect:" or "forward:" prefixes and delegate
  174
+		// handling to the superclass.
  175
+		if (viewName.startsWith(REDIRECT_URL_PREFIX) || viewName.startsWith(FORWARD_URL_PREFIX)) {
  176
+			return super.createView(viewName, locale);
  177
+		}
  178
+
  179
+		return this.createDeviceAwareView(viewName, locale);
  180
+	}
  181
+
  182
+
  183
+	/**
  184
+	 * Creates the device aware view object.
  185
+	 * 
  186
+	 * <p>
  187
+	 * Resolves the device aware view name and then delegates to 
  188
+	 * {@link #loadView(String, Locale)} to actually load the view object.
  189
+	 * </p>
  190
+	 * 
  191
+	 * @param viewName the name of the view to retrieve
  192
+	 * @param locale the <code>Locale</code> to retrieve the view for
  193
+	 * 
  194
+	 * @return the <code>View</code> instance, or <code>null</code> if not found
  195
+	 * 
  196
+	 * @throws Exception if the view couldn't be resolved
  197
+	 */
  198
+	protected View createDeviceAwareView(final String viewName, final Locale locale) throws Exception {
  199
+		return super.loadView(resolveDeviceAwareViewName(viewName), locale);
  200
+	}
  201
+
  202
+	/**
  203
+	 * Returns the device aware view name for the given view name.
  204
+	 * Resolves the device and site preference from the current request
  205
+	 * context and then delegates to 
  206
+	 * {@link #resolveDeviceAwareViewName(String, Device, SitePreference)}.
  207
+	 * 
  208
+	 * @param viewName the view name to resolve
  209
+	 * 
  210
+	 * @return the device aware view name for the given <code>viewName</code>
  211
+	 */
  212
+	protected String resolveDeviceAwareViewName(final String viewName) {
  213
+
  214
+		final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  215
+		final Device device = DeviceUtils.getCurrentDevice(requestAttributes);
  216
+		final SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(requestAttributes);
  217
+
  218
+		return resolveDeviceAwareViewName(viewName, device, sitePreference);
  219
+	}
  220
+
  221
+	/**
  222
+	 * Returns the device aware view name for the given view name, given device
  223
+	 * and given site preference.
  224
+	 * 
  225
+	 * @param viewName the view name to resolve (required)
  226
+	 * 
  227
+	 * @param device the <code>Device</code> for the current request (required)
  228
+	 * 
  229
+	 * @param sitePreference the <code>SitePreference</code> for the 
  230
+	 * current request or null if no site preference was specified
  231
+	 * 
  232
+	 * @return the device aware view name for the given <code>viewName</code>, 
  233
+	 * <code>device</code> and <code>sitePreference</code>
  234
+	 * 
  235
+	 */
  236
+	protected String resolveDeviceAwareViewName(
  237
+			final String viewName,
  238
+			final Device device,
  239
+			final SitePreference sitePreference) {
  240
+
  241
+		String deviceAwareViewName;
  242
+		
  243
+		if (device.isMobile() && (sitePreference == null || sitePreference.isMobile())) {
  244
+			deviceAwareViewName = this.buildDeviceViewName(mobilePrefix, viewName);
  245
+		} else {
  246
+			deviceAwareViewName = this.buildDeviceViewName(normalPrefix, viewName);
  247
+		}
  248
+
  249
+		if (logger.isDebugEnabled()) {
  250
+			logger.debug(String.format(
  251
+					"Resolved view with name [%s] for device [%s] preference [%s]%n", 
  252
+					deviceAwareViewName, device, sitePreference));
  253
+		}
  254
+
  255
+		return deviceAwareViewName;
  256
+	}
  257
+
  258
+	/**
  259
+	 * Sets the mobile prefix for this view resolver.
  260
+	 */
  261
+	public void setMobilePrefix(String mobilePrefix) {
  262
+		this.mobilePrefix = normalizePrefix(mobilePrefix);
  263
+	}
  264
+
  265
+	/**
  266
+	 * Sets the normal prefix for this view resolver.
  267
+	 */
  268
+	public void setNormalPrefix(String normalPrefix) {
  269
+		this.normalPrefix = normalizePrefix(normalPrefix);
  270
+	}
  271
+
  272
+}
4  spring-mobile-device/src/main/java/org/springframework/mobile/device/web/servlet/view/package-info.java
... ...
@@ -0,0 +1,4 @@
  1
+/**
  2
+ * Device aware view resolution for Spring MVC-based web apps.
  3
+ */
  4
+package org.springframework.mobile.device.web.servlet.view;
93  ...device/src/test/java/org/springframework/mobile/device/web/servlet/view/DeviceAwareViewResolverTest.java
... ...
@@ -0,0 +1,93 @@
  1
+/*
  2
+ * Copyright 2012 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *      http://www.apache.org/licenses/LICENSE-2.0
  9
+ *
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+package org.springframework.mobile.device.web.servlet.view;
  17
+
  18
+import junit.framework.Assert;
  19
+
  20
+import org.junit.Before;
  21
+import org.junit.Test;
  22
+import org.springframework.mobile.device.Device;
  23
+import org.springframework.mobile.device.DeviceType;
  24
+import org.springframework.mobile.device.StubDevice;
  25
+import org.springframework.mobile.device.site.SitePreference;
  26
+import org.springframework.mobile.device.web.servlet.view.DeviceAwareViewResolver;
  27
+
  28
+/**
  29
+ * Device aware view resolver tests.
  30
+ * 
  31
+ * @author Scott Rossillo
  32
+ *
  33
+ */
  34
+public final class DeviceAwareViewResolverTest {
  35
+	
  36
+	private final DeviceAwareViewResolver viewResolver = new DeviceAwareViewResolver();
  37
+	
  38
+	private final String viewName = "home";
  39
+	
  40
+	@Before
  41
+	public void setUp() {
  42
+		viewResolver.setPrefix("/WEB-INF/views/");
  43
+		viewResolver.setSuffix(".jsp");
  44
+		viewResolver.setNormalPrefix("/normal/");
  45
+		viewResolver.setMobilePrefix("/mobile/");
  46
+	}
  47
+
  48
+	@Test
  49
+	public void testMobileViewResolution() throws Exception {
  50
+		
  51
+		final Device device = new StubDevice(DeviceType.MOBILE);
  52
+		final String resolvedViewName;
  53
+		
  54
+		resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, null);
  55
+		
  56
+		Assert.assertEquals("mobile/home", resolvedViewName);
  57
+	}
  58
+	
  59
+	@Test
  60
+	public void testMobileViewResolutionWithMobileSitePreference() throws Exception {
  61
+		
  62
+		final Device device = new StubDevice(DeviceType.MOBILE);
  63
+		final SitePreference sitePreference = SitePreference.MOBILE;
  64
+		final String resolvedViewName;
  65
+		
  66
+		resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, sitePreference);
  67
+		
  68
+		Assert.assertEquals("mobile/home", resolvedViewName);
  69
+	}
  70
+	
  71
+	@Test
  72
+	public void testMobileViewResolutionWithNormalSitePreference() throws Exception {
  73
+		
  74
+		final Device device = new StubDevice(DeviceType.MOBILE);
  75
+		final SitePreference sitePreference = SitePreference.NORMAL;
  76
+		final String resolvedViewName;
  77
+		
  78
+		resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, sitePreference);
  79
+		
  80
+		Assert.assertEquals("normal/home", resolvedViewName);
  81
+	}
  82
+	
  83
+	@Test
  84
+	public void testNormalViewResolution() throws Exception {
  85
+		
  86
+		final Device device = new StubDevice(DeviceType.NORMAL);
  87
+		final String resolvedViewName;
  88
+		
  89
+		resolvedViewName = viewResolver.resolveDeviceAwareViewName(viewName, device, null);
  90
+		
  91
+		Assert.assertEquals("normal/home", resolvedViewName);
  92
+	}
  93
+}
47  src/reference/docbook/device.xml
@@ -487,4 +487,51 @@ public class HomeController {
487 487
         
488 488
     </section>
489 489
 
  490
+    <section id="spring-mobile-device-aware-view-resolver">
  491
+    
  492
+        <title>Device Aware View Resolution</title>
  493
+        
  494
+        <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>
  495
+
  496
+        <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>
  497
+
  498
+        <section id="spring-mobile-device-aware-view-resolver-config">
  499
+            
  500
+            <title>Configuring Device Aware View Resolution</title>
  501
+            
  502
+            <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>
  503
+            
  504
+            <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>
  505
+            
  506
+            <programlisting language="xml"><![CDATA[
  507
+<bean class="org.springframework.mobile.device.web.servlet.view.DeviceAwareViewResolver">
  508
+    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView" />
  509
+    <property name="prefix" value="/WEB-INF/views/" />        
  510
+    <property name="suffix" value=".jsp" />
  511
+    <property name="mobilePrefix" value="mobile" />
  512
+    <property name="normalPrefix" value="normal" />
  513
+    <property name="cache" value="true" />
  514
+</bean>]]>
  515
+            </programlisting>
  516
+
  517
+            <note>
  518
+                <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>
  519
+            </note>
  520
+            
  521
+            <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>
  522
+
  523
+            <programlisting language="xml"><![CDATA[            
  524
+<mvc:interceptors>
  525
+    <!-- Detect the client's Device -->
  526
+    <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />
  527
+    <!-- Detect the client's Site Preference -->
  528
+    <bean class="org.springframework.mobile.device.site.SitePreferenceHandlerInterceptor" />
  529
+</mvc:interceptors> ]]>
  530
+            </programlisting>
  531
+            
  532
+            <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>
  533
+            
  534
+        </section>
  535
+
  536
+    </section>
490 537
 </chapter>
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.