- Java 6 "przyłożyła" adnotację @Deprecated do metody File.toURL() z komentarzem, że metoda nie dbała o poprawość zwracanego URL, który mógł zawierać niedozwolone znaki, np. #, &, {, + czy ?.
- Jako poprawne wywołanie tej metody wskazano na właśnie File.toURI().toURL()
W ten sposób wykonanie poniższego kawałka kodu:
String sciezka = "że#to&nie{powinno+działać?";zwróci:
File pathFile = new File(sciezka);
System.out.println("1. " + pathFile.toURL());
System.out.println("2. " + pathFile.toURI().toURL());
1. file:/C:/sandbox/że#to&nie{powinno+działać?Jak można zauważyć pierwszy z URLi jest niepoprawny i próba otwarcia takiego adresu spowoduje wyjątek. Dodatkowo dokumentacja java.net.URL również wskazuje na klasę java.net.URI, właśnie ze względu na brak dbałości o poprawność zwracanych adresów URL. Więcej informacji o niedozwolonych znakach w adresie (ze względu na szczególne ich znaczenie) można znaleźć w RFC 2396: Uniform Resource Identifiers (URI): Generic Syntax w sekcji 2.4.3. Excluded US-ASCII Characters.
2. file:/C:/sandbox/że%23to&nie%7Bpowinno+działać%3F
W trakcie lektury książki Wicket in Action natrafiłem na wzmianki o wsparciu Ajaxa. Dla ustalenia uwagi moją znajomość JavaScript i innych technologii klienckich określiłbym bliską zeru, więc każdorazowe wsparcie ze strony szkieletów aplikacyjnych w tym zakresie witanych jest przeze mnie z wielkim entuzjazmem. Tak jest z JavaServer Faces, i tak jest z Apache Wicket. Oba rozwiązania mają swoje zalety, gdzie podstawową zaletą JSF podczas porównania z Wicket jest możliwość uruchamiania JSP i jego znaczników, gdzie właśnie to jest podnoszone jako zaleta Wicketa, w którym użycie JSP jest niezwykle utrudnone, jeśli w ogóle w pełni możliwe. Zwróciłem na to uwagę podczas wykorzystania znaczników jpivot dla kostek OLAP, gdzie musiałem zwizualizować kilka takich struktur i jedynym sposobem mogło być wykorzystanie JSF.
Wracając do Wicketa, bo o nim chciałem wspomnieć i jego wsparciu Ajaxa, uruchomienie wstawek ajaksowych sprowadza się do wykorzystania odpowiedników ajaksowanych dla kontrolek akcyjnych - łącze (ang. link) czy przycisk (ang. button), np. org.apache.wicket.ajax.markup.html.AjaxFallbackLink dla org.apache.wicket.markup.html.link.Link (de facto AjaxFallbackLink rozszerza Link) oraz org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton dla org.apache.wicket.markup.html.form.Button. Zaletą stosowania typów AjaxFallback* jest jednoczesna obsługa żądań ajaksowych i nieajaksowych, zlecając Wicketowi decyzję jaką obsługę wybrać do możliwości klienta (przeglądarki).
Dla zniecierpliwionych, podaję 5-minutowy "przepis" na ajaksową aplikację z Wicket. Jedyne co potrzeba, to Apache Maven 2 (dalej m2) oraz Eclipse IDE z wtyczką M2Eclipse, do pracy z projektami kontrolowanymi przez m2.
1. Utworzenie projektu wicket-ajax-demo
$ mvn archetype:create \2. Import projektu do Eclipse
-DarchetypeGroupId=org.apache.wicket \
-DarchetypeArtifactId=wicket-archetype-quickstart \
-DarchetypeVersion=1.4-m1 \
-DgroupId=pl.jaceklaskowski.wicket \
-DartifactId=wicket-ajax-demo
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:create] (aggregator-style)
[INFO] ------------------------------------------------------------------------
...
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: wicket-archetype-quickstart:1.4-m1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: packageName, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: basedir, Value: c:\projs\sandbox
[INFO] Parameter: package, Value: pl.jaceklaskowski.wicket
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: wicket-ajax-demo
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] OldArchetype created in dir: c:\projs\sandbox\wicket-ajax-demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Importuję projekt wicket-ajax-demo do Eclipse z pomocą File > Import > Maven Projects.
3. Podniesienie wersji Wicket do 1.4-m1 w pom.xml
Poprawiam pom.xml o podniesienie wersji Wicket do 1.4-m1.
<wicket.version>1.4-m1</wicket.version>Eclipse zatroszczy się pobraniem wymaganych zależności projektowych. Dobrze być wtedy w Sieci, aby mogły być pobrane. Zaleca się skorzystanie z menu Maven > Download Sources, aby Eclipse pobrał źródła zależności, w tym i Wicketa z Sieci, co umożliwi podejrzenie jak to działa bezpośrednio w kodzie.
Podniesienie wersji spowoduje, że Eclipse oznaczy kilka klas jako błędne, co będzie wymagało kolejnego uaktualnienia pom.xml o wersję kompilatora do 1.5 za pomocą konfiguracji wtyczki maven-compiler-plugin.
<plugin>Po zmianie najlepiej należy ponownie zaimportować projekt (wcześniej go usuwając) bądź urchomić polecenie mvn eclipse:eclipse z linii poleceń w katalogu projektu i F5 (Refresh) w Eclipse.
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
4. Modyfikacja strony domowej aplikacji - HomePage.html
<html>5. Modyfikacja strony domowej aplikacji - pl.jaceklaskowski.wicket.HomePage
<head>
<title>Wicket Quickstart Archetype Homepage</title>
</head>
<body>
<strong>Wicket Quickstart Archetype Homepage</strong>
<br/><br/>
<span wicket:id="message">message will be here</span>
<br>
<a href="#" wicket:id="link">Wciśnij mnie</a>, a napis wyżej się zmieni.
</body>
</html>
Dodanie wicketowego elementu do strony HTML wymaga odpowiedniej zmiany w klasie strony.
package pl.jaceklaskowski.wicket;Sprawdzenie target != null jest konieczne dla sytuacji, w której żadanie było nieajaksowe (klient nie wspiera Ajaxa).
import java.util.Calendar;
import java.util.Locale;
import org.apache.wicket.PageParameters;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.util.convert.IConverter;
public class HomePage extends WebPage {
private static final long serialVersionUID = 1L;
private Label label;
private Link link;
public HomePage(final PageParameters parameters) {
label = new Label("message", "If you see this message wicket is properly configured and running");
label.setOutputMarkupId(true);
add(label);
add(new AjaxFallbackLink("link") {
@Override
public void onClick(AjaxRequestTarget target) {
label.getModel().setObject("Wcisnieto mnie o " + Calendar.getInstance(new Locale("pl")).getTime());
if (target != null) {
target.addComponent(label);
}
}
});
}
}
Konieczne jest również wywołanie metody label.setOutputMarkupId(true), której brak spowoduje:
java.lang.IllegalArgumentException: cannot update component that does not have setOutputMarkupId property set to true.podczas uruchomienia aplikacji bez tego wywołania.
Component: [Component id = message, page = pl.jaceklaskowski.wicket.HomePage, path = 1:message.Label, isVisible = true, isVersioned = true]
at org.apache.wicket.ajax.AjaxRequestTarget.addComponent(AjaxRequestTarget.java:343)
at pl.jaceklaskowski.wicket.HomePage$1.onClick(HomePage.java:31)
at org.apache.wicket.ajax.markup.html.AjaxFallbackLink$1.onEvent(AjaxFallbackLink.java:73)
at org.apache.wicket.ajax.AjaxEventBehavior.respond(AjaxEventBehavior.java:161)
at org.apache.wicket.ajax.AbstractDefaultAjaxBehavior.onRequest(AbstractDefaultAjaxBehavior.java:298)
at org.apache.wicket.request.target.component.listener.BehaviorRequestTarget.processEvents(BehaviorRequestTarget.java:100)
at org.apache.wicket.request.AbstractRequestCycleProcessor.processEvents(AbstractRequestCycleProcessor.java:91)
at org.apache.wicket.RequestCycle.processEventsAndRespond(RequestCycle.java:1174)
at org.apache.wicket.RequestCycle.step(RequestCycle.java:1251)
at org.apache.wicket.RequestCycle.steps(RequestCycle.java:1352)
at org.apache.wicket.RequestCycle.request(RequestCycle.java:499)
at org.apache.wicket.protocol.http.WicketFilter.doGet(WicketFilter.java:375)
at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:199)
...
6. Uruchomienie aplikacji z mvn jetty:run
Ostatecznie należy sprawdzić poprawność wprowadzonych zmian uruchamiając aplikację z wybranym kontenerem servletów. Jako, że korzystam z m2 stawiam na Jetty, którego uruchomienie i uruchomienie aplikacji webowej sprowadza się do wykonania polecenia mvn clean jetty:run (dodałem clean dla zapewnienia, że klasy zostały skompilowane w odpowiedniej wersji javy).
$ mvn clean jetty:runPrzechodzę na adres http://localhost:8080/wicket-ajax-demo.
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ------------------------------------------------------------------------
[INFO] Building quickstart
[INFO] task-segment: [clean, jetty:run]
[INFO] ------------------------------------------------------------------------
...
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: quickstart
[INFO] Webapp source directory = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp
[INFO] web.xml file = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp\WEB-INF\web.xml
[INFO] Classes = C:\projs\sandbox\wicket-ajax-demo\target\classes
2008-05-29 23:09:58.908::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /wicket-ajax-demo
[INFO] Tmp directory = determined at runtime
[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] Webapp directory = C:\projs\sandbox\wicket-ajax-demo\src\main\webapp
[INFO] Starting jetty 6.1.9 ...
2008-05-29 23:09:58.987::INFO: jetty-6.1.9
2008-05-29 23:09:59.112::INFO: No Transaction manager found - if your webapp requires one, please configure one.
INFO - Application - [WicketApplication] init: Wicket core library initializer
...
INFO - WebApplication - [WicketApplication] Started Wicket version 1.4-m1 in development mode
********************************************************************
*** WARNING: Wicket is running in DEVELOPMENT mode. ***
*** ^^^^^^^^^^^ ***
*** Do NOT deploy to your live server(s) without changing this. ***
*** See Application#getConfigurationType() for more information. ***
********************************************************************
2008-05-29 23:09:59.924::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Wciśnięcie łącza Wciśnij mnie spowoduje zmianę napisu na zawierający datę wykonania obsługi wciśnięcia.
Niewielka aplikacja, a jak cieszy. I ta prostota Wicketa w kontekście obsługi Ajaxa - zero JavaScripu czy podobnie (!)
Pytanie konkursowe: Jaka jest rola typów AjaxFallback{Link,Button} w Wicket?
Do zobaczenia na JAVArsovii w nadchodzącą sobotę 31.05.2008 w godzinach 9:00-19:00, gdzie podczas mojej prezentacji o Apache Wicket pokaże tą i inne aplikacje webowe z nim na pokładzie. Jutro audycja w Polskim Radio EURO o 9:00. Ciekawym Waszych opinii odnośnie naszego radiowego występu (więcej o nim we wczorajszej notatce 3 dni do JAVArsovii 2008, audycja w Polskim Radiu, @Override i skróty netbeansowe). Wracam do lektury książki Wicket in Action.
Większość naszych developerów Javy używa Uniksów. Kilka lat temu testowaliśmy pierwszą werjsę naszego produktu i ku naszemu wielkiemu zaskoczeniu nie działała ona pod Windows (docelowa platforma serwerowa klienta).
OdpowiedzUsuńBardzo długo szukaliśmy przyczyny problemu (chwile zwątpienia w przenośność Javy :) ), aż w końcu okazało się, że problemem nie jest specyfika Windows, lecz spacja w nazwie katalogu. :) Zamiast przyjemnych "bezspacyjnych" katalogów Uniksowych jak /home czy /usr/local, w przypadku Windows mamy "Program Files" albo "Documents and Settings", w dodatku w formach uzależnionych od lokalizacji systemu - zgroza :). Zresztą nie tylko my mieliśmy ten problem - jboss zainstalowany w "c:\Program Files\jboss" też podówczas nie działał.
Przyczyną błędu okazał się właśnie problem o którym piszesz i w tym miejscu chciałbym uściślić - nie chodzi o to, że pewne znaki w nazwach plików są niedozwolone, co nie jest sprawdzane w metodzie toURL(), lecz o to, że w rezultacie wywołania tej metody znaki te nie są "escapowane" zgodnie z RFC, co jest ewidentnym błędem. Najlepszy przykład stanowi tutaj właśnie spacja - doskonale dozwolona jako element nazwy pliku, nawet przez Microsoft :)
Nasz konkretny problem związany był z używaniem RMIClassLoader, który standardowo "karmił się" URLami przechowanymi przez nasz własny URLClassLoader. URLClassLoader ładował klasy bez problemu mimo tego błędu, ale RMIClassLoader już nie. A oto właściwy bug report. Sun niestety nie zamierza nic z tym robić, by zachować kompatybilność w dół z kodem który bazuje na tym niepoprawnym działaniu (np. właśnie jak mi się zdaje URLClassLoader) :).