Статьи

Обновление до JSF 2


На прошлой неделе я потратил несколько часов на обновление
AppFuse с JSF 1.2 до JSF 2.0. На самом деле я обновил
MyFaces 1.2.7 до 2.0.4, но все реализации JSF должны быть одинаковыми, верно? В целом, это было довольно простое обновление с несколькими незначительными специфическими для AppFuse вещами. Моя цель в обновлении состояла в том, чтобы сделать все возможное, чтобы все заработало, и оставить интеграцию функций JSF 2 на более поздний срок.

В дополнение к обновлению MyFaces мне пришлось обновить Tomahawk, изменив artifactId зависимости на tomahawk20 . Я также смог удалить следующий слушатель из моего web.xml:

<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
<listener>

После этого я обнаружил, что MyFaces использует новый URI (/javax.faces.resource/) для обслуживания некоторых своих файлов ресурсов. Я любезно попросил Spring Security игнорировать эти запросы, добавив следующее в мой файл security.xml.

<intercept-url pattern="/javax.faces.resource/**" filters="none"/>

Поскольку JSF 2 по умолчанию включает Facelets, я попытался удалить Facelets в качестве зависимости. После этого я получил следующую ошибку:

ERROR [308855416@qtp-120902214-7] ViewHandlerWrapper.fillChain(158) | Error instantiation parent Faces ViewHandler
java.lang.ClassNotFoundException: com.sun.facelets.FaceletViewHandler
        at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:50)
        at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:244)
        at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:230)
        at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:401)
        at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:363)
        at org.ajax4jsf.framework.ViewHandlerWrapper.fillChain(ViewHandlerWrapper.java:144)
        at org.ajax4jsf.framework.ViewHandlerWrapper.calculateRenderKitId(ViewHandlerWrapper.java:68)
        at org.apache.myfaces.lifecycle.DefaultRestoreViewSupport.isPostback(DefaultRestoreViewSupport.java:179)
        at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute(RestoreViewExecutor.java:113)
        at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:171)
        at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:189)

Это было вызвано следующим элементом в моем файле web.xml …

<context-param>
<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
<param-value>com.sun.facelets.FaceletViewHandler</param-value>
</context-param>

… Я удалил его и попробовал еще раз. На этот раз я получил NoClassDefFoundError:

java.lang.NoClassDefFoundError: com/sun/facelets/tag/TagHandler
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:392)
        at org.mortbay.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:363)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:247)
        at org.apache.myfaces.shared_impl.util.ClassUtils.classForName(ClassUtils.java:184)
        at org.apache.myfaces.view.facelets.util.ReflectionUtil.forName(ReflectionUtil.java:67)

Поскольку казалось, что все работает с Facelets в пути к классам, я решил сохранить эту головную боль на более поздний срок. Я ввел две проблемы в JIRA AppFuse: одну для удаления Facelets и одну для замены Ajax4JSF на RichFaces .

Следующей проблемой, с которой я столкнулся, было перенаправление со страницы подсказки пароля AppFuse. Правило навигации для этой страницы следующее:

navigation-rule>
<from-view-id>/passwordHint.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/login</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>

В JSF 2.0 правило изменяет URL-адрес на /login.xhtml при перенаправлении (где оно было оставлено как / login с 1.2), и оно было обнаружено настройкой безопасности в моем web.xml, которая запрещает пользователям просматривать необработанные шаблоны.

<security-constraint>
<web-resource-collection>
<web-resource-name>Protect XHTML Templates</web-resource-name>
<url-pattern>*.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>

Чтобы решить эту проблему, мне пришлось внести пару изменений:

  • Закомментируйте ограничение безопасности в файле web.xml и переместите его в файл security.xml Spring Security.

    <intercept-url pattern="/**/*.xhtml" access="ROLE_NOBODY"/>

  • Добавьте правило в urlrewrite.xml, которое перенаправляет обратно на страницу входа (так как login.xhtml не существует, и я использую URL без расширений).

    <rule match-type="regex">
    <from>^/login.xhtml$</from>
    <to type="redirect">%{context-path}/login</to>
    </rule>

Получив функцию подсказки пароля в браузере, я попытался запустить интеграционные тесты (на основе Canoo WebTest ). Тест подсказки пароля не удался со следующей ошибкой:

[ERROR] /Users/mraible/dev/appfuse/web/jsf/src/test/resources/web-tests.xml:51: JavaScript error loading
page http://localhost:9876/appfuse-jsf-2.1.0-SNAPSHOT/passwordHint?username=admin: syntax error (http://
localhost:9876/appfuse-jsf-2.1.0-SNAPSHOT/javax.faces.resource/oamSubmit.js.jsf?ln=org.apache.myfaces#122)

Понимая, что это было вызвано тем, что мой хакер отправил форму при загрузке страницы , я переключился на Pretty Faces , что позволяет вам вызывать метод непосредственно из URL. После добавления зависимостей Pretty Faces в мой pom.xml я создал файл src / main / webapp / WEB-INF / pretty-config.xml со следующим XML:

<url-mapping>
<pattern value="/editProfile"/>
<view-id value="/userForm.jsf"/>
<action>#{userForm.edit}</action>
</url-mapping>

<url-mapping>
<pattern value="/passwordHint/#{username}"/>
<view-id value="/passwordHint.jsf"/>
<action>#{passwordHint.execute}</action>
</url-mapping>

Это позволило мне удалить и editProfile.xhtml, и passwordHint.xhtml, оба из которых просто автоматически отправляли формы.

В этот момент я решил, что мне будет хорошо пойти и снова запустить интеграционные тесты. Первое, что я обнаружил, было то, что «.jsf» был добавлен в мой красивый URL, скорее всего, с помощью UrlRewriteFilter. Добавление следующего в мой класс PasswordHint.java решило эту проблему.

if (username.endsWith(".jsf")) {
username = username.substring(0, username.indexOf(".jsf"));
}

Следующей вещью была загадочная ошибка, на которую мне понадобилось время, чтобы понять.

DEBUG [1152467051@qtp-144702232-0] PasswordHint.execute(38) | Processing Password Hint...
2011-03-05 05:48:52.471:WARN::/passwordHint/admin
com.ocpsoft.pretty.PrettyException: Exception occurred while processing <:#{passwordHint.execute}> null
        at com.ocpsoft.pretty.faces.beans.ActionExecutor.executeActions(ActionExecutor.java:71)
        at com.ocpsoft.pretty.faces.event.PrettyPhaseListener.processEvent(PrettyPhaseListener.java:214)
        at com.ocpsoft.pretty.faces.event.PrettyPhaseListener.afterPhase(PrettyPhaseListener.java:108)
        at org.apache.myfaces.lifecycle.PhaseListenerManager.informPhaseListenersAfter(PhaseListenerManager.java:111)
        at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:185)
        at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:189)

Покопавшись в недрах MyFaces, я обнаружил, что класс искал viewId с расширением, и никакой view-id не устанавливался. Добавление следующего в начало метода execute () решило эту проблему.

getFacesContext().getViewRoot().setViewId("/passwordHint.xhtml");

После внесения этого изменения все интеграционные тесты AppFuse проходят, и обновление кажется завершенным. Единственные другие проблемы, с которыми я столкнулся, были связаны с журналированием. Первая — ошибка в томагавке, которая, похоже, ни на что не влияет.

Mar 5, 2011 6:44:01 AM com.sun.facelets.compiler.TagLibraryConfig loadImplicit
SEVERE: Error Loading Library: jar:file:/Users/mraible/.m2/repository/org/apache/myfaces/tomahawk/tomahawk20/1.1.10/tomahawk20-1.1.10.jar!/META-INF/tomahawk.taglib.xml
java.io.IOException: Error parsing [jar:file:/Users/mraible/.m2/repository/org/apache/myfaces/tomahawk/tomahawk20/1.1.10/tomahawk20-1.1.10.jar!/META-INF/tomahawk.taglib.xml]: 
        at com.sun.facelets.compiler.TagLibraryConfig.create(TagLibraryConfig.java:410)
        at com.sun.facelets.compiler.TagLibraryConfig.loadImplicit(TagLibraryConfig.java:431)
        at com.sun.facelets.compiler.Compiler.initialize(Compiler.java:87)
        at com.sun.facelets.compiler.Compiler.compile(Compiler.java:104)

Второе — чрезмерная регистрация в MyFaces. Насколько я могу судить, это потому, что MyFaces переключился на java.util.logging вместо общего журнала. Я думаю, что со всеми фреймворками, которые использует AppFuse, теперь у него есть все фреймворки журналирования в своем classpath. Я надеялся исправить это, отправив сообщение в список рассылки , но пока не получил ответа.

[WARNING] [talledLocalContainer] Mar 5, 2011 6:50:25 AM org.apache.myfaces.config.annotation.TomcatAnnotationLifecycleProvider newInstance
[WARNING] [talledLocalContainer] INFO: Creating instance of org.appfuse.webapp.action.BasePage
[WARNING] [talledLocalContainer] Mar 5, 2011 6:50:25 AM org.apache.myfaces.config.annotation.TomcatAnnotationLifecycleProvider destroyInstance
[WARNING] [talledLocalContainer] INFO: Destroy instance of org.appfuse.webapp.action.BasePage

После успешного обновления AppFuse я переключился на AppFuse Light, где все было намного проще .

Теперь, когда AppFuse использует JSF 2, я надеюсь начать использовать некоторые из его
новых функций . Если вы хотите начать с ними сегодня, я приглашаю вас
взять исходный код и начать интегрировать их самостоятельно.

С http://raibledesigns.com/rd/entry/upgrading_to_jsf_2