Java

...now browsing by category

Java

 

Spring MVC Framework 3.0.1, Indexed Properties

Mittwoch, April 21st, 2010

Achtung, wer noch springframework 3.0.1 verwendet: Hier gibt’s einen schweren Bug, was Indexed Properties betrifft. Formulardaten mit Index wie:


<form:input path="myArray[0]"/>

werden nicht korrekt submitted: http://jira.springframework.org/browse/SPR-6871

Also schleunigst auf 3.0.2 updaten.

Hat mich gerade 4h gekostet.

IBatis Tipps (I): Enumerations

Dienstag, Juni 30th, 2009

Ich verwende gerne Java Enumerations, wenn ein Datenbankfeld nur eine bestimmte Anzahl an Typen enthalten kann . Ein Beispiel wäre eine Tabelle CONTACTS (ich gebe mal nicht alle Felder an) mit dem Feld “TYPE”, das PRIVATE,BUSINESS oder OTHER enthalten kann:


CREATE TABLE CONTACTS (
	ID INTEGER NOT NULL,
        TYPE AS VARCHAR(24),
        NAME VARCHAR(50) NOT NULL,
        .
        .
        .
	PRIMARY KEY (ID)
);

Für das Datenbankfeld TYPE erstelle ich folgende Enumeration:
Nehmen wir mal an, wir haben folgende Enumeration:


public enum ContactType {
     PRIVATE, BUSINESS, OTHER
}

Und dann noch folgendes Modell:


package de.binnichda.comp;
public class Contact {
   private Long id;
   private ContactType type;
   private String name;

   /* Und natürlich die zugehörigen Getter/Setter ... */
}

Noch kann man jetzt leider noch nicht viel mit der Enumeration anfangen, denn IBatis muss noch wissen, wie es diesen Typen behandeln soll. Dazu muss man eine Klasse erstellen, die “TypeHandlerCallback” implementiert. Mit Java 5 bietet es sich an, das ganze generisch zu erschlagen:


package de.binnichda.comp;
@SuppressWarnings("unchecked")
public class GenericEnumHandlerCallback <E extends Enum> implements TypeHandlerCallback {

   private Class<E> enumClass;

   public GenericEnumHandlerCallback(Class<E> enumClass) {
      this.enumClass = enumClass;
   }

   private Class<E> getEnumClass() { return enumClass; }

   public void setParameter(ParameterSetter setter, Object parameter) throws SQLException {
      setter.setString(parameter.toString());
   }

   public Object getResult(ResultGetter getter) throws SQLException {
      return valueOf(getter.getString());
   }

   public Object valueOf(String s) {

      if (s == null)
         return null;
      else
         return Enum.valueOf(getEnumClass(), s);
   }

}

Mein konkreter ContactTypeHandler muss dann folgendermaßen aussehen:


package de.binnichda.comp;
public class ContactTypeHandlerCallback extends
      GenericEnumHandlerCallback<ContactType> {

   public ContactTypeHandlerCallback() {
      super(ContactType.class);
   }
}

Jetzt noch den TypeHandlerCallback IBatis bekannt geben:


<typeHandler javaType="de.binnichda.comp.ContactType" callback="de.binnichda.comp.ContactTypeHandlerCallback"/>

Fertig, die Enumeration kann verwendet werden:


Contact contact = new Contact();
contact.setName("Felix Meinhold");
contact.setType(ContactType.PRIVATE);

contactDao.save(contact);

Oder folgendes IBatis Mapping, um mehrere Contact Typen abzufragen:


    <select id = "findByContacts"  parameterClass="map" resultMap = "contactMap">
        SELECT * FROM CONTACTS
        <isNotEmpty property="contactTypes" prepend="WHERE TYPE_ IN ">
            <iterate property="contactTypes"
             open="(" close=")"
             conjunction=",">#contactTypes[]#</iterate>
        </isNotEmpty>
    </select>

Streifen machen schlank – Stripes, das Java Web Framework (2)

Montag, Oktober 20th, 2008

Update: Bitte den Artikel kommentieren, ob er hilfreich oder nicht hilfreich war

[Fortsetzung von Streifen machen schlank - Stripes, das Java Web Framework (1)]

Eine erste ActionBean: Login in eine Applikation

Für die Beispielanwendung verkneife ich mir mal das typische Hello World oder ähnliches. Das hat mir persönlich noch nie geholfen. Meist merke ich, wie passend ein Framework für ein Projekt ist, wenn ich damit beginne, grundsätzliche Funktionen wie Login/Logout, Paging, Ajax uvm. umzusetzen – und da ist’s meist schon zu spät, sich noch einmal umzuentscheiden.

Eine ActionBean ist ein Java Objekt, das die Daten eines Requests erhält und die Eingaben des Benutzers verarbeitet. Diese ActionBean entspricht, wenn man so will, der ActionForm und dem Action aus Struts kombiniert in einer einzigen Klasse. Allerdings muß die ActionBean nirgendwo konfiguriert werden (bzw. wir haben das durch den Parameter ActionResolver.packages bereits für alle ActionBeans getan).

Vorbereitung: Login und Security

Für ein typisches Login nehmen wir mal folgenden Ablauf an:

  • Prüfen, ob der Benutzer bereits angemeldet ist
  • Wenn ja, Seite anzeigen
  • Wenn nicht, dann auf die LoginPage umleiten
  • Nutzer gibt seine Daten ein
  • Applikation prüft, ob gültig
  • Wenn ja, dann weiter zur Standardansicht oder zu der zuletzt betrachteten Seite
  • Wenn nein, Login-Seite mit Fehlern anzeigen

Da Stripes versucht (-und meiner Meinung nach gelingt das auch vortrefflich), ein leichtgewichtiges, intuitives und nach allen Seiten offenes Framework zu sein, ist kein Sicherheitskonzept implementiert.
Es bietet sich deshalb hier der Einfachheit halber an, einen ServletFilter zu implementieren, der prüft, ob der User angemeldet ist, die Weiterleitungen übernimmt und den User als Attribut in der Session speichert (Die Implementierung nimmer sich den SecurityFilter aus der Bugzooky Stripes Beispielanwendung zum Vorbild):


package personal.web.servlet;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sourceforge.stripes.util.StringUtil;

import org.apache.log4j.Logger;

public class SecurityFilter implements Filter {
   private static final Logger log = Logger.getLogger(SecurityFilter.class
         .getName());

   public void destroy() {
   }

   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
         FilterChain filterChain) throws IOException, ServletException {

      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;

      if (request.getSession().getAttribute("user") != null) {
          filterChain.doFilter(request, response);
      }
      else if ( isPublicResource(request) ) {
          filterChain.doFilter(request, response);
      }
      else {
         if (log.isDebugEnabled()) {
            log.debug(request.getServletPath() + " -> Redirecting to Login.");
         }

         String targetUrl = StringUtil.urlEncode(request.getServletPath());

         if (request.getServletPath().startsWith("/View.action"))
            targetUrl += "?" + request.getQueryString();

          // Redirect the user to the login page, noting where they were coming from
          response.sendRedirect(
                  request.getContextPath() + "/Login.jsp?targetUrl=" + targetUrl);
      }

   }

   public void init(FilterConfig config) throws ServletException {
   }

   protected boolean isPublicResource(HttpServletRequest request) {
       String resource = request.getServletPath();

       return publicUrls.contains(resource)
               || (!resource.endsWith(".jsp") &amp;&amp; !resource.endsWith(".action"))
               || (resource.startsWith("/errors/") || (resource.startsWith("/layout/")));
   }

   private static Set<String> publicUrls = new HashSet<String>();

   static {
       publicUrls.add("/Login.jsp");
       publicUrls.add("/Login.action");
       publicUrls.add("/Logout.jsp");
       publicUrls.add("/Logout.action");

   }
}

Und noch der nötige Eintrag in der web.xml:


<filter>
   <description>Handles Security</description>
   <filter-name>SecurityFilter</filter-name>
   <filter-class>de.binnichda.comp.stripes.web.SecurityFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>SecurityFilter</filter-name>
   <url-pattern>/*</url-pattern>
   <dispatcher>REQUEST</dispatcher>
</filter-mapping>

ActionBean und ActionBeanContext

Jede Stripes ActionBean implementiert das Interface ActionBean, das nur zwei Methoden verlangt, mit denen auf den sogenannten ActionBeanContext zugegriffen werden kann.

Der ActionBeanContext steht also jeder ActionBean zur Verfügung. Es bietet sich somit an, eine eigene, von ActionBeanContext abgeleitete Klasse zu entwickeln, die uns einen einfachen Zugriff auf den Context der Applikation und die Session bietet:


package de.binnichda.comp.stripes.web;

import net.sourceforge.stripes.action.ActionBeanContext;
import de.binnichda.comp.stripes.model.User;

public class ApplicationActionBeanContext extends ActionBeanContext {
   /**
    * Gibt den momentan angemeldeten User zurück, oder null wenn noch nicht
    * angemeldet.
    */

   public User getUser() {
      return (User) getRequest().getSession().getAttribute("user");
   }

   /** Speichert den angemeldeten User */
   public void setUser(User currentUser) {
      getRequest().getSession().setAttribute("user", currentUser);
   }

   /** Meldet den User ab und erklärt die aktuelle Session auf ungültig */
   public void logout() {
      getRequest().getSession().invalidate();
   }
   // ...
}

Da diese Klasse von Stripes instantiert und in der ActionBean gesetzt wird, muss sie noch in web.xml konfiguriert werden:


<init-param>
  <param-name>ActionBeanContext.Class</param-name>
  <param-value>
     de.binnichda.comp.stripes.web.ApplicationActionBeanContext
  </param-value>
</init-param>

Um nun den eigenen Context ApplicationActionBeanContext verwenden zu können, sollten wir eine Basisklasse BaseActionBean erstellen, die ActionBean implementiert und eben diesen Context zurückliefert:


package de.binnichda.comp.stripes.web;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;

public class BaseActionBean implements ActionBean {
   public ApplicationActionBeanContext getContext() {
      return this.context;
   }
   public void setContext(ActionBeanContext context) {
      this.context = (ApplicationActionBeanContext) context;
   }
   private ApplicationActionBeanContext context;
}

Das Eingemachte: LoginActionBean und Login.jsp

Die Action Bean

Der Entwickler legt nun eine Klasse “LoginActionBean.java” im package de.binnichda.comp.stripes.weban. Der Name kann standardmässig nicht frei gewählt werden, denn Stripes erzeugt aus dem vollständigen Namen der Klasse eine URL.

Die ActionBean hat zwei Properties, nämlich username und password, auf die über Getter-/Settermethoden zugegriffen werden kann und eine Methode “login”, die automatisch von Stripes identifiziert und ausgeführt wird, wenn ein Request für diese ActionBean eintrifft und der User im Browser einen Absendebutton (oder -image) mit dem Namen “login” gedrückt hat. Die Methode Login gibt eine Resolution zurück, also wohin Stripes nach der Ausführung hin verweisen soll.

Ein wichtiger Aspekt fehlt allerdings noch: Wie kann gewährleistet werden, dass username und passwort überhaupt gesetzt werden? Und wie informiere ich den User darüber, dass seine Eingaben fehlerhaft sind?

In Stripes ist das ganz einfach: Es müssen nur die Felder mit der Annotation @Validate und der Art der Validierung gekennzeichnet werden, die von Stripes überprüft werden sollen. Im Beispiel wird nur geprüft, ob username und password überhaupt gesetzt sind (required=true):


package de.binnichda.comp.stripes.web;

import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.validation.SimpleError;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidationError;
import de.binnichda.comp.stripes.model.User;

public class LoginActionBean extends BaseActionBean {

   public Resolution login() {

      if (!username.equals("freund")) {
         ValidationError error = new SimpleError("Unbekannter Benutzer {0}", username);
         getContext().getValidationErrors().add("username", error);
         return getContext().getSourcePageResolution();
      }

      if (!password.equals("geheim")) {
         ValidationError error = new SimpleError("Falscher Benutzer oder Kennwort");
         getContext().getValidationErrors().addGlobalError(error);
         return getContext().getSourcePageResolution();
      }

      User user = new User();

      user.setUsername(username);
      user.setPassword(password);
      // User in der Session Speichern
      getContext().setUser(user);

      return new RedirectResolution("/View.jsp");
   }

   public String getUsername() {
      return username;
   }

   public String getPassword() {
      return password;
   }

   public void setUsername(String username) {
      this.username = username;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   @Validate(required=true)
   private String username;

   @Validate(required=true)
   private String password;
}
Die View Login.jsp

Die zugehörige View Login.jsp wird als normale JSP mit einem Verweis auf die Stripes Tag Library angelegt. Das <stripes:form> Html Tag verweist auf die LoginActionBean. Dies kann entweder über den Namen der Klasse oder den Link /Login.action geschehen. Die Eingabefelder username und password greifen über die Getter/Setter – Metoden auf die entsprechenden Eigenschaften der ActionBean zu. Das <stripes:submit;> Tag ruft beim Absenden des Formulars die Methode in der ActionBean auf, die seinem Attribut name entspricht; in diesem Beispiel also die Methode “login”. Zur Fehlerausgabe dient das

<stripes:errors/> Tag:


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
   <head><title>Login </title></head>
<body>
<h1>Bitte geben Sie Ihre Anmeldedaten ein</h1>
<stripes:form beanclass="de.binnichda.comp.stripes.web.LoginActionBean" focus="">
<table>
   <tr>
      <td>Benutzername</td>
      <td><stripes:text name="username"/></td>
   </tr>
   <tr>
      <td>Kennwort</td>
      <td><stripes:text name="password"/></td>
   </tr>
   <tr>
      <td colspan="2">
          <stripes:submit name="login" value="Login"/>
      </td>
   </tr>
</table>
</stripes:form>
</body>
</html>

Um dann eine Seite nach dem Login anzeigen zu können, benötigen wir noch eine einfache JSP Seite View.jsp:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ page import="de.binnichda.comp.stripes.model.User" %>
<h1>Hallo <%=((User)session.getAttribute("user")).getUsername() %></h1>

Und die erste sehr einfache Stripes Applikation ist fertig.

Hier kann man sich das komplette Beispiel als .WAR Datei herunterladen

stripesbeispielwar

Wenn man diese Datei unter Tomcat 6 deployt, erreicht man die Anwendung unter http://localhost:8080/StripesBeispiel/.

Streifen machen schlank – Stripes, das Java Web Framework (1)

Montag, September 8th, 2008

Stripes ist ein Java Framework zur Erstellung von Webanwendungen. Es zeichnet sich durch ein offenes und leichtgewichtiges Design aus. Durch seine Offenheit ermöglicht es dem Entwickler, gewohnte Tools wie JSTL, Taglibs, Spring, iBatis usw. zu verwenden ohne zu viel Zeit mit dem Erlernen neuer Methoden zu vergeuden. Außerdem verlangt es, außer in der web.xml, keinerlei Konfiguration (Yeah!).

Die aktuelle Stripes-Version (in diesem Artikel 1.5) lädt man sich von der Stripes Download Seite herunter. (Achtung, nicht aus Versehen nur die hervoragende Dokumentation herunterladen, der Link zum Download steht etwas weiter unten auf der Seite).

Um Stripes in einer Webapplikation verwenden zu können, muß man die .JAR Dateien commons-logging.jar, cos.jar und stripes.jar aus dem./lib Verzeichnis der Distribution in WEB-INF/lib kopieren. Die Datei StripesResources.properties aus demselben Verzeichnis kopiert man in WEB-INF/classes.

Stripes arbeitet mit einem Filter und einem Dispatcherservlet. Diese müssen im web.xml konfiguriert werden:


<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>de.binnichda.comp.stripes.web</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>

<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>

<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

Stripes arbeitet mit sogenannten Actionbeans, die die Businesslogik enthalten.
Actionbeans sind vergleichbar mit Struts-Actions, allerdings mit dem wichtigen Unterschied, dass Actionbeans in Stripes nicht per XML definiert werden müssen. Es ist nur nötig, Stripes mitzuteilen, in welchem Java-Package die Actionbeans zu finden sind (hier de.binnichda.comp.stripes.web):


<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>de.binnichda.comp.stripes.web</param-value>
</init-param>

[Zu Teil zwei :Streifen machen schlank - Stripes, das Java Web Framework (2)]