Struts2 how to create multiple views for mobile and desktop - struts2

I am creating a site which will be accessible via mobile and desktop devices. So I want to create 2 views of my application. My action code and everything else in the backend (manageers, DAOs) is same. Just JSP changes for both.
How I can do this via Struts 2?

In struts there are many way to obtain the same thing.
In this case, the one I prefer is:
You could write an interceptor that changes the return code based on the user-agent of
the client, such that there would be versions for PC and mobile of each jsp.
In your configuration you need to have all the result codes for all jsp (or you could simply define the result through the wildcard mapping).
For example: change the result code from "success" to "mobile_success". In case you want map both results in the same jsp you can map, as I said before, in this way
<result name="*success">

not sure whether there is library for automating such task for struts 2. but if there is, using such libraries might be better
anyway, here is the theory. every browser has its own "signature" written in the request header, called "User-Agent". different browser (supposedly) has different user agent. for example, my firefox user agent is as following:
Mozilla/5.0 (Windows NT 6.0; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5
basically, by detecting the user agent, you can know what browser is used to access your site. the list of mobile browser user agents can be found in http://www.zytrax.com/tech/web/mobile_ids.html
if i'm not wrong, you can retrieve the user agent in server by httpServletRequest.getHeader("User-Agent"); (correct me if i'm wrong)
you can then create an interceptor which will decide whether a client is from mobile or from desktop. that interceptor can return different result for different client type. for example, if the client is desktop, you can return "successDesktop" and if the client is mobile, you can return "successMobile".
well, hopefully someone else can come up with (far) easier solution

I am currently trying to solve this very same problem. A framework would be nice, and I'm all ears if anyone has tested and approved one. That said, I can't find anything mature enough for me to be justify moving from Struts for the mobile view.
My best solution currently is to create actions for each of the parts of my full page which will be displayed on full browsers. Then to reuse those actions to display page segments on the mobile side.
I found trying to make one page look right for a desktop browser and a mobile browser simultaneously was not a sustainable approach.
jQuery mobile looks like a very promising library for styling the elements retrieved by struts.
So while it is surely possible to cram both versions of the site into one action I think taking the time to create small reusable actions that result in jsp snippits will pay off as your app scales.
Here are some possibilities for the near future:
(I can't add these as links as I don't have enough reputation...you'll have to add the 'http://www.')
Struts2 jQuery Mobile Project homepage: http://code.google.com/p/struts2-jquery/
Struts2 jQuery Mobile project: code.google.com/p/struts2-jquery/downloads/detail?name=struts2-jquery-mobile-showcase-3.1.1.war
an example of struts2 jQuery Mobile: weinfreund.de/struts2-jquery-mobile-showcase/index.action
#fajrian - using 'user agent' to determine a browser type could become a real pain as more and more mobile and desktop browsers are released. A better approach would be to determine whether to display a mobile version or full version based on the window's dimensions. A perfect example.
edit - Check out CSS3 media queries.

As Maurizio said you could use interceptors. Here is what I found.... http://www.benmccann.com/blog/struts-2-tutorial-interceptors/

This works for me and should basically get round the problem. You do need to know at least part of the user agent strings though:
public class MobileInterceptor extends AbstractInterceptor {
private static final String RESULT_CODE_SUFFIX_MOBILE = "mobile";
private static final String REQUEST_HEADER_ACCEPT = "Accept";
private static final String[] MOBILE_BROWSER_UAS = {"iPhone OS","Android","BlackBerry","Windows Phone"};
public String intercept(ActionInvocation invocation) throws Exception {
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
// check if a wireless version of the page exists
// by looking for a wireless action mapping in the struts.xml
Map results = invocation.getProxy().getConfig().getResults();
System.out.println("Results:"+results.toString());
if(!results.containsKey(resultCode + RESULT_CODE_SUFFIX_MOBILE)) {
return;
}
// send to mobile version if mobile browser is used
final String acceptHeader = ServletActionContext.getRequest().getHeader(REQUEST_HEADER_ACCEPT);
//Get User Agent String
String userAgent = ServletActionContext.getRequest().getHeader("User-Agent");
System.out.println("UA: "+userAgent);
//Boolean to indicate whether to show mobile version
boolean showMobileVersion = false;
//Run through each entry in the list of browsers
for(String ua : MOBILE_BROWSER_UAS){
if(userAgent.toLowerCase().matches(".*"+ua.toLowerCase()+".*")){
showMobileVersion = true;
}
}
if(showMobileVersion) {
invocation.setResultCode(resultCode + RESULT_CODE_SUFFIX_MOBILE);
}
}
});
return invocation.invoke();
}

Related

Async Function Fails when called as part of a Constructor

I'm rather new to Blazor, but I am currently trying to get access to some classes from within a class library that I've created and deployed as a Nuget package. As background, the Nuget package is an Api library, which allows me to talk to a webservice (I don't know if this is relevant or not). However, every time I go to the page where I'm testing, the page never loads and instead I left looking at the browser loading circle until I navigate away or close the application. During my testing here, it seems like it's the #inject call of my interface into the Blazor component which is causing the issue as when I remove it and try to load the page normally, the page does so.
So to demonstrate what I have setup, here is where I've added the Singletons to the DI:
builder.Services.AddSingleton<IApiConfigHelper, ApiConfigHelper>();
builder.Services.AddSingleton<IApiHelper, ApiHelper>();
builder.Services.AddSingleton<ISystemEndpoint, SystemEndpoint>();
Then on the blazor page, I have the following declarations at the top of my page:
#using Library.Endpoints
#using Library.Models
#page "/"
#inject ISystemEndpoint _systemEndpoint
Now I am leaning towards is this something to do with the Nuget package and using it with DI. I have tested the library away from this project (In a console application) and can confirm it's working as it should.
I have also created a local class library as a test to, to see if I could inject a data access class into the page and I can confirm that this works without an issue, which suggests to me that DI is working, just not with my Nuget package.
I did have a look into CORS, given that the Nuget package is accessing an external domain, and setup the following simple CORS policy in the app:
builder.Services.AddCors(policy =>
{
policy.AddPolicy("OpenCorsPolicy", opt =>
opt.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
Which is added to the app after the AddRouting call like so:
app.UseCors("OpenCorsPolicy");
However again, this wasn't the solution so if anyone is able to point me in the right direction with where I may be going wrong with this or offer any advice, I would be most grateful.
EDIT 1 - Provides details #mason queried
Regarding SystemEndpoint, the constructor is being injected with 2 things, as below:
public SystemEndpoint(IApiHelper apiHelper, IOptions<UriConfigModel> uriOptions)
{
_apiHelper = apiHelper;
_uriOptions = uriOptions.Value;
}
My Nuget Library is dependant on the following:
Azure.Identity
Azure.Security.KeyVault.Secrets
Microsoft.AspNet.WebApi.Client
Microsoft.Extensisons.Options.ConfigurationExtensions
EDIT 2 - Doing some further testing with this I have added a simple Endpoint class to my Nuget library, which returns a string with a basic message, as well as returning the values of the 2 UriConfig properties as below. I added this test to 1) sanity check that my DI was working correctly, and 2) check the values that are being assigned from appsettings to my UriConfig Object.
public class TestEndpoint : ITestEndpoint
{
private readonly IOptions<UriConfigModel> _uriConfig;
public TestEndpoint(IOptions<UriConfigModel> uriConfig)
{
_uriConfig = uriConfig;
}
public string TestMethod()
{
return $"You have successfully called the test method\n\n{_uriConfig.Value.Release} / {_uriConfig.Value.Version}";
}
}
However when adding in the dependency of IApiHelper into the Ctor, the method then breaks and fails to load the page. Looking into ApiHeloer, the Ctor has a dependency being injected into it of IApiConfigHelper. Looking at the implementation, the Ctor of ApiConfigHelper is setting up the values and parameters of the HttpClient that should make the REST calls to the external Api.
Now I believe what is breaking the code at this point is a call I'm making to Azure Key Vault, via REST, to pull out the secret values to connect to the Api. The call to KeyVault is being orchestrated via the following method, making use of the Azure.Security.KeyVault.Secrets Nuget Package, however I assume that at the heart of it, it's making a REST call to Azure on my behalf:
private async Task<KeyVaultSecret> GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = await _secretClient.GetSecretAsync(secretName);
return result.Value;
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
So that's where I stand with this at the moment. I still believe it could be down to CORS, as it seems to be falling over when making a call to an external service / domain, but I still can say 100%. As a closing thought, could it be something as simple as when I call call the above method, it's not being awaited????
So after persisting with this it seems like the reason it was failing was down to "awaiting" the call to Azure KeyVault, which was happening indirectly via the constructor of ApiConfigHelper. The resulting method for getting KeyVault value is now:
private KeyVaultSecret GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = _secretClient.GetSecret(secretName);
if (result is not null)
{
return result.Value;
}
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
I am now able to successfully make calls to my library and return values from the Api it interacts with.
I can also confirm that this IS NOT a CORS issue. Once I saw that removing the await was working, I then removed the CORS policy declarations from the service and the app in my Blazor's start-up code and everything continued to work without an issue.
As a final note, I must stress that this is only seems an issue when using the library with Blazor (possibly webApi projects) as I am able to use the library, awaiting the Azure call just fine in a console application.

Primefaces DialogFramework - How to show a dialog located in WEB-INF?

I am using Primefaces DialogFramework with
Primefaces 5.0
Mojarra 2.1.27
Glassfish 3.1.2.2 Build 5
My problem is, that if the user knows the location of my dialog, he is able to access it directly via the URL. I do not want that to be possible, so I thought it would be able to put the dialog in WEB-INF folder of my web-app, but now, if I want to open the dialog, I get a FileNotFound-Exception.
If my dialog is located in some regular folder, it works fine
RequestContext.getCurrentInstance().openDialog("/myfolder/mydialog");
// this works as expected
but if it is located in WEB-INF, it does not work any longer
RequestContext.getCurrentInstance().openDialog("/WEB-INF/mydialog",options,null);
// this is causing a fileNotFoundException
I also tried to set up a navigation rule for this in faces-config but again with no success
<navigation-case>
<from-outcome>mydialog</from-outcome>
<to-view-id>/WEB-INF/mydialog.xhtml</to-view-id>
<redirect />
</navigation-case>
How may I open dialogs located in WEB-INF folder, or is it not possible at all?
Thanks in advance
Unfortunately, putting PrimeFaces Dialog Framework dialogs in /WEB-INF in order to prevent direct access is indeed not going to work. The dialogs are loaded entirely client side. On the POST request which opens the dialog, JSF/PrimeFaces returns an oncomplete script with the (public!) URL of the dialog to JavaScript/jQuery, which in turn shows a basic dialog template with an <iframe> whose URL is set to the dialog URL, which in turn loads the content. In effects, 2 requests are being sent, the first to get the dialog's URL and the second to get the dialog's content based on that URL in the <iframe>.
There's no way to keep the dialog in /WEB-INF without falling back to the "traditional" dialog approach via <p:dialog> and conditional display via JS/CSS. There's also no way in the server side to verify based on some headers if the request is coming from an <iframe>, so that all others could simply be blocked. Your closest bet is the referer header, but this can be spoofed.
One way to minimize abuse is checking the presence of pfdlgcid request parameter (identified by Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM) when a dialog is being requested. PrimeFaces namely appends this request parameter representing "conversation ID" to the dialog URL. Presuming that all dialogs are stored in a folder /dialogs, then you could do the job with a simple servlet filter. Here's a kickoff example which sends a HTTP 400 error when /dialogs/* is being requested without the pfdlgcid request parameter.
#WebFilter("/dialogs/*")
public class DialogFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String id = request.getParameter(Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM);
if (id != null) {
chain.doFilter(req, res); // Okay, just continue request.
}
else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); // 400 error.
}
}
// ...
}
However, the abuser might not be that stupid and discover the pfdlgcid request parameter during the normal flow and still be able to open the dialog individually when supplying that parameter, even with a random value. I thought of comparing the actual pfdlgcid value to the known ones. I checked the PrimeFaces DialogNavigationHandler source code, but unfortunately, PrimeFaces doesn't store this value anywhere in the session. You'd need to provide a custom DialogNavigationHandler implementation wherein you store the pfdlgcid value in the session map which in turn is also compared in the servlet filter.
First add the following method to the DialogFilter:
public static Set<String> getIds(HttpServletRequest request) {
HttpSession session = request.getSession();
Set<String> ids = (Set<String>) session.getAttribute(getClass().getName());
if (ids == null) {
ids = new HashSet<>();
session.setAttribute(getClass().getName(), ids);
}
return ids;
}
Then copypaste the PrimeFaces DialogNavigationHandler source code into your own package and add the following line after line 62:
DialogFilter.getIds((HttpServletRequest) context.getExternalContext().getRequest()).add(pfdlgcid);
Replace the <navigation-handler> in faces-config.xml with the customized one.
Finally, alter the if condition in the DialogFilter#doFilter() method as follows:
if (getIds(request).contains(id)) {
// ...
}
Now, this prevents the abuser from attempting to open the dialog with a random ID. This however doesn't prevent the abuser from attempting to open the dialog by copypasting the exact <iframe> URL immediately after opening it. Given the way how the PrimeFaces dialog framework works, there's no way to prevent that. You could at most remove the pfdlgcid value from the session when the dialog is about to returns to the parent. However, when the dialog is closed by pure JS means, then this is also bypassed.
All in all, if you really, really, want to avoid the enduser being able to open the dialog individually, then you can't go around the "traditional" <p:dialog> approach.

How to detect mobile(not tablet) requests?

How can I detect mobile devices(but not tablets) requests in ASP.NET MVC application?
In ASP.NET, you can easily detect the mobile device request using Request.Browser.IsMobileDevice property and Request.UserAgent.
The following code checks the IsMobileDevice property and redirects to the mobile specific page:
protected void Page_Load(object sender, EventArgs e)
{
if (Request.Browser.IsMobileDevice)
{
Response.Redirec("~/default_mobile.aspx");
}
}
If you request "default.aspx" from mobile browser, it will redirect to default_mobile.aspx page
Read here where they explain in detail
Some times this may not work for some tablets
For that you have to use Request.Browser.ScreenPixelsWidth and validate against values of various screen sizes to achieve this . I think that will be the only way left to do it in the server side
If you want to trust client side javascript . There are varous scripts available to do that .
Check this Detect Mobile Browsers which may also help you .
As the other bloke said, you can use Request.Browser.IsMobileDevice
Either in your controller or in your layout page
Razor. You can do this in the layout page and it'll change depending on device e.g.
#if (Request.Browser.IsMobileDevice) {
}
Controller for an individual View e.g.
public ActionResult Index()
{
if (Request.Browser.IsMobileDevice)
return View("Index.Mobile");
else
return View();
}
An alternative is to use this API called 51Degrees.mobi. I personally haven't used it, but it looks pretty good and very complete: http://51degrees.codeplex.com/wikipage?title=MVC
Also, this has some more information on the subject of mobile detection. http://www.asp.net/whitepapers/add-mobile-pages-to-your-aspnet-web-forms-mvc-application

Caching in JavaFX WebView

We're developing a JavaFX 2.x application, which needs to provide some GIS support. We've come to conclusion that using GoogleMaps via an embedded WebView is the quickest option. The problem with it is that every time our application starts, the corresponding JavaScript libraries are downloaded. This makes development very difficult as it takes a couple of second before anything interactive can be done on the WebView panel.
The first thing that comes to mind is to have some sort of caching like web browsers do in order to store the libraries and read them locally when needed. How can this be achieved with WebView? Are there any alternatives to caching in our case?
Thanx.
The WebView component doesn't provide caching of web resources out of the box. However, it does utilize the java.net stack for network communications. What this means is you can install your own URL Handler that talks to a cache and serves resources from that cache. For example, put something like this block in your main() method before the JavaFX launch call:
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
public URLStreamHandler createURLStreamHandler(String protocol) {
if ( "http".equals(protocol) ) {
return new URLStreamHandler() {
protected URLConnection openConnection(URL u) throws IOException {
if ( isCached(u) ) {
return new CachedStreamConnection(u);
}
return new MyURLConnection(u);
}
};
}
// Don't handle a non-http protocol, so just return null and let
// the system return the default one.
return null;
}
});
Of course the devil is in the details. You should take the cache policies returned by the HTTP headers (like ETags) into consideration when storing a resource in your cache. Another consideration is HTML meta tags. Here is a good resource on caching.
You may also want to consider a cookie management system to complement this cache management system.

Authenticating via oAUTH with providers using Phonegap for Blackberry

We are currently working on the finishing touches of an application which uses Phonegap and have hit some issues with the Blackberry port.
So far, we've been reviewing the content available online and can't find a really finale answer to this. Seems like the "right" way to make and oauth authentication process for either Twitter, Facebook or Foursquare would be to use the ChildBrowser plugin, instantiate a window and then use that to handle the process.
Rightly so, there seems to be a lack of a ChildBrowser plugin for Blackberry. We've been looking so far at a couple of private projects on Github that look like they build/use that capability but we are not sure on how to control the created window.
Most (or all?) of those plugins refer to invoking the native Blackberry browser to handle the URLS, but then how would be manage to work on the callbacks, get the tokens and close the windows since it's another process.
For example, we have this concept code:
function openWindow() {
if (typeof blackberry !== 'undefined') {
app_id = SOMETHING_HERE;
redirect = 'http://www.facebook.com/connect/login_success.html';
url = 'https://graph.facebook.com/oauth/authorizeclient_id='+app_id+'&redirect_uri='+redirect+'&display=touch&scope=publish_stream';
var args = new blackberry.invoke.BrowserArguments(url);
blackberry.invoke.invoke(blackberry.invoke.APP_BROWSER, args);
}
}
Which works for opening the URL, but that's it. Is there a way to get a handle on the window and inject some listener to events? What should be our correct approach?
Thanks!
I am not PhoneGap user, but we did have to handle a very similar scenario - native app invokes the mobile browser to prompt the oAuth flow and then be able to handle a callback to the aative app.
This is possible on the BlackBerry using the BrowserContentProviderRegistry API. You can register your app to be invoked whenever a particular MIME type is returned to the browser. Sounds complicated but its fairly straightforward when all the pieces are in play.
Here is the rough flow -
Native app invokes browser to the oAuth page. This is part is easy and seems like you got this part.
The oAuth redirect needs to go to a URL that you can control. Something like http://mycompany.com/oAuthRedirectHandler.asp.
The oAuthRedirectorHandler.asp has simple code like this (we chose classic ASP but this can be done in PHP or any language, you can also ignore the Android block below) -
<html><body>
<h1>Redirect page</h1>
If you are not re-directed, please open the application manually.
<% strUA = Request.ServerVariables("HTTP_USER_AGENT")
if (InStr(strUA, "BlackBerry")) then
Response.Write("Opening appplication on BlackBerry")
Response.ContentType="application/x-MyCustomApp"
elseif (InStr(strUA, "Android")) then
Response.Write("Opening appplication on Android")
Response.Redirect("MyCustomApp://mycompany.com")
end if %>
</body> </html>
In your BlackBerry code you want a new BrowserContentProvider like this -
final class CustomBrowserProvider extends BrowserContentProvider{
String[] ACCEPT = new String[]{"application/x-MyCustomApp};
String appName;
CustomBrowserProvider(String appName){
this.appName = ApplicationDescriptor.currentApplicationDescriptor().getModuleName();
//cache this appName from the constructor in the invocation code below.
}
public String[] getSupportedMimeTypes() { return ACCEPT;}
public String[] getAccept(RenderingOptions context){return ACCEPT;}
public BrowserContent getBrowserContent( BrowserContentProviderContext context) throws RenderingException {
//this is where the callback happens
//this is happening in a separate process, raise your main app here using the appName that got passed in
//I dont have a sanitized ready to go sample to post here on how to do this, but not too complicated
//as a hint use the ApplicationDescriptor and CodeModuleManager classes
return null;
}
}
Now, in your application initialization, register this new BrowserPlugin like this -
BrowserContentProviderRegistry converterRegistry = BrowserContentProviderRegistry.getInstance();
converterRegistry.register(new CustomBrowserProvider());
Hope this helps. This has worked pretty well for us. The one downside we've had here is that when the user returns to the browser app, they are left with an empty page and there is no good way to close that in the BB.

Resources