I'm trying to get the fallback solution working on mobile but having some issues.
I've the following jsp structure
views
-mobile
--about.jsp
-tablet
--about.jsp
--intermediary.jsp
about.jsp
intermediary.jsp
Currently I don't have a intermediary.jsp in the mobile but I have it in the fallback directory. The resolveViewName within the AbstractDeviceDelegatingViewResolver only fires if the view is null.
public View resolveViewName(String viewName, Locale locale) throws Exception {
String deviceViewName = getDeviceViewName(viewName);
View view = delegate.resolveViewName(deviceViewName, locale);
if (enableFallback && view == null) {
view = delegate.resolveViewName(viewName, locale);
}
if (logger.isDebugEnabled() && view != null) {
logger.deb
The problem I'm having is I can't find any viewResolver to return null. The InternalResourceViewResolver doesn't return null and the UrlBasedViewResolver always returns the view name of /mobile/intermediary.jsp which doesn't exist which in turn throws a 404. Anyone know which resolver I should be using for the fallback solution to work?
Thanks,
look at the source you will find the answer:
public abstract class AbstractUrlBasedView{
...
public boolean checkResource(Locale locale) throws Exception {
return true;
}
...
}
public class InternalResourceView extends AbstractUrlBasedView {
//!!not override the checkResource method!!
}
public class FreeMarkerView extends AbstractTemplateView {
...
#Override
public boolean checkResource(Locale locale) throws Exception {
try {
// Check that we can get the template, even if we might subsequently get it again.
getTemplate(getUrl(), locale);
return true;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("No FreeMarker view found for URL: " + getUrl());
}
return false;
}
catch (ParseException ex) {
throw new ApplicationContextException(
"Failed to parse FreeMarker template for URL [" + getUrl() + "]", ex);
}
catch (IOException ex) {
throw new ApplicationContextException(
"Could not load FreeMarker template for URL [" + getUrl() + "]", ex);
}
}
...
}
so that you may be extends InternalResourceView like below:
public class MyInternalResourceView extends InternalResourceView {
private static final boolean SEP_IS_SLASH = File.separatorChar == '/';
protected File getTemplate(String name, Locale locale) throws IOException {
File source;
if(getServletContext()!=null){
name = getServletContext().getRealPath("")+name;
source = new File( SEP_IS_SLASH ? name : name.replace('/',
File.separatorChar));
}
else{
source = new File( SEP_IS_SLASH ? name : name.replace('/',
File.separatorChar));
}
if (!source.isFile()) {
return null;
}
return source;
}
#Override
public boolean checkResource(Locale locale) throws Exception {
try {
// Check that we can get the template, even if we might subsequently
// get it again.
return getTemplate(getUrl(), locale)!=null;
} catch (IOException ex) {
return false;
}
}
}
and in viewResolver set your view class
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="com.xxxx.MyInternalResourceView" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" p:contentType="text/html;charset=UTF-8" />
Related
I implemented a converter to convert String to UUID and include length validation.
public class StringToUUIDConverter implements Converter<String, UUID> {
public UUID convert(String source){
if(source.length>36){
throw new RuntimeException();
}else{
return ...
}
}
}
At first, I thought I would get a RuntimeException when I passed in a string of length 37. However, I tested it and found that no exception was thrown.
Code snippet of TypeConverterDelegate.convertIfNecessary()
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
ConversionUtils convert all exception to ConversionFailedException :
abstract class ConversionUtils {
#Nullable
public static Object invokeConverter(GenericConverter converter, #Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return converter.convert(source, sourceType, targetType);
}
catch (ConversionFailedException ex) {
throw ex;
}
catch (Throwable ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
}
}
TypeConverterDelegate catches the exception I throw and follows another set of logic to convert String to UUID. Although I've done what I want with InitBinder, what should I do if I really want to throw an exception here?
As described in BalusC's and A. Tijms' book "The Definitive Guide to JSF in Java EE 8" (and similarly coded in Omnifaces) I have constructed the following CustomException handler for exceptions occuring in regular and ajax requests.
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
public CustomExceptionHandlerFactory(ExceptionHandlerFactory wrapped) {
super(wrapped);
}
#Override
public ExceptionHandler getExceptionHandler() {
return new CustomExceptionHandler(getWrapped().getExceptionHandler());
}
private class CustomExceptionHandler extends ExceptionHandlerWrapper {
private CustomExceptionHandler(ExceptionHandler wrapped) {
super(wrapped);
}
#Override
public void handle() throws FacesException {
handleException(FacesContext.getCurrentInstance());
getWrapped().handle();
}
private void handleException(FacesContext fctx) {
Iterator<ExceptionQueuedEvent> it
= getUnhandledExceptionQueuedEvents().iterator();
if (fctx == null
|| fctx.getExternalContext().isResponseCommitted()
|| !it.hasNext()) {
return;
}
Throwable t = it.next().getContext().getException();
Throwable tc = t;
while ((tc instanceof FacesException || tc instanceof ELException)
&& tc.getCause() != null) {
tc = tc.getCause();
}
renderErrorPageView(fctx, t);
it.remove();
while (it.hasNext()) {
it.next();
it.remove();
}
}
private void renderErrorPageView(FacesContext fctx, Throwable t) {
ExternalContext ctx = fctx.getExternalContext();
String uri = ctx.getRequestContextPath()
+ ctx.getRequestServletPath();
Map<String, Object> requestMap = ctx.getRequestMap();
requestMap.put(RequestDispatcher.ERROR_REQUEST_URI, uri);
requestMap.put(RequestDispatcher.ERROR_EXCEPTION, t);
String viewId = "/view/stop_error.xhtml";
Application app = fctx.getApplication();
ViewHandler viewHandler = app.getViewHandler();
UIViewRoot viewRoot = viewHandler.createView(fctx, viewId);
fctx.setViewRoot(viewRoot);
try {
ctx.responseReset();
if (!fctx.getPartialViewContext().isAjaxRequest()) {
ctx.setResponseStatus(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
ViewDeclarationLanguage vdl
= viewHandler.getViewDeclarationLanguage(fctx, viewId);
vdl.buildView(fctx, viewRoot);
fctx.getPartialViewContext().setRenderAll(true);
vdl.renderView(fctx, viewRoot);
fctx.responseComplete();
}
catch (IOException e) {
throw new FacesException(e);
}
finally {
requestMap.remove(RequestDispatcher.ERROR_EXCEPTION);
}
}
}
}
In web.xml I have
<error-page>
<exception-type>javax.faces.application.ViewExpredException</exception-type>
<location>/faces/view/expired.xhtml</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/faces/view/stop_error.xhtml</location>
</error-page>
For regular requests it works like a charm. However, if there is a (Runtime)Exception in an ajax request, I only get the java script alert box that the asynchronous request returned nothing (in Development mode) or nothing (in Production mode). The above code runs fully through, however, the error page is not displayed. I am using Tomcat 9 and Mojarra 2.3.8 under Java 11. What I am doing wrong?
I tested using the composite component described in http://balusc.omnifaces.org/2013/01/composite-component-with-multiple-input.html where I throw an IllegalStateException within the updateDaysIfNecessary method which is triggered by changing the month in the repective drop down box.
I'm trying to validate the information, user is giving at the registration. One of the fields contains the mailadress, which should be validated by looking in the database to confirm, it doesn't exist yet.
Problem is, that if i type an existing mailadress, it will give back a NonUniqueResultException, but also does store the new user with the duplicate mailadress in the database. Don't understand this, beacuse in the JSF-lifecycle after validation fails, it shouldn't go on to the invoke application phase, right?
Here's my code:
mail field in register formular
<b:inputText id="mail" required="true"
requiredMessage="Bitte geben Sie Ihre E-Mail-Adresse an!"
label="E-Mail" placeholder="name#example.com" value="#{registrierenManagedBean.nutzer.mail}">
<f:validator validatorId="mailValidatorRegistrieren"/>
<b:messages for="mail"/>
</b:inputText>
my custom validator
#FacesValidator("mailValidatorRegistrieren")
public class MailValidatorRegistrieren implements Validator {
#EJB
private DAO dao;
private String mail;
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
#Override
public void validate(FacesContext facesContext, UIComponent uiComponent, Object o) throws ValidatorException {
mail = (String)o;
boolean matchesPattern = EMAIL_PATTERN.matcher(mail).find();
if(!matchesPattern)
{
throw new ValidatorException((new FacesMessage("Invalid mail")));
}
if(mail.isEmpty()) {
return;
} else if(validateNutzer(mail)){
throw new ValidatorException(new FacesMessage("mail alredy used"));
} else{
return;
}
}
private boolean validateNutzer(String mail) {
try {
Nutzer n = dao.findNutzerByMail(mail);
return n.getMail().equals(mail);
} catch (NullPointerException e) {
return false;
}
}
}
and the "findNutzerByMail"-method from my DAO
public Nutzer findNutzerByMail(String mail) {
try {
return em.createNamedQuery("findNutzerByMail", Nutzer.class)
.setParameter("mail", mail)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
When the date format is not correct (for example when I manually post 13,02,2018 instead of 13.02.2018 and also other incorrect dates such as 13.02.999) the app crashes. How can I fix it? (the manual input is important, i can`t just disable it).
XHTML:
<rich:calendar enableManualInput="true" datePattern="dd.MM.yyyy"
value="#{myBean.data.myDate}">
<f:converter converterId="mydate"/>
</rich:calendar>
Converter:
#FacesConverter("mydate")
public class LocalDateConverter implements Converter {
private static final DateTimeFormatter formatter;
static {
formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
formatter.withLocale(new Locale("ru"));
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return LocalDate.parse(value, formatter);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
} else if (value instanceof LocalDate) {
return ((LocalDate) value).format(formatter);
} else if (value instanceof LocalDateTime) {
return ((LocalDateTime) value).format(formatter);
} else {
throw new IllegalArgumentException("Value is not java.time.LocaleDate");
}
}
Converters should throw a ConverterException which can contain a FacesMessage. This message can be displayed on your XHTML page, near the input component that caused the exception using <h:message for="inputComponentId"/>.
The problem occurs in your getAsObject method. There you should catch the DateTimeParseException exception and throw a ConverterException:
try {
return LocalDate.parse(value, formatter);
}
catch (DateTimeParseException ex) {
throw new ConverterException(new FacesMessage("Invalid date: " + value));
}
See also:
https://docs.oracle.com/javaee/7/tutorial/jsf-custom010.htm
How to use java.time.ZonedDateTime / LocalDateTime in p:calendar
You don't need converter at all. Simply include label attribute in rich:calendar component and let system figure out if value is correct. Example:
<h:outputLabel for="programStartDate" value="#{msg.programStartDate}" />
<rich:calendar id="programStartDate" value="#{program.programStartDate}"
label="#{msg.programStartDate}" inputStyle="width: 100px;"
datePattern="#{referenceData.defaultDatePattern}"
timeZone="#{referenceData.timezone}"
enableManualInput="true" popup="true" required="true" />
use a try catch and catch the exception so it doesn't crash but continue without allowing the exception to crash your program
#FacesConverter("mydate")
public class LocalDateConverter implements Converter
{
private static final DateTimeFormatter formatter;
static {
formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
formatter.withLocale(new Locale("ru"));
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value)
{
return LocalDate.parse(value, formatter);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
try
{
if (value == null)
{
return "";
}
else if (value instanceof LocalDate) {
return ((LocalDate)value).format(formatter);
} else if (value instanceof LocalDateTime) {
return ((LocalDateTime)value).format(formatter);
} else {
throw new IllegalArgumentException("Value is not java.time.LocaleDate");
}
}
catch (Exception)
{
return "SOME DEFAULT DATE";
}
}
}
I hava a POJO object with one BigDecimal field sum.
In controller I add this POJO object as form like this:
MyForm form = new MyForm();
model.addAttribute("command", form);
My jsp:
<form:input path="sum" size="27"/>
In controller i add initbinder:
binder.registerCustomEditor(BigDecimal.class, new SumEditor());
Part of my SumEditor class:
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(parseMoney(text));
}
private BigDecimal parseMoney(String str) {
try {
return new BigDecimal(str);
} catch (Exception e) {
logger.error("error", e);
}
return null;
}
But in JSP view I see (in input field): |null________|
How fix this? I need: |___________|
You should simply override getText method of SumEditor to have it return an empty string ("") for a null value :
#Override
public String getAsText() {
if (getValue == null) {
return "";
}
BigDecimal val = (BigDecimal) getValue();
return val.toStr(); // or whatever conversion you need
}