Monday, June 10, 2013

Translate From/To MySQL and Oracle SQL Dialects in Java

I needed this feature when using MySQL Workbench which is a free tool to design, develop and administer data bases. The tool permits to graphically design an Enhanced Entity-Relation (EER) model then to generate the corresponding MySQL script or even to directly deploy it to a running MySQL database schema. Despite the fact it is an Oracle product, MySQL Workbench does not (yet ?) provide the feature of generating Oracle SQL dialect scripts given an EER model description.

In the following we provide a generic translator that takes a set of translation rules and performs the translation on a given input String. Two more or less complete sets permitting to translate MySQL from/to Oracle SQL dialects are also provided.

The Eclipse Project tree looks like:


1 - Package trans.util:

This package contains useful classes.

1.1 - Strings.java:


package trans.util;

import java.util.ArrayList;

public abstract class Strings
{
 public static ArrayList<String> string2Lines(String in)
 {
  ArrayList<String> result = new ArrayList<String>();
  
  for (String s : in.trim().split("\n"))
  {
   if ((s != null) && !s.isEmpty())
   {
    result.add(s);
   }
  }
  
  return(result);
 }
 
 public static String lines2String(ArrayList<String> lines)
 {
  String result = "";
  
  for (String s : lines)
  {
   result += s + "\n";
  }
  
  return(result);
 }
}

This abstract class provides methods splitting one String content to several lines and vice versa.

1.2 - Files.java:


package trans.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public abstract class Files
{
 public static String LINE_SEPARATOR = System.getProperty("line.separator");
 public static String FILE_SEPARATOR = System.getProperty("file.separator");
 
 public static void string2File(String s, String path) throws IOException 
 {
  FileWriter fw = new FileWriter(path);
  BufferedWriter bw = new BufferedWriter(fw);
  bw.write(s);
  bw.close();
  fw.close();
 }
 
 public static String file2String(String filename) throws IOException 
 {
  FileReader fr = new FileReader(filename);
  BufferedReader br = new BufferedReader(fr);
  StringBuilder result = new StringBuilder();
  String line;
  
  while ((line = br.readLine()) != null) 
  {
   result.append(line);
   result.append(LINE_SEPARATOR);
  }
  
  br.close();
  fr.close();
  
  return result.toString();
 }
}

This class provides methods returning the content of a file as a String and storing some String content in a file given its path. This class is not really needed by the translation logic.

2 - Package trans.rules:

This package contains an abstract class TranslationRules.java that defines translation rules.

package trans.rules;

import java.util.HashMap;
import java.util.Map;

public abstract class TranslationRules
{
 public static String COMMENT_DELETED_LINES_PREFIX = "-- NOT TRANSLATED : ";
 
 private String[] deleteLinesStartingWith;
 public String[] getDeleteLinesStartingWith() {
  return deleteLinesStartingWith;
 }
 public void setDeleteLinesStartingWith(String[] deleteLinesStartingWith) {
  this.deleteLinesStartingWith = deleteLinesStartingWith;
 }
 
 private boolean commentDeletedLines;
 public boolean isCommentDeletedLines() {
  return commentDeletedLines;
 }
 public void setCommentDeletedLines(boolean commentDeletedLines) {
  this.commentDeletedLines = commentDeletedLines;
 }
 
 private String[] delete;
 public String[] getDelete() {
  return delete;
 }
 public void setDelete(String[] delete) {
  this.delete = delete;
 }
 
 private Map <String, String> replace = new HashMap <String,String >();
 public Map <String, String> getReplace() {
  return replace;
 }
 public void setReplace(Map <String, String > replace) {
  this.replace = replace;
 }
 
 private Map <String, String> replaceMany = new HashMap <String,String >();
 public Map <String, String> getReplaceMany() {
  return replaceMany;
 }
 public void setReplaceMany(Map <String, String > replaceMany) {
  this.replaceMany = replaceMany;
 }
 
 public TranslationRules(String[] deleteLinesStartingWith, String[] delete, Map <String, String> replace, Map <String, String> replaceMany)
 {
  this.deleteLinesStartingWith = deleteLinesStartingWith;
  this.commentDeletedLines = false;
  this.delete = delete;
  this.replace = replace;
  this.replaceMany = replaceMany;
 }
}

The class defines the following properties:

- String[] deleteLinesStartingWith: This array defines the regular expression patterns which when matched at the beginning of a line of the input to be translated will result in the line being deleted or commented (with the prefix in String COMMENT_DELETED_LINES_PREFIX) if boolean commentDeletedLines is set to true.

- String[] delete: This array defines the regular expression patterns which when matched inside the input to be translated will be deleted (replaced by the empty String).

- Map <String, String> replace: This map defines the regular expression patterns (in the map keys) which when matched inside the input to be translated will be transformed (to the corresponding map values).

Map <String, String> replaceMany: Similar to the previous but may need several replacement iterations.

The class TranslationRules.java is implemented by two concrete classes.

- Oracle2MySQL.java:

package trans.rules;

import java.util.HashMap;
import java.util.Map;

public class Oracle2MySQL extends TranslationRules
{
 public static String[] ORACLE2MYSQL_DELETE_LINES_STARTING_WITH = {"^(CREATE|create|Create)(\\s+)(USER|user|User)",
                  "^(GRANT|grant|Grant)"};
 public static String[] ORACLE2MYSQL_DELETE = {};
 public static Map <String, String > ORACLE2MYSQL_REPLACE = new HashMap <String, String>();
 static
 {
  ORACLE2MYSQL_REPLACE.put("NUMERIC", "DECIMAL");
  ORACLE2MYSQL_REPLACE.put("NUMBER", "DECIMAL");
  ORACLE2MYSQL_REPLACE.put("VARCHAR2", "VARCHAR");
 }
 public static Map <String, String> ORACLE2MYSQL_REPLACE_MANY = new HashMap <String, String>();
 static
 {
  ORACLE2MYSQL_REPLACE_MANY.put("(CONSTRAINT|constraint|Constraint)(\\s+)(\\w+)(\\s+)(UNIQUE|unique|Unique)(\\s*)\\((\\s*)(\\w+)(.*)\\)","UNIQUE INDEX $3 ($8)");
 }
 
 public Oracle2MySQL()
 {
  super(ORACLE2MYSQL_DELETE_LINES_STARTING_WITH, ORACLE2MYSQL_DELETE, ORACLE2MYSQL_REPLACE, ORACLE2MYSQL_REPLACE_MANY);
 }
 
 public Oracle2MySQL(boolean commentDeletedLines)
 {
  super(ORACLE2MYSQL_DELETE_LINES_STARTING_WITH, ORACLE2MYSQL_DELETE, ORACLE2MYSQL_REPLACE, ORACLE2MYSQL_REPLACE_MANY);
  setCommentDeletedLines(commentDeletedLines);
 }
}


MySQL2Oracle.java:

package trans.rules;

import java.util.HashMap;
import java.util.Map;

public class MySQL2Oracle extends TranslationRules
{
 public static String[] MYSQL2ORACLE_DELETE_LINES_STARTING_WITH = {"^(SET|set|Set)", 
                  "^(USE|use|Use)",
                  "^(CREATE|create|Create)(\\s+)(SCHEMA|schema|Schema)",
                  "^(CREATE|create|Create)(\\s+)(USER|user|User)",
                  "^(GRANT|grant|Grant)"};
 public static String[] MYSQL2ORACLE_DELETE = {"`mysql`\\.",
             "(ON|on|On)(\\s+)(DELETE|delete|Delete)(\\s+)(((NO|no|No)(\\s+)(ACTION|action|Action))|CASCADE|cascade|Cascade|RESTRICT|restrict|Restrict|((SET|set|Set)(\\s+)(NULL|null|Null)))",
             "(ON|on|On)(\\s+)(UPDATE|update|Update)(\\s+)(((NO|no|No)(\\s+)(ACTION|action|Action))|CASCADE|cascade|Cascade|RESTRICT|restrict|Restrict|((SET|set|Set)(\\s+)(NULL|null|Null)))",
             "(IF|if|If)(\\s+)(EXISTS|exists|Exists)",
             "(IF|if|If)(\\s+)(NOT|not|Not)(\\s+)(EXISTS|exists|Exists)",
             "(ENGINE|engine|Engine)(\\s+)=(\\s+)(INNODB|innodb|InnoDB)",
             "(ENGINE|engine|Engine)(\\s+)=(\\s+)(MYISAM|myisam|MyIsam)",
             "`"};
 
 public static Map <String, String> MYSQL2ORACLE_REPLACE = new HashMap <String, String>();
 static
 {
  MYSQL2ORACLE_REPLACE.put("DECIMAL", "NUMERIC");
  MYSQL2ORACLE_REPLACE.put("VARCHAR", "VARCHAR2");
 }
 
 public static Map <String, String> MYSQL2ORACLE_REPLACE_MANY = new HashMap <String, String>();
 static
 {
  MYSQL2ORACLE_REPLACE_MANY.put("(UNIQUE|unique|Unique)(\\s+)(INDEX|index|Index)(\\s+)(\\w+)(\\s*)\\((\\s*)(\\w+)(\\s+ASC|asc|DESC|desc)?\\)"
         , "CONSTRAINT $5 UNIQUE ($8)");
  MYSQL2ORACLE_REPLACE_MANY.put("(CREATE|create|Create)(\\s+)(TABLE|table|Table)(\\s+)(IF NOT EXISTS|if not exists|If Not Exists)?(\\s+)(\\w+)(\\s+)\\(([^;]+)(INDEX|index|Index)(\\s+)(\\w+)(\\s*)\\((\\s*)(\\w+)(\\s+ASC|asc|DESC|desc)?(\\s*)\\)(\\s*),([^;]+)\\)(\\s+);"
         ,"$1$2$3$4$5$6$7$8($9$19)$20;\nCREATE INDEX $12 ON $7($15);");
 }
 
 public MySQL2Oracle()
 {
  super(MYSQL2ORACLE_DELETE_LINES_STARTING_WITH, MYSQL2ORACLE_DELETE, MYSQL2ORACLE_REPLACE, MYSQL2ORACLE_REPLACE_MANY);
 }
 
 public MySQL2Oracle(String schema)
 {
  super(MYSQL2ORACLE_DELETE_LINES_STARTING_WITH, MYSQL2ORACLE_DELETE, MYSQL2ORACLE_REPLACE, MYSQL2ORACLE_REPLACE_MANY);
  getDelete()[0] = "`" + schema + "`\\.";
 }
 
 public MySQL2Oracle(boolean commentDeletedLines)
 {
  super(MYSQL2ORACLE_DELETE_LINES_STARTING_WITH, MYSQL2ORACLE_DELETE, MYSQL2ORACLE_REPLACE, MYSQL2ORACLE_REPLACE_MANY);
  setCommentDeletedLines(commentDeletedLines);
 }
 
 public MySQL2Oracle(String schema, boolean commentDeletedLines)
 {
  super(MYSQL2ORACLE_DELETE_LINES_STARTING_WITH, MYSQL2ORACLE_DELETE, MYSQL2ORACLE_REPLACE, MYSQL2ORACLE_REPLACE_MANY);
  getDelete()[0] = "`" + schema + "`\\.";
  setCommentDeletedLines(commentDeletedLines);
 }
}

Comments: Static instances are provided to instantiate inherited properties for the both classes above.
Off course these Arrays and Maps can be enriched to match you custom expectations, here are some advice if you want to extend them:

MYSQL2ORACLE_DELETE_LINES_STARTING_WITH: Here you have the choice to either provide regular expressions (prefixed by ^ which enforces matching the pattern at the beginning of the line) or a simple String. You do not need to matter about trailing blank space characters at the beginning of the line since each line of the input will be trimmed before treatment.

MYSQL2ORACLE_DELETE: Here regular expressions are expected. Off course constant String patterns can be provided since they are also regular expression and if you want this just pay attention to escape characters having special meanings for regular expressions: example don't use "." if you want to match a simple dot but rather "\\." since the dot means matching any character in a regular expression.

MYSQL2ORACLE_REPLACE: Here also regular expressions (including constant Strings) are expected. For example in line 26 we add the entry that will transform each VARCHAR (MySQL syntax) occurrence by VARCHAR2 (Oracle syntax).

- MYSQL2ORACLE_REPLACE_MANY: Here also regular expressions (including constant Strings) are expected. Example in lines 34-35 we provide the way to extract an inner foreign key index declaration from a CREATE TABLE query and append it to its end. Since only one foreign key index declaration will be matched by the regular expression, there should be as many replacements as foreign key index declarations.

One important parameter for the MySQL2Oracle.java is the schema. This helps in removing all the schema references in the script. For both classes the property commentDeletedLines can be set upon construction.

3 - Package trans.engine:

This package contains the generic class that will perform the translation given a TranslationRules instance.

package trans.engine;

import java.io.IOException;
import java.util.Map;
import trans.rules.MySQL2Oracle;
import trans.rules.TranslationRules;
import trans.util.Files;
import trans.util.Strings;

public class TranslationAgent
{
 private TranslationRules translationRules;
 public TranslationRules getTranslationRules() {
  return translationRules;
 }
 public void setTranslationRules(TranslationRules translationRules) {
  this.translationRules = translationRules;
 }

 public TranslationAgent(TranslationRules translationRules)
 {
  this.translationRules = translationRules;
 }
 
 public String deleteLinesStartingWithBadItems(String in)
 {
  String result = "";
  
  for (String s : Strings.string2Lines(in))
  {
   boolean goodLine = true;
   
   for (String bad : getTranslationRules().getDeleteLinesStartingWith())
   {
    if ((!bad.startsWith("^") && s.trim().startsWith(bad) || (bad.startsWith("^") && s.trim().matches(bad+"(.+)"))))
    {
     goodLine = false;
     break;
    }
   }
   
   if (goodLine)
   {
    result += s + "\n";
   }
   else
   {
    if (getTranslationRules().isCommentDeletedLines())
    {
     result += TranslationRules.COMMENT_DELETED_LINES_PREFIX + s + "\n";
    }
   }
  }
  
  return(result);
 }
 
 public String removeBadItems(String in)
 {
  String result = in;
  
  for (String bad : getTranslationRules().getDelete())
  {
   result = result.replaceAll(bad,"");
  }
  
  return(result);
 }
 
 public String replaceBadItems(String in)
 {
  String result = in;

  for (Map.Entry<String,String> rep : getTranslationRules().getReplace().entrySet())
  {
   result = result.replaceAll(rep.getKey(),rep.getValue());
  }
  
  return(result);
 }
 
 public String replaceBadItemsManyTimes(String in)
 {
  String result = in;
  String exResult = "";
  
  do
  {
   exResult = result;
   
   for (Map.Entry<String,String> rep : getTranslationRules().getReplaceMany().entrySet())
   {
    result = result.replaceAll(rep.getKey(),rep.getValue());
   }
  }
  while (!result.equals(exResult));
  
  return(result);
 }
 
 public String translate(String in)
 {
  return(replaceBadItemsManyTimes(replaceBadItems(removeBadItems(deleteLinesStartingWithBadItems(in)))).trim().replaceAll("[\\t ]+\n","\n"));
 }
 
 public void translate(String inPath, String outPath) throws IOException
 {
  Files.string2File(translate(Files.file2String(inPath)), outPath);
 }
 
 public static void main(String args[]) throws IOException
 {
  System.out.println(new TranslationAgent(new MySQL2Oracle("mshj",true)).translate(Files.file2String(args[0])));
 }
}

The String translate(String in) return the translation of its String input by calling successively deleteLinesStartingWithBadItems which deletes or comments the non-needed lines, removeBadItems which removes non-needed tokens, replaceBadItems  which replaces tokens by their translations and finally replaceBadItemsManyTimes which perform unbounded number of replacement iterations.
The replaceBadItemsManyTimes performs as many replacement iterations as there should be, for example when translating inner foreign key index declarations in a MySQL script: each index will be detected in one iteration and thus there will be as many iterations as such index declarations.

The main method displays the result of the translation (from MySQL to Oracle SQL dialect) of the script at the path args[0].

(+) What is supported:

- Regular insert into, delete, drop, create, update, alter, select ... instructions
- Constraints (foreign keys and uniqueness)
- Indexes

(-) What is not supported:

- Granting privileges
- Creating users and schema

Off course all this can be improved. The aim was to rapidly create an incomplete yet useful translator using only replacement and Java Regular Expressions. A more complete solution could be achieved by using parsers (JavaCC).


Friday, June 7, 2013

Moving, Renaming Directories,Files or Packages in Java

It is often needed to move and/or rename directories using java programs. One particular case where i needed this feature was after following some tutorial on the web ending up with a heavily configured Eclipse project and where i needed to rename the hole project and sometimes even package names to fit my desired parameters in stead of starting the configuration all over again for a new project. (You may be interested if you followed this JEE tutorial)
Eclipse on its side proposes Project re-factoring but for packages renaming you'll need to do it by hand.
Another scenario where this feature maybe useful is when you do some code generation and where you need to create a new project instance starting from some skeleton.

Classes are grouped in an Eclipse Java Project illustrated by this tree:



1. Package file.util.files:

This package contains an abstract class that exposes useful operations for files.

FileUtils.java:


package file.util.files;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public abstract class FileUtils
{
 public static String LINE_SEPARATOR = System.getProperty("line.separator");
 public static String FILE_SEPARATOR = System.getProperty("file.separator");
 
 public static String toPath(String packageName)
 {
  return(packageName.replaceAll("\\.", FILE_SEPARATOR));
 }
 
 public static String getExtension(File file)
 {
  String result = "";
  
  if (file.isFile())
  {
   String fileName = file.getName();
   int lastDotIndex = fileName.lastIndexOf(".");
   
   if ((lastDotIndex != -1) && (lastDotIndex != (fileName.length()-1)))
   {
    result = fileName.substring(lastDotIndex + 1);
   }
  }
  
  return(result);
 }
 
 public static void string2File(String s, String path) throws IOException 
 {
  FileWriter fw = new FileWriter(path);
  BufferedWriter bw = new BufferedWriter(fw);
  bw.write(s);
  bw.close();
  fw.close();
 }
 
 public static String file2String(String filename) throws IOException 
 {
  FileReader fr = new FileReader(filename);
  BufferedReader br = new BufferedReader(fr);
  StringBuilder result = new StringBuilder();
  String line;
  
  while ((line = br.readLine()) != null) 
  {
   result.append(line);
   result.append(LINE_SEPARATOR);
  }
  
  br.close();
  fr.close();
  
  return result.toString();
 }
 
 public static void copyFolder(File src, File dest) throws IOException
 {
  if(src.isDirectory())
  {
   if(!dest.exists())
   {
    dest.mkdir();
   }
   String files[] = src.list();
    
   for (String file : files)
   {
    File srcFile = new File(src, file);
    File destFile = new File(dest, file);
    copyFolder(srcFile,destFile);
   }
  }
  else
  {
   InputStream in = new FileInputStream(src);
   OutputStream out = new FileOutputStream(dest); 
        
   byte[] buffer = new byte[1024];
    
   int length;
   while ((length = in.read(buffer)) > 0)
   {
    out.write(buffer, 0, length);
   }
    
   in.close();
   out.close();
  }
 }
 
 public static void removeEmptySubDirs(File source)
 {
  if (source.isDirectory())
  {
   if (!source.delete())
   {
    for (File sub : source.listFiles())
    {
     removeEmptySubDirs(sub);
    }
   }
  }
 }
}

public static String toPath(String packageName) in lines 19-22:
Replaces a package name to its corresponding path. Example: file.util.files will return file/util/files for Linux and file\util\files for Windows.

public static String getExtension(File file) in lines 24-40:
Returns the extension (the sub-string of its name starting from the last . occurrence excluding the .) of the File file.

public static void string2File(String s, String path) throws IOException in lines 42-49:
This static method takes as parameters a String s and a String representing a file system valid path path. It stores the content of s in the file at the path path.

public static String file2String(String filename) throws IOException in lines 551-68:
This static method returns the content of the file at the path filename as a String.

public static void copyFolder(File src, File dest) throws IOException in lines 70-103:
Copy the directory (and all its sub-directories) or the file described by the File src to the the directory or file described by the File dest.

public static void removeEmptySubDirs(File source) in lines 105-118:
Removes all the empty sub-directories of the directory described by the File source.

2. Package file.util.files.refactor:

This package contains the master class that will expose he re-factoring feature and a configuration class holding the re-factoring parameters.

RefactorConfiguration.java:


package file.util.files.refactor;

import java.util.ArrayList;
import java.util.Arrays;

public class RefactorConfiguration
{
 public static String[] DEFAULT_ECLIPSE_EXTENSIONS_2_SKIP = {"jpeg","jpg","bmp","png","gif","zip","jar","war","class","pdf","ttf","css"};
 
 public static RefactorConfiguration DEFAULT_ECLIPSE_REFACTOR_CONFIGURATION = 
   new RefactorConfiguration
   (
     true,
     false,
     true,
     false,
     true,
     true,
     new ArrayList<String>(Arrays.asList(DEFAULT_ECLIPSE_EXTENSIONS_2_SKIP)),
     false,
     new ArrayList<String>(),
     true
   );
 
 private boolean inDirNames;
 public boolean isInDirNames() {
  return inDirNames;
 }
 public void setInDirNames(boolean inDirNames) {
  this.inDirNames = inDirNames;
 }
 
 private boolean exactMatchInDirNames;
 public boolean isExactMatchInDirNames() {
  return exactMatchInDirNames;
 }
 public void setExactMatchInDirNames(boolean exactMatchInDirNames) {
  this.exactMatchInDirNames = exactMatchInDirNames;
 }

 private boolean inFileNames;
 public boolean isInFileNames() {
  return inFileNames;
 }
 public void setInFileNames(boolean inFileNames) {
  this.inFileNames = inFileNames;
 }
 
 private boolean exactMatchInFileNames;
 public boolean isExactMatchInFileNames() {
  return exactMatchInFileNames;
 }
 public void setExactMatchInFileNames(boolean exactMatchInFileNames) {
  this.exactMatchInFileNames = exactMatchInFileNames;
 }

 private boolean inFiles;
 public boolean isInFiles() {
  return inFiles;
 }
 public void setInFiles(boolean inFiles) {
  this.inFiles = inFiles;
 }
 
 private boolean skipExtensions;
 public boolean isSkipExtensions() {
  return skipExtensions;
 }
 public void setSkipExtensions(boolean skipExtensions) {
  this.skipExtensions = skipExtensions;
 }
 
 private ArrayList<String> extensions2Skip;
 public ArrayList<String> getExtensions2Skip() {
  return extensions2Skip;
 }
 public void setExtensions2Skip(ArrayList<String> extensions2Skip) {
  this.extensions2Skip = extensions2Skip;
 }
 
 private boolean visitExtensions;
 public boolean isVisitExtensions() {
  return visitExtensions;
 }
 public void setVisitExtensions(boolean visitExtensions) {
  this.visitExtensions = visitExtensions;
 }
 
 private ArrayList<String> extensions2Visit;
 public ArrayList<String> getExtensions2Visit() {
  return extensions2Visit;
 }
 public void setExtensions2Visit(ArrayList<String> extensions2Visit) {
  this.extensions2Visit = extensions2Visit;
 }
 
 private boolean packageNamesAware;
 public boolean isPackageNamesAware() {
  return packageNamesAware;
 }
 public void setPackageNamesAware(boolean packageNamesAware) {
  this.packageNamesAware = packageNamesAware;
 }
 
 public RefactorConfiguration
  (
   boolean inDirNames,
   boolean exactMatchInDirNames,
   boolean inFileNames,
   boolean exactMatchInFileNames,
   boolean inFiles,
   boolean skipExtensions,
   ArrayList<String> extensions2Skip,
   boolean visitExtensions,
   ArrayList<String> extensions2Visit,
   boolean packageNamesAware
  )
 {
  this.inDirNames = inDirNames;
  this.exactMatchInDirNames = exactMatchInDirNames;
  this.inFileNames = inFileNames;
  this.exactMatchInFileNames = exactMatchInFileNames;
  this.inFiles = inFiles;
  this.skipExtensions = skipExtensions;
  this.extensions2Skip = extensions2Skip;
  this.visitExtensions = visitExtensions;
  this.extensions2Visit = extensions2Visit;
  this.packageNamesAware = packageNamesAware;
 }
}

The class defines several boolean flags having the following meanings:
- inDirNames: if set to true re-factoring will also address the directories' names.
- exactMatchInDirNames: if set to true (and if inDirNames is set to true) re-factoring will address directories with names matching exactly the pattern to be re-factored.
inFileNames: if set to true re-factoring will also address the files' names.
exactMatchInFileNames: if set to true (and if inFileNames is set to true) re-factoring will address files' names matching exactly the pattern to be re-factored.
- inFiles: if set to true re-factoring will also address the content of files.
- skipExtensions: if set to true all the files having extensions in extensions2Skip will be skipped.
- visitExtensions: if set to true only files having extension in extensions2Visit will be addressed by re-factoring.
Using skipExtensions and visitExtensions is mutually exclusive, only one should be set to true otherwise all files will not be handled.
- packageNamesAware: is set to true and if for example we are replacing each occurrence of file.util.files.refactor by test.refactor then each occurrence of the former will be replaced by the latter in the content of every visited file but also every directory having in its path the sub-path expression file/util/files/refactor, lets say the directory is $Prefix/file/util/files/refactor/$Suffix will be moved to the path $Prefix/test/refactor/$Suffix.

In lines 8-23, a good configuration for re-factoring an eclipse project is provided.

RefactorAgent.java:



package file.util.files.refactor;

import java.io.File;
import java.io.IOException;
import file.util.files.FileUtils;

public class RefactorAgent
{
 private RefactorConfiguration configuration;
 public RefactorConfiguration getConfiguration() {
  return configuration;
 }
 public void setConfiguration(RefactorConfiguration configuration) {
  this.configuration = configuration;
 }
 
 public RefactorAgent(RefactorConfiguration configuration)
 {
  this.configuration = configuration;
 }
 
 public RefactorAgent ()
 {
  this.configuration = RefactorConfiguration.DEFAULT_ECLIPSE_REFACTOR_CONFIGURATION;
 }
 
 public void refactor(File mySource, String myFrom, String myTo) throws IOException
 {
  if (mySource.exists())
  {
   File newSource = mySource;
   
   if (mySource.isDirectory() && getConfiguration().isInDirNames())
   {
    if (
      getConfiguration().isPackageNamesAware()
      &&
      (
       (getConfiguration().isExactMatchInDirNames() && mySource.getAbsolutePath().endsWith(FileUtils.toPath(myFrom)))
       ||
       (!getConfiguration().isExactMatchInDirNames() && (mySource.getAbsolutePath().indexOf(FileUtils.toPath(myFrom)) != -1))
      )
     )
    {
     newSource = new File (mySource.getAbsolutePath().replaceAll(FileUtils.toPath(myFrom), FileUtils.toPath(myTo)));
     newSource.mkdirs();
     mySource.renameTo(newSource);
    }
    
    if (
      !getConfiguration().isPackageNamesAware()
      &&
      (
       (getConfiguration().isExactMatchInDirNames() && mySource.getName().equals(myFrom))
       ||
       (!getConfiguration().isExactMatchInDirNames() && (mySource.getName().indexOf(myFrom) != -1))
      )
     )
    {
     newSource = new File (mySource.getParent() + FileUtils.FILE_SEPARATOR + mySource.getName().replaceAll(myFrom, myTo));
     newSource.mkdirs();
     mySource.renameTo(newSource);
    }
   }
   
   if (mySource.isFile() && getConfiguration().isInFileNames())
   {
    if (
      (getConfiguration().isExactMatchInFileNames() && mySource.getName().equals(myFrom))
      ||
      (!getConfiguration().isExactMatchInFileNames() && (mySource.getName().indexOf(myFrom) != -1))
     )
    {
     newSource = new File (mySource.getParent() + FileUtils.FILE_SEPARATOR + mySource.getName().replaceAll(myFrom, myTo));
     mySource.renameTo(newSource);
    }
   }
   
   if (newSource.isDirectory())
   {
    for (File sub : newSource.listFiles())
    {
     refactor(sub, myFrom, myTo);
    }
   }
   else
   {
    if (getConfiguration().isInFiles())
    {
     String extension = FileUtils.getExtension(newSource);
     
     if (
       (!getConfiguration().isSkipExtensions() && getConfiguration().isVisitExtensions() && getConfiguration().getExtensions2Visit().contains(extension)) 
       || 
       (!getConfiguration().isVisitExtensions() && getConfiguration().isSkipExtensions() && !getConfiguration().getExtensions2Skip().contains(extension))
      )
     {
      FileUtils.string2File(FileUtils.file2String(newSource.getPath()).replaceAll(myFrom, myTo).replaceAll(FileUtils.toPath(myFrom), FileUtils.toPath(myTo)),newSource.getPath());
     }
    }
   }
  }
 }
 
 public static void main(String[] args) throws IOException
 {
  new RefactorAgent().refactor(new File("/some_path/Test"),"Test","NewName");
  new RefactorAgent().refactor(new File("/some_path/newName"),"mshj.tutorial","pckgname.spckgname1.spckgname2");
  FileUtils.removeEmptySubDirs(new File("/some_path/newName"));
 }
}

This is the master class.
public void refactor(File mySource, String myFrom, String myTo) throws IOException in lines 27-103:
Performs the re-factoring on the file or directory described by the File mySource replacing occurrences of myFrom by myTo.

The main method is provided with the code to rename the project (and its packages) obtained in this JEE tutorial to custom values.


Execute Oracle/MySQL SQL Scripts in Java

Sometimes it is useful to execute SQL scripts from Java programs. In the following we present java classes permitting to:

- Execute Oracle or MySQL SQL update scripts given a valid and authorized DB connection.
-  Execute Oracle or MySQL SQL query and collect the results as a collection of lines (where a line is a Collection of Strings) or as a simple HTML page with a table summarizing the results.

The classes are grouped in an Eclipse Java Project illustrated by the following tree:



1. Project dependencies:

The project needs jdbc libraries for both Oracle (download here) and MySQL (download here) databases.

2. Project Packages:

The project defines two packages:

sql.util.connection: which contains classes defining JDBC Oracle and MySQL database connections.

ConnectionDefinition.java: is an abstract class describing general JDBC database connection as follows:

package sql.util.connection;

public abstract class ConnectionDefinition
{
 private String url;  
 private String user;  
 private String password;
    
 public String getUrl() {
  return url;
 }
 public void setUrl(String url) {
  this.url = url;
 }
 
 public String getUser() {
  return user;
 }
 public void setUser(String user) {
  this.user = user;
 }
 
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 
 public ConnectionDefinition(String url, String user, String password)
 {
  this.url = url;
  this.user = user;
  this.password = password;
 }
 
 public static String display(ConnectionDefinition cd)
 {
  if (cd == null)
  {
   return("ConnectionDefinition :\n\tnull");
  }
  else
  {
   return( "ConnectionDefinition :\n"
     + "\turl = " + ((cd.getUrl() == null)?"null":cd.getUrl()) + "\n"
     + "\tuser = " + ((cd.getUser() == null)?"null":cd.getUser()) + "\n"
     + "\tpassword = " + ((cd.getPassword() == null)?"null":cd.getPassword()) + "\n");
  }
 }
}

The class defines three properties (the DB connection url, the user name and the corresponding password) and a display function returning a friendly String description of the connection. This class is extended by :

OracleConnectionDefinition.java:

package sql.util.connection;

public class OracleConnectionDefinition extends ConnectionDefinition
{
 private String host;
 public String getHost() {
  return host;
 }
 public void setHost(String host) {
  this.host = host;
 }
 
 private int port;
 public int getPort() {
  return port;
 }
 public void setPort(int port) {
  this.port = port;
 }
 
 private String sid;
 public String getSid() {
  return sid;
 }
 public void setSid(String sid) {
  this.sid = sid;
 }
 
 public OracleConnectionDefinition(String host, int port, String sid, String user, String password)
 {
  super("jdbc:oracle:thin:@" + host + ":" + port + ":" + sid, user, password);
 }
 
 public OracleConnectionDefinition(String sid, String user, String password)
 {
  super("jdbc:oracle:thin:@localhost:1521:" + sid, user, password);
 }
 
 public OracleConnectionDefinition(String user, String password)
 {
  super("jdbc:oracle:thin:@localhost:1521:xe", user, password);
 }
}

For Oracle databases the JDBC connection url is formed by three parameters the host (the server address), the listening port and the sid (site identifier). The class provides several default constructors (1521 is the default listening port and xe is the default sid for Oracle Express Editions).

MySQLConnectionDefinition.java:

package sql.util.connection;

public class MySQLConnectionDefinition extends ConnectionDefinition
{
 private String host;
 public String getHost() {
  return host;
 }
 public void setHost(String host) {
  this.host = host;
 }
 
 private int port;
 public int getPort() {
  return port;
 }
 public void setPort(int port) {
  this.port = port;
 }
 
 private String schema;
 public String getSchema() {
  return schema;
 }
 public void setSchema(String schema) {
  this.schema = schema;
 }
 
 public MySQLConnectionDefinition(String host, int port, String schema, String user, String password)
 {
  super("jdbc:mysql://" + host + ":" + port + "/" + schema, user, password);
 }
 
 public MySQLConnectionDefinition(String schema, String user, String password)
 {
  super("jdbc:mysql://localhost:3306/" + schema, user, password);
 }
 
 public MySQLConnectionDefinition(String user, String password)
 {
  super("jdbc:mysql://localhost:3306/mysql", user, password);
 }
}

For MySQL databases the JDBC connection url is formed by three parameters the host (the server address), the listening port and the schema. The class provides several default constructors (3306 is the default listening port and mysql is the default schema).


sql.util: which contains the class that will execute scripts.

SQLExecutor.java:

package sql.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import sql.util.connection.ConnectionDefinition;
import sql.util.connection.MySQLConnectionDefinition;
import sql.util.connection.OracleConnectionDefinition;

public class SQLExecutor
{
 public static String LINE_SEPARATOR = System.getProperty("line.separator");
 private static final String ORACLE_DRIVER_NAME = "oracle.jdbc.OracleDriver";
 private static final String MYSQL_DRIVER_NAME = "com.mysql.jdbc.Driver";
 
 public static void loadMySQLDriver()
 {
  try
  {
   Class.forName(MYSQL_DRIVER_NAME);
  }
  catch(ClassNotFoundException e)
  {
   System.out.println(MYSQL_DRIVER_NAME + " is missing.");
   e.printStackTrace();
  }
 }
 
 public static void loadOracleDriver()
 {
  try
  {
   Class.forName(ORACLE_DRIVER_NAME);
  }
  catch(ClassNotFoundException e)
  {
   System.out.println(ORACLE_DRIVER_NAME + " is missing.");
   e.printStackTrace();
  }
 }
 
 ConnectionDefinition connection;
 public ConnectionDefinition getConnection() {
  return connection;
 }
 public void setConnection(ConnectionDefinition connection) {
  this.connection = connection;
 }
 
 public SQLExecutor(ConnectionDefinition connection)
 {
  this.connection = connection;
  
  if (connection instanceof OracleConnectionDefinition)
  {
   loadOracleDriver();
  }
  
  if (connection instanceof MySQLConnectionDefinition)
  {
   loadMySQLDriver();
  }
 }
 
 public void executeUpdate(String query)
 {
  Connection con;
  Statement stmt;
  
  try
  {
   con = DriverManager.getConnection(getConnection().getUrl(),getConnection().getUser(),getConnection().getPassword());
   
   try
   {
    stmt = con.createStatement();
    
    query = query.trim();
    
    if (query.endsWith(";"))
    {
     query = query.substring(0,query.length()-1);
    }
    
    try
    {
     stmt.executeUpdate(query);
    }
    catch (SQLException e)
    {
     System.out.println("Could not execute update query:\n" + query);
     e.printStackTrace();
    }
    
    stmt.close();
    stmt = null;
    
   }
   catch (SQLException e)
   {
    System.out.println("Could not create statement.");
    e.printStackTrace();
   }
   
   con.close();
         con = null;
   
  }
  catch (SQLException e)
  {
   System.out.println("Something is wrong with your connection parameters:\n" + ConnectionDefinition.display(getConnection()));
   e.printStackTrace();
  }
 }
 
 public void executeScript(String script)
 {
  Connection con;
  Statement stmt;
  
  try
  {
   con = DriverManager.getConnection(getConnection().getUrl(),getConnection().getUser(),getConnection().getPassword());
   
   try
   {
    stmt = con.createStatement();
    
    script = script.trim();
    
    if (script.endsWith(";"))
    {
     script = script.substring(0,script.length()-1);
    }
    
    String[] queries = script.split(";" + LINE_SEPARATOR);
    
    int counter = 0;
    for (String query : queries)
    {
     counter++;
     if ((query != null) && !query.isEmpty())
     {
      try
      {
       stmt.executeUpdate(query);
      }
      catch (SQLException e)
      {
       System.out.println("Could not execute update query in line " + counter + ":\n" + query);
       e.printStackTrace();
      }
     }
    }
    
    stmt.close();
    stmt = null;
   }
   catch (SQLException e)
   {
    System.out.println("Could not create statement.");
    e.printStackTrace();
   }
   
   con.close();
         con = null;
   
  }
  catch (SQLException e)
  {
   System.out.println("Something is wrong with your connection parameters:\n" + ConnectionDefinition.display(getConnection()));
   e.printStackTrace();
  } 
 }
 
 public ArrayList<ArrayList<String>> executeQueryArray(String query)
 {
  ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
  
  Connection con;
  Statement stmt;
  
  try
  {
   con = DriverManager.getConnection(getConnection().getUrl(),getConnection().getUser(),getConnection().getPassword());
   
   try
   {
    stmt = con.createStatement();
    
    query = query.trim();
    
    if (query.endsWith(";"))
    {
     query = query.substring(0,query.length()-1);
    }
    
    ResultSet resultSet;
    
    try
    {
     resultSet = stmt.executeQuery(query);
     
     ResultSetMetaData rsmd;
     
     ArrayList<String> columns = new ArrayList<String>();
     
     try
     {
      rsmd = resultSet.getMetaData();
      
      int counter = 0;
      
      while (counter < rsmd.getColumnCount())
      {
       columns.add(rsmd.getColumnLabel(counter+1));
       counter++;
      }
      
      result.add(columns);
      
      while (resultSet.next())
      {
       ArrayList<String> line = new ArrayList<String>();
       int counter2 = 0;
       
       while (counter2 < columns.size())
       {
        line.add(resultSet.getString(counter2+1));
        counter2++;
       }
       
       result.add(line);
      }
      
     }
     catch (SQLException e)
     {
      System.out.println("Could not get MetaData from ResultSet for query:\n" + query);
      e.printStackTrace();
     }
     
     resultSet.close();
    }
    catch (SQLException e)
    {
     System.out.println("Could not execute query:\n" + query);
     e.printStackTrace();
    }
    
    stmt.close();
    stmt = null;
    
   }
   catch (SQLException e)
   {
    System.out.println("Could not create statement.");
    e.printStackTrace();
   }
   
  }
  catch (SQLException e)
  {
   System.out.println("Something is wrong with your connection parameters:\n" + ConnectionDefinition.display(getConnection()));
   e.printStackTrace();
  }
  
  return(result);
 }
 
 public String executeQuery2HTML(String query)
 {
  String finalResult = "<html>" + LINE_SEPARATOR + "\t</head>" + LINE_SEPARATOR + "\t<body>" + LINE_SEPARATOR + "\t\t<table border = \"1\" align = \"center\">";
  
  boolean done = false;
  
  for (ArrayList<String> array : executeQueryArray(query))
  {
   finalResult += LINE_SEPARATOR + "\t\t\t<tr>" + LINE_SEPARATOR;
   for (String cell : array)
   {
    finalResult += ((done)?"\t\t\t\t<td align = \"center\">":"\t\t\t\t<th align = \"center\">") + cell + ((done)?"</td>" + LINE_SEPARATOR:"</th>" + LINE_SEPARATOR);
   }
   finalResult += "\t\t\t</tr>";
   
   done = true;
  }
  
  return(finalResult + LINE_SEPARATOR + "\t\t</table>" + LINE_SEPARATOR + "\t</body>" + LINE_SEPARATOR + "</html>");
 }
 
 public static void testOracle(String user, String password)
 {
  String scriptOracle = "CREATE TABLE test (id NUMERIC(2) NOT NULL, title VARCHAR2(10), description VARCHAR2(20), PRIMARY KEY(id));" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(1,'title1','Title One');" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(2,'title2','Title Two');" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(3,'title3','Title Three');" + LINE_SEPARATOR
       + "COMMIT;";
  
  SQLExecutor oracleSQLExecutor = new SQLExecutor(new OracleConnectionDefinition(user,password));
  
  oracleSQLExecutor.executeScript(scriptOracle);
  System.out.println(oracleSQLExecutor.executeQuery2HTML("SELECT * FROM test;"));
  
  String scriptCleanUp = "DROP TABLE test;";
     oracleSQLExecutor.executeScript(scriptCleanUp);
 }
 
 public static void testMySQL(String user, String password)
 {
  String scriptMySQL = "CREATE TABLE test (id NUMERIC(2) NOT NULL, title VARCHAR(10), description VARCHAR(20), PRIMARY KEY(id));" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(1,'title1','Title One');" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(2,'title2','Title Two');" + LINE_SEPARATOR
       + "INSERT INTO test VALUES(3,'title3','Title Three');" + LINE_SEPARATOR
       + "COMMIT;";
  
  SQLExecutor mySQLExecutor = new SQLExecutor(new MySQLConnectionDefinition(user,password));
  
  mySQLExecutor.executeScript(scriptMySQL);
     System.out.println(mySQLExecutor.executeQuery2HTML("SELECT * FROM test;"));
  
     String scriptCleanUp = "DROP TABLE test;";
     mySQLExecutor.executeScript(scriptCleanUp);
 }
 
 public static void main(String[] args)
    {
     if (args.length == 3)
     {
      if ("oracle".equals(args[0].toLowerCase()))
      {
       testOracle(args[1],args[2]);
      }
      
      if ("mysql".equals(args[0].toLowerCase()))
      {
       testMySQL(args[1],args[2]);
      }
     }
     else
     {
      System.out.println("Executes test scripts on \"localhost:1521:xe\" or \"localhost:3306/mysql\"\n\nArguments:\n- DB Type : Oracle or MySQL\n- DB User Name\n- DB Password\n\nExample: \"oracle\" \"system\" \"manager\"");
     }
    }
}

We briefly describe here the important methods:

public void executeScript(String script) in lines 120-178:
The method splits the script into several single instructions (statements) then executes the instructions one by one. Exceptions are caught whenever the connection does not succeed, the statement execution is not possible (SQL syntax error or maybe not enough privileges to execute the statement).

public ArrayList<ArrayList<String>> executeQueryArray(String query) in lines 180-273:
The method returns the result of the execution of a select query as a list of lists of Strings. The first inner list contains the returned field titles and each subsequent inner list (if any) contains values for those fields. Again, exceptions are caught whenever the connection does not succeed, the statement execution is not possible (SQL syntax error or maybe not enough privileges to execute the statement).

public String executeQuery2HTML(String query) in lines 275-294:
Relies on the previous method to format the result of the execution of a select query as a table in a simple html page.

public static void testOracle(String user, String password) in lines 296-311 and public static void testMySQL(String user, String password) in lines 313-328:
These two methods use the previously described methods to execute test scripts and queries for simple default settings for Oracle and MySQL databases. A dummy test table is created and three lines are inserted into it. A select statement is executed against the test table and corresponding results are displayed as html. Finally the test table is dropped.

These two methods can be used by executing the main method of the class with three arguments: the database type, the authorized user name and password.


Wednesday, April 24, 2013

How To : Create / Modify / Delete an Environment Variable ?

Windows:

- Right-click your Computer icon (on your Desktop or in the Windows Menu) and choose Properties.
- A window entitled System is displayed. Click on Advanced system settings in its left panel.
- In the System Properties window select the Advanced tab and click on the Environment Variables… button given at the bottom of the window.
- In the Environment Variables window you will notice two sub-panels entitled User variables for your username and System variables.
- Use the buttons New..., Edit ... and Delete of the User variables panel (respectively the System variables panel) to create, modify or delete a User Environment variable (respectively a System Environment Variable).

Linux:

This depends on the Linux distribution. (To be Completed)

Maven / Spring / Hibernate / JSF2 Tutorial - Third Part (Advanced JEE Web Application)


In this part we build and describe an advanced JEE Web Application. This application permits to simple users to modify their passwords and to a super user to freely insert, update or delete users, profiles and roles. The application relies on Spring Security to control users access.

This application is built upon the result of Maven/Spring/Hibernate/JSF2 Tutorial - Second Part (Skeleton JEE Web Application).


N.B: In the following by deploying the application we mean :


- In a command line console set your working directory to the path of your Test Eclipse project then successively type :

mvn clean
mvn compile
mvn war:war

- Under the sub-directory Test/target/ you should see the Test-1.0-SNAPSHOT folder. Copy that folder under the webapps/ folder of Apache Tomcat.
- Start your Apache Tomcat Server (if it is not started) then type the following address in a browser: http://localhost:8081/Test-1.0-SNAPSHOT/. (or change the port to your local setting)


Outline:

3.1 - Customizing Application:
  a - Application Messages
  b - JSF Validation Messages
  c - Using JSF Templates
  d - Richfaces Skins

3.2 - Password Update Feature
  a - Presentation Layer
  b - Service (BO) Layer
  c - Repository (DAO) Layer

3.3 - Generic Spring Components
  a - Generic Repository (DAO)
  b - Generic Service (BO)
  c - Generic Controller

3.4 - JSF and Richfaces useful components
  a - Super User features
  b - RichFaces Accordion
  c - JSF Data Table
  d - RichFaces Pick Lists
  e - JSF Converters
  f - JSF navigation

3.5 - Spring Security Access Control

3.1 - Customizing Application:

We propose here to introduce some customization to the application built in Part 2 of this tutorial.

a - Application Messages:

It is recommended to have all the application messages (titles of pages, text inputs or command buttons) stored in property files. This permits to handle them more efficiently.

But what actually are the messages of our application ?

. In Connection.xhtml we defined three titles in the following value attributes:

...
<h:outputText value = "User Name" />
...
<h:outputText value = "Password" />
...
<h:commandButton value = "Login" action = "#{loginController.doLogin()}" />
...

. But also in mshj.tutorial.listener.LoginErrorPhaseListener where we used the two following messages to stress out an authentication failure or a concurrency session exception:

...
new FacesMessage(FacesMessage.SEVERITY_ERROR,
                      "Username or password not valid.", "Username or password not valid")
...
new FacesMessage(FacesMessage.SEVERITY_ERROR,
                      "Maximum number of sessions Exceeded.", "Maximum number of sessions Exceeded")
...

- Create a new file named messages.properties under src/main/resources/.
- Paste into this file the following entries (one per each message):

userName=User Name
password=Password
login=Login
loginFailure=Username or Password not Valid
sessionConcurrency=Maximum Number of Concurrent Sessions Exceeded

- Make JSF aware of the use of the message resource bundle. Edit src/main/webapp/WEB-INF/faces-config.xml such that it finally looks like:

<?xml version="1.0"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
   
 <!-- Resource Bundles (Messages and JSF Messages) -->
 <application>
  <resource-bundle>
   <base-name>messages</base-name>
   <var>messages</var>
  </resource-bundle>
 </application>   
   
   <!-- JSF and Spring are integrated -->
   <application>
     <el-resolver>
      org.springframework.web.jsf.el.SpringBeanFacesELResolver
     </el-resolver>
   </application>
    
</faces-config>

In Lines 10-15 we added the messages resource bundle.

- In Connection.xhtml transform the previously discussed static values to ones gathered from the messages resource bundle:

...
<h:outputText value = "#{messages.userName}" />
...
<h:outputText value = "#{messages.password}" />
...
<h:commandButton value = "#{messages.login}" action = "#{loginController.doLogin()}" />
...

- In mshj.tutorial.listener.LoginErrorPhaseListener perform these transformations:
. Replace "Username or password not valid" by a call to the messages resource bundle with key "loginFailure":

...
new FacesMessage(FacesMessage.SEVERITY_ERROR,
         FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                  .getString("loginFailure"),
         FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                  .getString("loginFailure"))
...

. Then replace "Maximum number of sessions Exceeded" by a call to the messages resource bundle with key "sessionConcurrency":

...
new FacesMessage(FacesMessage.SEVERITY_ERROR,
         FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                  .getString("sessionConcurrency"),
         FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                  .getString("sessionConcurrency"))
...

Indeed,

FacesContext.getCurrentInstance().getApplication()
   .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
       .getString("someKey")

returns the value mapped by "someKey" in the application messages resource bundle.

It is also possible to define application messages depending on the user's (language) Locale settings.
You can define a default Locale, i.e. the one to be used if the user's Locale is not supported and a set of supported Locales. Here's an example of configuring french messages for users having a french Locale.

- Edit src/main/webapp/WEB-INF/faces-config.xml such that it finally looks like:

<?xml version="1.0"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
   
 <!-- Resource Bundles (Messages and JSF Messages) -->
 <application>

  <locale-config>
    <default-locale>en</default-locale>
    <supported-locale>fr_FR</supported-locale>
  </locale-config> 
 
  <resource-bundle>
   <base-name>messages</base-name>
   <var>messages</var>
  </resource-bundle>

 </application>   
   
   <!-- JSF and Spring are integrated -->
   <application>
     <el-resolver>
      org.springframework.web.jsf.el.SpringBeanFacesELResolver
     </el-resolver>
   </application>
    
</faces-config>

- Now add the following messages_fr_FR.properties file under src/main/resources/:

userName=Nom d'Utilisateur
password=Mot de Passe
login=S'authentifier
loginFailure=Nom d'Utilisateur ou Mot de Passe Incorrect
sessionConcurrency=Nombre Maximum de Sessions Concurrentes Atteint

Change your browser Locale to french (if it is not already french) and try out the messages customization after deploying the application.
Note that to switch displayed messages depending on the user's Locale, you have only to create dedicated files and to add the Locale's name as suffix to the corresponding application messages resource bundles.

b - JSF Validation Messages:

JSF validates the inputs according to their constraints. For example, in Connection.xhtml the two input texts (User Name and Password) are set to be required. Leaving one of these inputs empty leads to a default message, for example for password something like: J_password : validation error. Value is required. These messages values can be customized, also depending on the user's Locale.

- Create the following  jsfMessages.properties file under src/main/resources/:

javax.faces.converter.NumberConverter.NUMBER={2}: ''{0}'' is not a number.
javax.faces.converter.NumberConverter.NUMBER_detail={2}: ''{0}'' is not a number. Example: {1}.
javax.faces.converter.LongConverter.LONG={2}: ''{0}'' must be a number consisting of one or more digits.
javax.faces.converter.LongConverter.LONG_detail={2}: ''{0}'' must be a number between -9223372036854775808 to 9223372036854775807 Example: {1}.
javax.faces.component.UIInput.REQUIRED={0}: Validation Error: Value is required.
javax.faces.converter.BigDecimalConverter.DECIMAL={2}: ''{0}'' must be a signed decimal number.
javax.faces.converter.BigDecimalConverter.DECIMAL_detail={2}: ''{0}'' must be a signed decimal number consisting of zero or more digits, that may be followed by a decimal point and fraction. Example: {1}.
javax.faces.converter.DateTimeConverter.DATE={2}: ''{0}'' could not be understood as a date.
javax.faces.converter.DateTimeConverter.DATE_detail={2}: ''{0}'' could not be understood as a date. Example: {1}.

- Create also a french entry jsfMessages_fr_FR.properties file under src/main/resources/:

javax.faces.converter.NumberConverter.NUMBER=Erreur de validation pour {2}: ''{0}'' n'est pas numérique.
javax.faces.converter.NumberConverter.NUMBER_detail=Erreur de validation pour {2}: ''{0}'' n'est pas numérique. Exemple de valeur attendue: {1}.
javax.faces.converter.LongConverter.LONG=Erreur de validation pour {2}: ''{0}'' n'est pas numérique.
javax.faces.converter.LongConverter.LONG_detail=Erreur de validation pour {2}: ''{0}'' doit être un nombre compris entre -9223372036854775808 et 9223372036854775807. Exemple de valeur attendue: {1}.
javax.faces.component.UIInput.REQUIRED=Erreur de validation pour {0}: ce champ est obligatoire.
javax.faces.converter.BigDecimalConverter.DECIMAL=Erreur de validation pour {2}: ''{0}'' doit être un nombre décimal signé.
javax.faces.converter.BigDecimalConverter.DECIMAL_detail=Erreur de validation pour {2}: ''{0}'' doit être un nombre décimal signé composé de zéro ou plusieurs chiffres, qui peuvent être suivis par une virgule et une fraction. Exemple de valeur attendue: {1}.
javax.faces.converter.DateTimeConverter.DATE=Erreur de validation pour {2}:''{0}'' ne peut pas être converti en une date.
javax.faces.converter.DateTimeConverter.DATE_detail=Erreur de validation pour {2}:''{0}'' ne peut pas être converti en une date. Exemple de valeur attendue: {1}.

- Let now JSF be aware of using custom validation messages. Edit src/main/webapp/WEB-INF/faces-config.xml such that it finally looks like:

<?xml version="1.0"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
   
 <!-- Resource Bundles (Messages and JSF Messages) -->
 <application>

  <locale-config>
    <default-locale>en</default-locale>
    <supported-locale>fr_FR</supported-locale>
  </locale-config> 
 
  <resource-bundle>
   <base-name>messages</base-name>
   <var>messages</var>
  </resource-bundle>

  <message-bundle>jsfMessages</message-bundle>

 </application>   
   
   <!-- JSF and Spring are integrated -->
   <application>
     <el-resolver>
      org.springframework.web.jsf.el.SpringBeanFacesELResolver
     </el-resolver>
   </application>
    
</faces-config>

Note that when customizing the message you can use parameters. Example:

javax.faces.converter.NumberConverter.NUMBER_detail={2}: ''{0}'' is not a number. Example: {1}.

Here {2} will be replaced by the input's label (or id if no label is specified), {0} by the value actually provided by the user for the input and {1} by some generated value that passes the validation.

c - Using JSF Templates:

Usually application pages correspond to some pattern. For example a header (with some logo and title), a footer ... etc. JSF permits to define such pages templates.


- Under src/main/webapp/ add a new folder named templates.
- Create the following Default.xhtml under src/main/webapp/templates/:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>

    <h:head>
      <link type="text/css" rel="stylesheet" href="../resources/css/myCSS.css" />
    </h:head>

  <body>

    <div id = "header_div">
      <div id = "banner_div" align = "center">
        <ui:insert name = "title">
          <h:graphicImage value="../resources/images/logo.png" />
        </ui:insert>
      </div>
      
      <div id = "subtitle_div" align = "center">
        <ui:insert name = "subtitle"/>
      </div>
      
      <div id = "status_div" align = "center">
        <ui:insert name = "status" />
      </div>
          
    </div>
    
    <div id = "menu_div"  align = "center">
      <ui:insert name = "menu"/>
    </div>
    
    <div id = "messages_div"  align = "center">
      <ui:insert name = "messages">
        <table border = "0" align = "center">
          <tr>
            <td align = "center">
              <h:messages globalOnly="true" styleClass="error-text" />
            </td>
          </tr>
        </table>
      </ui:insert>
    </div>
    
    <div id = "content_div" align = "center">
      <ui:insert name = "content"/>
    </div>
    
    <div id = "footer_div"  align = "center">
      <ui:insert name = "footer">
        <h:graphicImage value="../resources/images/footer.png" />
      </ui:insert>
    </div>

  </body>
  
</html>

Using <ui:insert /> tags we define insertions in the template. For example, in Lines 20-22, we define a header containing a banner image (../resources/images/logo.png). These insertions will be displayed in any page that instantiates the template page. Let's see how to make Connection.xhtml and AccessDenied.xhtml instantiate the Default.xhtml template.

- Edit the Connection.xhtml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.login}</h3>
    </ui:define>
    
    <ui:define name = "content">
      <h:form id = "loginForm">
        <table align = "center" border = "0">
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.userName}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputText 
                id = "j_username"
                value = "#{loginController.j_username}"
                label = "J_username"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
              <h:message for = "j_username" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.password}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputSecret
                id = "j_password"
                value = "#{loginController.j_password}"
                label = "J_password"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
                <h:message for = "j_password" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <td align = "center" colspan = "3">
              <h:commandButton
                value = "#{messages.login}"
                action = "#{loginController.doLogin()}"
              >
                <f:phaseListener type="mshj.tutorial.listener.LoginErrorPhaseListener" />
              </h:commandButton>
            </td>
          </tr>
        </table>
      </h:form>
    </ui:define>
    
  </ui:composition>
  
</html>

Here the <ui:composition /> tag states that this page instantiates a template (more precisely Default.xhtml). Then using <ui:insert /> tags we specify what insertions of the template will be overridden by the current page (here for example the insertion named content is overridden).

- Lets do something analogous with AccessDenied.xhtml by editing it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle" />
    
    <ui:define name = "content">
      <h3>#{messages.accessDenied}</h3>
    </ui:define>
    
  </ui:composition>
  
</html>

- Default.xhtml uses two images and declares a style definition file. Download these files here (logo.pngfooter.png and myCSS.css) and copy them in the corresponding directories of your Eclipse Project (src/main/webapp/resources/images for the two images and src/main/webapp/resources/css for the css file).
AccessDenied.xhtml also defines an new application message identified by the key accessDenied. Add corresponding entries to your src/main/resources/messages*.properties files, such that:
src/main/resources/messages.properties becomes:

userName=User Name
password=Password
login=Login
loginFailure=Username or Password not Valid
sessionConcurrency=Maximum Number of Concurrent Sessions Exceeded
accessDenied=Access Denied !

. and src/main/resources/messages_fr_FR.properties becomes:

userName=Nom d'Utilisateur
password=Mot de Passe
login=S'authentifier
loginFailure=Nom d'Utilisateur ou Mot de Passe Incorrect
sessionConcurrency=Nombre Maximum de Sessions Concurrentes Atteint
accessDenied=Accès Refusé !

Not all insertions defined by Default.xhtml are instantiated for now. One can populate for example the status insertion by a welcome message for the user logged in or the menu insertion with options for navigating between different pages of the application (the latter will be done in further steps).

d - Richfaces Skins:

Using Richfaces one can take advantage of lovely predefined themes. Richfaces comes with a set of three themes we propose to use the Emerald Town theme. The interesting thing here is that you can easily customize those themes.

- Under the folder src/main/resources/ create the file mshj.skin.properties as follows:

baseSkin=emeraldTown
headerBackgroundColor=#4E671D

Here we declare the skin we are using as base then we override some property of the skin.

- Edit src/main/webapp/WEB-INF/web.xml and add the following context parameters to the JSF context parameters section:

  <context-param>
    <param-name>org.richfaces.enableControlSkinning</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>org.richfaces.enableControlSkinningClasses</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>org.richfaces.skin</param-name>
    <param-value>mshj</param-value>
  </context-param>

Here we ask to be allowed to control skins (and their properties). We also provide the configuration file that will contain the base skin name and the properties we want to override.

- Deploy the application and notice the graphical changes.

3.2 - Password Update Feature:

We propose in the following to create a page where an authenticated user can change his password. For that we need a Controller that will interpose between the user and a service (BO) that does the job while relying on a Repository (DAO).
In the following we precisely show how the three layers of our application are designed in a loosely-coupled way driven by the user's need (having her password updated).

a - Presentation Layer:

In the presentation layer we need two things:
(i) a page with three input texts (the old password for more security, the new password and a confirmation of the latter) and a command button to submit the password modification;
(ii) a controller that will store the user's input and relay the corresponding update request to some service.

- Under src/main/webapp/pages/ add a new PasswordUpdate.xhtml page as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.updatePassword}</h3>
    </ui:define>

    <ui:define name = "menu">
      <h:form id = "menuForm">
        <rich:menuItem action = "#{loginController.doLogout()}" >
          <h:graphicImage value="../resources/images/logout.png" width = "64" height = "64" />
        </rich:menuItem>
      </h:form>
    </ui:define>
    
    <ui:define name = "content">
      <h:form id = "passwordUpdateForm">
        <table align = "center" border = "0">
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.currentPassword}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputSecret
                id = "current_password"
                value = "#{passwordUpdateController.currentPassword}"
                label = "current_password"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
              <h:message for = "current_password" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.newPassword}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputSecret
                id = "new_password"
                value = "#{passwordUpdateController.newPassword}"
                label = "new_password"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
              <h:message for = "new_password" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.confNewPassword}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputSecret
                id = "confNew_password"
                value = "#{passwordUpdateController.confNewPassword}"
                label = "confNew_password"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
              <h:message for = "confNew_password" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <td align = "center" colspan = "3">
              <h:commandButton
                value = "#{messages.updatePassword}"
                action = "#{passwordUpdateController.update()}"
              />
            </td>
          </tr>
        </table>
      </h:form>
    </ui:define>
    
  </ui:composition>
  
</html>

Note that we implemented the menu insertion of the Default.xhtml template to add a logout entry.
- This menu entry uses an image logout.png, download it (here) then save it under src/main/webapp/resources/images/.

The page uses also some application messages that should be added to the messages bundles:

- Edit src/main/resources/messages.properties and add the following entries:

updatePassword=Update Password
currentPassword=Current Password
newPassword=New Password
confNewPassword=New Password Confirmation


Edit src/main/resources/messages_fr_FR.properties and add the following entries:

updatePassword=Mise à Jour du Mot de Passe
currentPassword=Mot de Passe Courant
newPassword=Nouveau Mot de Passe
confNewPassword=Confirmation du Nouveau Mot de Passe

The page also refers to a controller that we create below:

- Under the package mshj.tutorial.controller create the new PasswordUpdateController.java controller as follows:

package mshj.tutorial.controller;

import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import mshj.tutorial.service.UpdatePasswordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class PasswordUpdateController implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  private String currentPassword;
  
  public String getCurrentPassword() {
    return currentPassword;
  }
  public void setCurrentPassword(String currentPassword) {
    this.currentPassword = currentPassword;
  }
  
  private String newPassword;
  
  public String getNewPassword() {
    return newPassword;
  }
  public void setNewPassword(String newPassword) {
    this.newPassword = newPassword;
  }
  
  private String confNewPassword;
  
  public String getConfNewPassword() {
    return confNewPassword;
  }
  public void setConfNewPassword(String confNewPassword) {
    this.confNewPassword = confNewPassword;
  }
  
  @Autowired
  private PasswordUpdateService passwordUpdateService;
  
  public PasswordUpdateService getPasswordUpdateService() {
    return updatePasswordService;
  }
  public void setPasswordUpdateService(PasswordUpdateService passwordUpdateService) {
    this.passwordUpdateService = passwordUpdateService;
  }
  
  
  
  public void update()
  {
    FacesContext.getCurrentInstance()
      .addMessage(null, 
        new FacesMessage(getPasswordUpdateService()
            .update(getCurrentPassword(), getNewPassword(), getConfNewPassword())));
  }
}

Here we defined containers (properties) for all input text values and the method update which relies on an auto-wired UpdatePasswordService service. What is required from that service is to try to perform the update task and return a message summarizing the results (this message is then added by the controller to the application global messages). Note that for now we don't need the full implementation of the service we only need that it implements the update method (i.e. that it fulfills the contract).
For that we use the following interface:

- Create the new package mshj.tutorial.service and create inside it the following PasswordUpdateService interface:

package mshj.tutorial.service;

public interface PasswordUpdateService
{
  String update(String currentPassword, String newPassword, String confNewPassword);
}

Note that if many profiles of developers are collaborating to fulfill the task, the presentation layer job is done and interestingly without interfering with the other layers (loose-coupling). Time now for the service layer engineer to proceed. So you can take off you "presentation layer" hat and put the "service layer" one.

b - Service (BO) Layer:

Here we start with a contract in hand. We have to provide an implementation for the PasswordUpdateService interface having in mind that all data access stuff should be delegated to a repository bean.

But what's really to be done in the Service layer ?
(i) Checking that the password update request is coherent: that the current password is really the one of the currently connected user and that the new password and its confirmation are equal.
(ii) If the request is coherent ask the Repository bean to do the update job. If not return a message asking the user to fix the error.

- Create the new package mshj.tutorial.service.impl and create inside it the following PasswordUpdateServiceImpl class:

package mshj.tutorial.service.impl;

import java.io.Serializable;
import javax.faces.context.FacesContext;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import mshj.tutorial.security.MyUserDetails;
import mshj.tutorial.service.PasswordUpdateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly=true)
public class PasswordUpdateServiceImpl implements Serializable, PasswordUpdateService
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private UserRepository userRepository;
  
  public UserRepository getUserRepository() {
    return userRepository;
  }
  public void setUserRepository(UserRepository userRepository) {
    this.userRepository = userRepository;
  }
  
  @Override
  @Transactional(readOnly=false, propagation=Propagation.REQUIRED)
  public String update(String currentPassword, String newPassword,String confNewPassword)
  {
    if (newPassword.equals(confNewPassword))
    {
      User currentUser = ((MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
      if (currentUser.getPassword().equals(currentPassword))
      {
        currentUser.setPassword(newPassword);
        getUserRepository().update(currentUser);
        return(FacesContext.getCurrentInstance().getApplication()
                .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                .getString("passwordUpdated"));
        
      }
      else
      {
        return(FacesContext.getCurrentInstance().getApplication()
                .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                .getString("wrongPassword"));
      }
    }
    else
    {
      return(FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
              .getString("passwordsMismatch"));
    }
  }
}

The bean is annotated as Service. The method update is implemented as expected: relying on the auto-wired UserRepository repository to do the update job if the parameters of the update request passes all the checks.

Messages returned by the update method must declared in the resource bundle:

Edit src/main/resources/messages.properties and add the following entries:

passwordsMismatch=New Password does not match its Confirmation
wrongPassword=Check your Current Password
passwordUpdated=Password Successfully Updated. Update will be effective after logout


Edit src/main/resources/messages_fr_FR.properties and add the following entries:

passwordsMismatch=Le Nouveau Mot de Passe et sa Confirmation ne correspondent pas
wrongPassword=Vérifier votre Mot de Passe Courant
passwordUpdated=Mot de Passe mis à jour avec Succès. La mise à jour sera effective après déconnexion


We stress out two things here. First the instruction

(MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()

permits to retrieve the currently connected user.  Second the hole service is declared to be read-only transactional. Only the method update overrides this setting being as expected declared as read-write transactional. Defining transaction settings at the service layer is a good habit since it permits a given method to perform many database accesses (through calling different repositories) in the same transaction.

Remark: Addressing the application message bundles in the service layer maybe looking as a violation of separation of concerns since the service layer is addressing a resource which is handled by the presentation layer. While writing this i was thinking as follows: the business layer is in charge of telling why an update request is not coherent (for example according to the business rule stating that the new password and its confirmation should match), the controller is only in charge of relaying this to the user.

Loose coupling between layers is also respected here. Indeed only the contract to be respected by the Repository is needed for now (we don't need the full implementation yet).

- Create the new package mshj.tutorial.repository and create inside it the following UserRepository interface:

package mshj.tutorial.repository;

import mshj.tutorial.model.User;

public interface UserRepository
{
  void update(User user);
}

Now we can proceed with designing the Repository (DAO) layer of our application task.

c - Repository (DAO) Layer:

Now we have to implement the contract of the UserRepository interface.
- Create the new package mshj.tutorial.repository.impl and create inside it the following UserRepositoryImpl class:

package mshj.tutorial.repository.impl;

import java.io.Serializable;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepositoryImpl implements Serializable, UserRepository
{
  private static final long serialVersionUID = 1L;

  @Autowired
  private SessionFactory sessionFactory;
  
  public SessionFactory getSessionFactory() {
    return sessionFactory;
  }
  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }  
  
  @Override
  public void update(User user)
  {
    getSessionFactory().getCurrentSession().update(user);
  }
}

The bean is declared as Repository and relies in turn on the Hibernate Session to perform the password update.  and ... we are done !

Before deploying and testing the new page, update the mshj.tutorial.handler.MyAuthenticationSuccessHandler bean to redirect upon a successful authentication the users having no roles to PasswordUpdate.xhtml as follows:

// ...
      if (roles.isEmpty())
      {
        response.sendRedirect("PasswordUpdate.xhtml");
      }
// ...

- Deploy and test the application. Notice that you have now a new button to log out from the application.

3.3 - Generic Spring Components:

A Repository bean will completely rely on Hibernate to perform usual database operations. This motivates thinking about a Generic Repository class (implementing some Generic Repository Interface) that groups the usual Hibernate calls (insert, update, delete, conditioned select) and which will be extended by all specific repositories needed by the application.

a - Generic Repository (DAO):

- Under the package mshj.tutorial.repository create the parametric interface GenericRepository as follows:

package mshj.tutorial.repository;

import java.io.Serializable;
import java.util.List;

public interface GenericRepository<T, ID extends Serializable>
{ 
  public ID save(T entity);
  public void update(T entity);
  public void delete(T entity);
  public List<T> findAll();
  public List<T> find(String query);
}


The interface is parametric of T the type of the Model Class (mapping some table) and ID the type of the class' identifier property (the property mapping the primary key of the table). It presents the usual contract (list of qualified methods) a Repository (DAO) should perform which will be implemented by the its generic implementation:

- Under the package mshj.tutorial.repository.impl create the parametric class GenericRepositoryImpl as follows:

package mshj.tutorial.repository.impl;

import java.io.Serializable;
import java.util.List;
import org.hibernate.SessionFactory;
import mshj.tutorial.repository.GenericRepository;
import org.springframework.beans.factory.annotation.Autowired;

public class GenericRepositoryImpl<T, ID extends Serializable> implements GenericRepository<T, ID>
{
  @Autowired
  private SessionFactory sessionFactory;
  
  public SessionFactory getSessionFactory() {
    return sessionFactory;
  }
  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  private Class<T> myClass;

  public Class<T> getMyClass() {
    return myClass;
  }
  public void setMyClass(Class<T> myClass) {
    this.myClass = myClass;
  }

  public GenericRepositoryImpl(Class<T> myClass)
  {
    this.myClass = myClass;
  }
  
  @SuppressWarnings("unchecked")
  public ID save(T entity)
  {
    return((ID)getSessionFactory().getCurrentSession().save(entity));
  }

  public void update(T entity)
  {
    getSessionFactory().getCurrentSession().update(entity);
  }

  public void delete(T entity)
  {
    getSessionFactory().getCurrentSession().delete(entity);
  }
  
  @SuppressWarnings("unchecked")
  public List<T> findAll()
  {
    return(getSessionFactory().getCurrentSession().createQuery("from " + getMyClass().getSimpleName()).list());
  } 
  
  @SuppressWarnings("unchecked")
  public List<T> find(String queryString)
  {
    return(getSessionFactory().getCurrentSession().createQuery(queryString).list());
  }
}


The class implements the usual contract (list of qualified methods) a Repository (DAO) should perform:

public ID save(T entity); That takes as parameter an instance of the class T describing a line to be inserted in the mapped table and returns an instance of ID which contains the value of the primary key of the inserted line. Returning the value of the primary key is very useful when it is automatically generated using a strategy supported by Hibernate, example: using some auto-incremented sequence.

public void update(T entity); That takes as parameter an instance of the class T describing a line to be updated in the mapped table. Here entity specifies the criteria of selection of the line to be updated (basically its primary key value) as well as the updates to be performed (all other non-primary key fields).

public void delete(T entity); That takes as parameter an instance of the class T describing a line to be deleted in the mapped table.

public List<T> findAll(); Returns a List of instances of the class T corresponding to all the mapped table lines.

public List<T> find(String query); Returns a List of instances of the class T corresponding to the mapped table lines satisfying the HQL (Hibernate Query Language) query query.


We notice here that all methods implementations simply call corresponding methods of the available Hibernate Session which is available using the instruction getSessionFactory().getCurrentSession() (remark that the SessionFactory is auto-wired).The Hibernate Session transparently (w.r.t. to our DAO code) takes care of the database connections, statement creation and execution, result sets parsing ... etc.


Having this done one can easily derive implementations for our UserRepository interface and UserRepositoryImpl class as follows:

- To keep copies of the old Repository interface and class, rename (outside Eclipse in your file system explorer) mshj.tutorial.repository.UserRepoistory.java (respectively mshj.tutorial.repository.impl.UserRepoistoryImpl.java) to mshj.tutorial.repository.UserRepoistory.java.old (respectively to mshj.tutorial.repository.impl.UserRepoistoryImpl.java.old).
Then in Eclipse right-click on your project and choose refresh.


- Under the package mshj.tutorial.repository create the new version of UserRepository interface as follows:

package mshj.tutorial.repository;

import mshj.tutorial.model.User;

public interface UserRepository extends GenericRepository<User, Long>
{
  void update(User user);
}


- Under the package mshj.tutorial.repository.impl create the new version of UserRepositoryImpl class as follows:

package mshj.tutorial.repository.impl;

import java.io.Serializable;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepositoryImpl extends GenericRepositoryImpl<User, Long> implements Serializable, UserRepository
{
  private static final long serialVersionUID = 1L;
  
  public UserRepositoryImpl()
  {
    super(User.class);
  }
}

Note that here we fix the type of the parameter T of GenericRepository(Impl) to be User in the constructor.
Note also how concise is now the definition of the Repository layer.

- Deploy and try the new application version.

Comments: Using a Generic Repository bean can be quite useful since the code to be written for specific repositories becomes very concise. Nevertheless the methods you may bookmark in the Generic Repository may be not all needed by (or maybe not sufficient for) all the specific repositories you are brought to write for your application. An optimal solution maybe to stick to the minimum in the Generic Repository : just auto-wire the SessionFactory, provide a myClass property and only a getter for the CurrentSession. Then provide the methods (database operations) you need in your specific repository: declare them as a contract in the repository interface then implement them in the repository class using the inherited CurrentSession getter. The proposed solution release you from handling SessionFactory in your specific repositories while keeping some level of genericity.
Remark: It would have been great if we could shift all @Repository annotations to the GenericRepository or GenericRepositoryImpl ! Unfortunately this actually does not work.

b - Generic Service (BO):

Thinking about Generic Services came to my mind when i was a JEE beginner and thus writing bad JEE code ! At that time i was creating BO's that simply encapsulate DAO's calls and my controllers was wrongly calling as much BO's as i needed and using them in a "spaghetti code fashion" to obtain results requested by the user. In this flawed setting i had as many BO's as DAO's and it seemed more efficient to use Generic BO's that inject the corresponding DAO in a given BO.
Hindsight i realized this is not the perfect way to proceed. Putting a Controller back in its place (only collecting user requests and returning corresponding responses to her) i now write BO's that may use many DAO's thus breaking down the possibility of genericity for the Service layer.

Nevertheless in some cases (where a BO is really using only one DAO) one can take advantage of genericity. This is more or less our case for the Password Update task.

- In the mshj.tutorial.service create the GenericService interface as follows:

package mshj.tutorial.service;

import java.io.Serializable;

public interface GenericService<T,ID extends Serializable> 
{

}

- In the mshj.tutorial.service.impl create the GenericServiceImpl interface as follows:

package mshj.tutorial.service.impl;

import java.io.Serializable;
import mshj.tutorial.service.GenericService;
import mshj.tutorial.repository.GenericRepository;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class GenericServiceImpl <T, ID extends Serializable, D extends GenericRepository<T,ID>> implements GenericService<T, ID>
{
  private Class<T> modelClass;
  
  public Class<T> getModelClass() {
    return modelClass;
  }
  public void setModelClass(Class<T> modelClass) {
    this.modelClass = modelClass;
  }  
  
  private Class<D> repositoryClass;
  
  public Class<D> getRepositoryClass() {
    return repositoryClass;
  }
  public void setRepositoryClass(Class<D> repositoryClass) {
    this.repositoryClass = repositoryClass;
  }
  
  private D myRepository;
  
  public D getMyRepository() {
    return myRepository;
  }

  @Autowired
  public void setMyRepository(D myRepository) {
    this.myRepository = myRepository;
  }
  
  public GenericServiceImpl(Class<T> modelClass, Class<D> repositoryClass)
  {
    this.modelClass = modelClass;
    this.repositoryClass = repositoryClass;
  }

  public ID save(T entity)
  {
    return(getMyRepository().save(entity));
  }

  public void update(T entity)
  {
    getMyRepository().update(entity);
  }

  public void delete(T entity)
  {
    getMyRepository().delete(entity);
  }
  
  public List findAll()
  {
    return(getMyRepository().findAll());
  }
  
  public List find(String request)
  {
    return(getMyRepository().find(request));
  }
}

Note that a Repository (extending GenericRepository) is injected in the GenericServiceImpl class. Very Important !!! The dependency injection is specified on the setter (vs. on the field itself). On run-time, this "gives time" to Spring to find precisely which instance of type D (extending GenericRepository<T,ID>) to instantiate for the field myRepository depending on the parameters <T, ID, D<T,ID>> of your bean service implementing GenericServiceImpl. More precisely if you write several repositories extending GenericRepositoryImpl (even if they have each a different set of prameters) and if you define the dependency injection on the field, Spring will through (among others) the exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [mshj.tutorial.repository.GenericRepository] is defined: expected single matching bean but found n: ...

where n is here the number of classes instantiating GenericRepositoryImpl. You can read more about Spring dependency injection here (Especially the Section 4.4.1.2 Setter-based dependency injection).

Having this done one can easily derive implementations for our PasswordUpdateService interface and PasswordUpdateServiceImpl class as follows:

- To keep copies of the old Service interface and class, rename (outside Eclipse in your file system explorer) mshj.tutorial.service.PasswordUpdateService.java (respectively mshj.tutorial.service.impl.PasswordUpdateServiceImpl.java) to mshj.tutorial.service.PasswordUpdateService.java.old (respectively to mshj.tutorial.service.impl.PasswordUpdateServiceImpl.java.old)
Then in Eclipse right-click on your project and choose refresh.

- Under the package mshj.tutorial.service create the new version of PasswordUpdateService interface as follows:

package mshj.tutorial.service;

import mshj.tutorial.model.User;

public interface PasswordUpdateService extends GenericService<User, Long>
{
  public String update(String currentPassword, String newPassword,String confNewPassword);
}

Note that the interface declares the signature of the method update needed by the PasswordUpdateController.

- Under the package mshj.tutorial.service.impl create the new version of PasswordUpdateServiceImpl class as follows:

package mshj.tutorial.service.impl;

import java.io.Serializable;
import java.util.List;
import javax.faces.context.FacesContext;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import mshj.tutorial.security.MyUserDetails;
import mshj.tutorial.service.PasswordUpdateService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly=true)
public class PasswordUpdateServiceImpl extends GenericServiceImpl<User, Long, UserRepository> implements Serializable, PasswordUpdateService
{
  private static final long serialVersionUID = 1L;
  
  public PasswordUpdateServiceImpl()
  {
    super(User.class, UserRepository.class);
  }
  
  @Override
  @Transactional(readOnly=false, propagation=Propagation.REQUIRED)
  public String update(String currentPassword, String newPassword,String confNewPassword)
  {
    if (newPassword.equals(confNewPassword))
    {
      User currentUser = ((MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
      if (currentUser.getPassword().equals(currentPassword))
      {
        currentUser.setPassword(newPassword);
        update(currentUser);
        return(FacesContext.getCurrentInstance().getApplication()
                .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                .getString("passwordUpdated"));
        
      }
      else
      {
        return(FacesContext.getCurrentInstance().getApplication()
                .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
                .getString("wrongPassword"));
      }
    }
    else
    {
      return(FacesContext.getCurrentInstance().getApplication()
              .getResourceBundle(FacesContext.getCurrentInstance(), "messages")
              .getString("passwordsMismatch"));
    }
  }
}

No need to auto-wire the Repository now since this is already done by the generic super class.
Note also that the update(currentUser); call addresses now a method provided by the generic service.

Comments: Remember that Generic Services (BOs) are useful only in specific cases (only one DAO is used by the BO). Nevertheless one can adapt Generic BOs to different needs: BOs using exactly 2 DAOs (Master/Detail setting) ... etc. Off course it is not mandatory that all your Services will be extending Generic ones, do it only if this makes writing code easier for you.
Remark: It would have been great if we could shift all our @Transactional annotations to the GenericService or GenericServiceImpl ! Or at least the @Service annotation ... Unfortunately this actually does not work. For the @Transactional annotations this is not a big limitation since you maybe want to have a more fine-grained control on the transactional aspects of your implemented service methods.

c - Generic Controller:


As we did for other layers one can also think about grouping useful code in some Generic Controller class and making (a subset of) all other Controllers extend it. Example of features you might need for a controller are:

(i) Adding a global message to a view (targeting <h:messages /> tag).
(ii) Getting a message from the application messages resource bundle given its key;
(iii) Getting the current connected user (for example to display a customized welcome message) ... etc.

Note that features (ii) and (iii) could also be available at the Service (BO) layer, and thus one can also think about adding them to a Generic Bo ...
Even feature (i) is used by our LoginErrorPhaseListener ...

So what if we do simpler and implement all these features in static methods of some abstract class. Then all the layers can access the abstract class. Deal ?

- Create the package mshj.tutorial.util and create the ApplicationUtil abstract class as follows:

package mshj.tutorial.util;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import mshj.tutorial.model.User;
import mshj.tutorial.security.MyUserDetails;
import org.springframework.security.core.context.SecurityContextHolder;

public abstract class ApplicationUtil
{  
  public static User getConnectedUser()
  {
    return(((MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser());
  }
  
  public static String getApplicationMessage(String bundle, String key)
  {
    return((FacesContext.getCurrentInstance().getApplication()
      .getResourceBundle(FacesContext.getCurrentInstance(), bundle)
      .getString(key) != null)
        ?FacesContext.getCurrentInstance().getApplication()
          .getResourceBundle(FacesContext.getCurrentInstance(), bundle)
          .getString(key)
          :"???" + key + "???");
  }
  
  public static String getApplicationMessage(String key)
  {
    return(getApplicationMessage("messages",key));
  }
  
  public static void addJSFMessage(String component, String message)
  {
    FacesContext.getCurrentInstance()
    .addMessage(component, 
      new FacesMessage(message));
  }
  
  public static void addJSFMessageByKey(String component, String key)
  {
    FacesContext.getCurrentInstance()
    .addMessage(component, 
      new FacesMessage(getApplicationMessage(key)));
  }  
  
  public static void addGlobalJSFMessage(String message)
  {
    addJSFMessage(null, message);
  }
  
  public static void addGlobalJSFMessageByKey(String key)
  {
    addJSFMessageByKey(null, key);
  }
}

Here we defined the different features described above. Please note that you can add a message to a specific component of a page given its "path" in the page. Let's say you have an input text with id it_id inside a form having id f_id the path of this component is "f_id.it_id". The message will then be displayed at the position where a <h:message /> tag was defined for the component.

Using this class will make our code more readable. Now we have to update classes using these features and replace the long instructions they call by simple calls to ApplicationUtil's methods:

- Update mshj.tutorial.listener.LoginErrorPhaseListener as follows:

package mshj.tutorial.listener;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;

public class LoginErrorPhaseListener implements PhaseListener
{
  private static final long serialVersionUID = 1L;
  
  @Override
  public void afterPhase(PhaseEvent arg0)
  {
    
  }
  
  @Override
  public void beforePhase(PhaseEvent arg0)
  {
    Exception e = (Exception) FacesContext.getCurrentInstance().
      getExternalContext().getSessionMap().get(WebAttributes.AUTHENTICATION_EXCEPTION);
       
    if (e instanceof BadCredentialsException)
    {
      FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
        WebAttributes.AUTHENTICATION_EXCEPTION, null);
      ApplicationUtil.addGlobalJSFMessageByKey("loginFailure");
    }
    else
    {
      if (e instanceof SessionAuthenticationException)
      {
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
          WebAttributes.AUTHENTICATION_EXCEPTION, null);
        ApplicationUtil.addGlobalJSFMessageByKey("sessionConcurrency");
      }
    }
  }
  
  @Override
  public PhaseId getPhaseId()
  {
    return PhaseId.RENDER_RESPONSE;
  }
}



- Update mshj.tutorial.service.impl.PasswordUpdateServiceImpl as follows:

package mshj.tutorial.service.impl;

import java.io.Serializable;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import mshj.tutorial.service.PasswordUpdateService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly=true)
public class PasswordUpdateServiceImpl extends GenericServiceImpl implements Serializable, PasswordUpdateService
{
  private static final long serialVersionUID = 1L;
  
  public PasswordUpdateServiceImpl()
  {
    super(User.class, UserRepository.class);
  }
  
  @Override
  @Transactional(readOnly=false, propagation=Propagation.REQUIRED)
  public String update(String currentPassword, String newPassword,String confNewPassword)
  {
    if (newPassword.equals(confNewPassword))
    {
      User currentUser = ApplicationUtil.getConnectedUser();
      if (currentUser.getPassword().equals(currentPassword))
      {
        currentUser.setPassword(newPassword);
        update(currentUser);
        return(ApplicationUtil.getApplicationMessage("passwordUpdated"));
        
      }
      else
      {
        return(ApplicationUtil.getApplicationMessage("wrongPassword"));
      }
    }
    else
    {
      return(ApplicationUtil.getApplicationMessage("passwordsMismatch"));
    }
  }
}


- Update mshj.tutorial.controller.PasswordUpdateController as follows:

package mshj.tutorial.controller;

import java.io.Serializable;
import mshj.tutorial.service.PasswordUpdateService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class PasswordUpdateController implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  private String currentPassword;
  
  public String getCurrentPassword() {
    return currentPassword;
  }
  public void setCurrentPassword(String currentPassword) {
    this.currentPassword = currentPassword;
  }
  
  private String newPassword;
  
  public String getNewPassword() {
    return newPassword;
  }
  public void setNewPassword(String newPassword) {
    this.newPassword = newPassword;
  }
  
  private String confNewPassword;
  
  public String getConfNewPassword() {
    return confNewPassword;
  }
  public void setConfNewPassword(String confNewPassword) {
    this.confNewPassword = confNewPassword;
  }
  
  @Autowired
  private PasswordUpdateService passwordUpdateService;
  
  public PasswordUpdateService getPasswordUpdateService() {
    return passwordUpdateService;
  }
  public void setPasswordUpdateService(PasswordUpdateService updatePasswordService) {
    this.passwordUpdateService = updatePasswordService;
  }
  
  public void update()
  {
    ApplicationUtil.addGlobalJSFMessage(
        getPasswordUpdateService()
          .update(getCurrentPassword(), getNewPassword(), getConfNewPassword())
          );
  }
}

We are now done with generic components. We proceed with adding more features to the application while introducing more interesting features of JSF and Richfaces.

3.4 - Super User Features: JSF and Richfaces useful components:

We are now going to add super user features to the application. We need 3 pages for these features: a page for administrating users, a page for administrating profiles and another one for administrating roles. We start by providing the pages and the different application layer they require before explaining used JSF and Richfaces components in details.

a - Super User features:

- Under the package mshj.tutorial.controller create the following three controllers:

. UsersAdministrationController.java:

package mshj.tutorial.controller;

import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import mshj.tutorial.model.Profile;
import mshj.tutorial.model.User;
import mshj.tutorial.service.ManageUserService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class UsersAdministrationController implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private ManageUserService manageUserService;
  
  public ManageUserService getManageUserService() {
    return manageUserService;
  }
  public void setManageUserService(ManageUserService manageUserService) {
    this.manageUserService = manageUserService;
  }
  
  private User newUser;
  
  public User getNewUser() {
    return newUser;
  }
  public void setNewUser(User newUser) {
    this.newUser = newUser;
  }
  
  private String newUserConfPassword;
  
  public String getNewUserConfPassword() {
    return newUserConfPassword;
  }
  public void setNewUserConfPassword(String newUserConfPassword) {
    this.newUserConfPassword = newUserConfPassword;
  }
  
  @PostConstruct
  public void init()
  {
    clearNewUserForm();
  }
  
  public void clearNewUserForm()
  {
    setNewUser(new User());
    getNewUser().setProfiles(new ArrayList<Profile>());
    setNewUserConfPassword(getNewUser().getPassword());
  }
  
  public void addUser()
  {    
    if (getNewUserConfPassword().equals(getNewUser().getPassword()))
    {
      try
      {
        getManageUserService().addUser(getNewUser());
        clearNewUserForm();
        ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userAdded"));
      }
      catch(Exception e)
      {
        ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userNotAdded"));
      }
    }
    else
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userNotAddedPasswordsMismatch"));
    }
  }
  
  public void updateUser(User user)
  {
    try
    {
      getManageUserService().updateUser(user);
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userUpdated"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userNotUpdated"));
    }
  }
  
  public void deleteUser(User user)
  {
    getManageUserService().deleteUser(user);
    ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("userDeleted"));
  }
}


ProfilesAdministrationController.java:

package mshj.tutorial.controller;

import java.io.Serializable;
import java.util.ArrayList;
import javax.annotation.PostConstruct;
import mshj.tutorial.model.Profile;
import mshj.tutorial.model.Role;
import mshj.tutorial.service.ProfileService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class ProfilesAdministrationController implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private ProfileService profileService;
  
  public ProfileService getProfileService() {
    return profileService;
  }
  public void setProfileService(ProfileService profileService) {
    this.profileService = profileService;
  }
  
  private Profile newProfile;
  
  public Profile getNewProfile() {
    return newProfile;
  }
  public void setNewProfile(Profile newProfile) {
    this.newProfile = newProfile;
  }
  
  @PostConstruct
  public void init()
  {
    clearNewProfileForm();
  }
  
  public void clearNewProfileForm()
  {
    setNewProfile(new Profile());
    getNewProfile().setRoles(new ArrayList<Role>());
  }
  
  public void addProfile()
  {
    try
    {
      getProfileService().addProfile(getNewProfile());
      clearNewProfileForm();
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileAdded"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileNotAdded"));
    }
  }
  
  public void updateProfile(Profile profile)
  {
    try
    {
      getProfileService().updateProfile(profile);
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileUpdated"));      
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileNotUpdated"));
    }
  }
  
  public void deleteProfile(Profile profile)
  {
    try
    {
      getProfileService().deleteProfile(profile);
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileDeleted"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("profileNotDeleted"));
    }
  }
}


RolesAdministrationController.java:

package mshj.tutorial.controller;

import java.io.Serializable;
import javax.annotation.PostConstruct;
import mshj.tutorial.model.Role;
import mshj.tutorial.service.RoleService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class RolesAdministrationController implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private RoleService roleService;
  
  public RoleService getRoleService() {
    return roleService;
  }
  public void setRoleService(RoleService roleService) {
    this.roleService = roleService;
  }
  
  private Role newRole;
  
  public Role getNewRole() {
    return newRole;
  }
  public void setNewRole(Role newRole) {
    this.newRole = newRole;
  }
  
  @PostConstruct
  public void init()
  {
    clearNewRoleForm();
  }
  
  public void clearNewRoleForm()
  {
    setNewRole(new Role());
  }
  
  public void addRole()
  {
    try
    {
      getRoleService().addRole(getNewRole());
      clearNewRoleForm();
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleAdded"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleNotAdded"));
    }
  }
  
  public void updateRole(Role role)
  {
    try
    {
      getRoleService().updateRole(role);
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleUpdated"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleNotUpdated"));
    }
  }
  
  public void deleteRole(Role role)
  {
    try
    {
      getRoleService().deleteRole(role);
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleDeleted"));
    }
    catch (Exception e)
    {
      ApplicationUtil.addGlobalJSFMessage(ApplicationUtil.getApplicationMessage("roleNotDeleted"));
    }
  }
}

Comments: Each of the the three controllers above uses a dedicated Service to perform usual database operations (add, update and delete) and provides a property field to hold user data for the creation of a new entry: example private User newUser; for the UsersAdministrationController.java. This field is re-initialized after each new insertion.


- Under the package mshj.tutorial.service create the following three service interfaces:

. ManageUserService.java:

package mshj.tutorial.service;

import java.util.List;

import mshj.tutorial.model.User;

public interface ManageUserService extends GenericService<User, Long>
{
  List<User> findAllButConnectedUser();
  void updateUser(User user);
  void deleteUser(User user);
  void addUser(User newUser);
}


. ProfileService.java

package mshj.tutorial.service;

import java.util.List;

import mshj.tutorial.model.Profile;

public interface ProfileService extends GenericService<Profile, Long>
{
  Profile findByTitle(String title);
  List<Profile> findAllProfiles();
  void updateProfile(Profile profile);
  void deleteProfile(Profile profile);
  void addProfile(Profile profile);
}


. RoleService.java

package mshj.tutorial.service;

import java.util.List;

import mshj.tutorial.model.Role;

public interface RoleService extends GenericService<Role, Long>
{
  Role findByTitle(String title);
  List<Role> findAllRoles();
  void updateRole(Role role);
  void deleteRole(Role role);
  void addRole(Role role);
}


Comments: Each of the three service contracts expose methods used by the controllers.



- Under the package mshj.tutorial.service.impl update the following service implementation:


. ManageUserServiceImpl.java:

package mshj.tutorial.service.impl;

import java.io.Serializable;
import java.util.List;
import mshj.tutorial.model.User;
import mshj.tutorial.repository.UserRepository;
import mshj.tutorial.service.ManageUserService;
import mshj.tutorial.util.ApplicationUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly=true)
public class ManageUserServiceImpl extends GenericServiceImpl<User, Long, UserRepository> implements Serializable, ManageUserService
{
  private static final long serialVersionUID = 1L;
  
  public ManageUserServiceImpl()
  {
    super(User.class, UserRepository.class);
  }
  
  @Override
  public List<User> findAllButConnectedUser()
  {    
    List<User> allUsers = findAll();
    allUsers.remove(ApplicationUtil.getConnectedUser());
    return(allUsers);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void updateUser(User user)
  {
    update(user);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void deleteUser(User user)
  {
    delete(user);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void addUser(User newUser)
  {
    save(newUser);
  }
}

- Under the package mshj.tutorial.service.impl create the following two service implementations:

. ProfileServiceImpl.java

package mshj.tutorial.service.impl;
 
import java.io.Serializable;
import java.util.List;
import mshj.tutorial.model.Profile;
import mshj.tutorial.repository.ProfileRepository;
import mshj.tutorial.service.ProfileService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
@Transactional(readOnly=true)
public class ProfileServiceImpl extends GenericServiceImpl<Profile, Long, ProfileRepository> implements Serializable, ProfileService
{
  private static final long serialVersionUID = 1L;
   
  public ProfileServiceImpl()
  {
    super(Profile.class, ProfileRepository.class);
  }
    
  public Profile findByTitle(String title)
  {
    return(find("from Profile where title like '" + title + "'").get(0));
  }
  
  @Override
  public List<Profile> findAllProfiles()
  {    
    return(findAll());
  }
  
  @Override
  @Transactional(readOnly=false)
  public void updateProfile(Profile profile)
  {
    update(profile);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void deleteProfile(Profile profile)
  {
    delete(profile);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void addProfile(Profile newProfile)
  {
    save(newProfile);
  }  
}


. RoleServiceImpl.java

package mshj.tutorial.service.impl;
 
import java.io.Serializable;
import java.util.List;
import mshj.tutorial.model.Role;
import mshj.tutorial.repository.RoleRepository;
import mshj.tutorial.service.RoleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
@Transactional(readOnly=true)
public class RoleServiceImpl extends GenericServiceImpl<Role, Long, RoleRepository> implements Serializable, RoleService
{
  private static final long serialVersionUID = 1L;
   
  public RoleServiceImpl()
  {
    super(Role.class, RoleRepository.class);
  }
    
  public Role findByTitle(String title)
  {
    return(find("from Role where title like '" + title + "'").get(0));
  }
  
  @Override
  public List<Role> findAllRoles()
  {    
    return(findAll());
  }
  
  @Override
  @Transactional(readOnly=false)
  public void updateRole(Role role)
  {
    update(role);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void deleteRole(Role role)
  {
    delete(role);
  }
  
  @Override
  @Transactional(readOnly=false)
  public void addRole(Role newRole)
  {
    save(newRole);
  }  
}


Comments: Each of the three service implementations implement their respective contract methods (reusing methods inherited from the GenericService) and declare their transactional semantics.


- Under the package mshj.tutorial.repository create the following two repository interfaces:

. ProfileRepository.java

package mshj.tutorial.repository;
 
import mshj.tutorial.model.Profile;
 
public interface ProfileRepository extends GenericRepository<Profile, Long>
{
}


. RoleRepository.java

package mshj.tutorial.repository;
 
import mshj.tutorial.model.Role;
 
public interface RoleRepository extends GenericRepository<Role, Long>
{
}


- Under the package mshj.tutorial.repository.impl create the following three service implementations:

. ProfileRepositoryImpl.java

package mshj.tutorial.repository.impl;
 
import java.io.Serializable;
import mshj.tutorial.model.Profile;
import mshj.tutorial.repository.ProfileRepository;
import org.springframework.stereotype.Repository;

@Repository
public class ProfileRepositoryImpl extends GenericRepositoryImpl<Profile, Long> implements Serializable, ProfileRepository
{
  private static final long serialVersionUID = 1L;
   
  public ProfileRepositoryImpl()
  {
    super(Profile.class);
  }
}


. RoleRepositoryImpl.java

package mshj.tutorial.repository.impl;
 
import java.io.Serializable;
import mshj.tutorial.model.Role;
import mshj.tutorial.repository.RoleRepository;
import org.springframework.stereotype.Repository;

@Repository
public class RoleRepositoryImpl extends GenericRepositoryImpl<Role, Long> implements Serializable, RoleRepository
{
  private static final long serialVersionUID = 1L;
   
  public RoleRepositoryImpl()
  {
    super(Role.class);
  }
}

Remark: Adding, Updating or Deleting Users, Profiles or Roles entries may lead to exceptions (org.springframework.dao.DataIntegrityViolationException). This happens whenever a request breaks a constraint at the database level: primary key, unique constraints or deleting entries which are referenced by foreign key in other tables etc. Putting the try/catch blocks in the service level (and not in the controller level as done here) would be a cleaner way to write the code but unfortunately the exceptions will not be caught at that level in all cases. This is due to the fact that these exceptions are thrown after the current transaction commits and that we put our transactional semantics in the service layer. A way to work around this would be to define the transactional semantics at the repository level.

- Under src/main/webapp/pages/ create the following three JSF pages:

. UsersAdministration.xhtml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.usersAdministration}</h3>
    </ui:define>
    
    <ui:define name = "content">
      <table border = "0" align = "center">
        <tr>
          <td align = "center">
            <rich:accordion
              id = "usersAdministrationAccordion"
              rendered = "true"
              switchType = "client"
            >
              <rich:accordionItem
                id = "addNewUserAccordionItem"
                header = "#{messages.addNewUser}"
                rendered = "true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "addNewUserAccordionItemForm">
                  <table border = "0" align = "center">
                    <tr>
                      <th align = "center">
                        <h:outputText value = "#{messages.userId}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.userName}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.password}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.confPassword}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.userIdentity}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.userProfiles}" styleClass="title-text"/>
                      </th>
                      <th align = "center"/>
                    </tr>
                    <tr>
                      <td align = "center">
                        <h:inputText id = "newUserId" value = "#{usersAdministrationController.newUser.id}" required = "true" size = "4" maxlength = "4" />
                        <h:message for = "newUserId" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputText id = "newUserLogin" value = "#{usersAdministrationController.newUser.login}" required = "true" size = "8" maxlength = "8" />
                        <h:message for = "newUserLogin" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputSecret id = "newUserPassword" value = "#{usersAdministrationController.newUser.password}" required = "true" size = "8" maxlength = "8" redisplay="true" />
                        <h:message for = "newUserPassword" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputSecret id = "newUserConfPassword" value = "#{usersAdministrationController.newUserConfPassword}" required = "true" size = "8" maxlength = "8" redisplay="true" />
                        <h:message for = "newUserConfPassword" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputText id = "newUserIdentity" value = "#{usersAdministrationController.newUser.identity}" required = "true" size = "25" maxlength = "256" />
                        <h:message for = "newUserIdentity" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <rich:pickList
                          id = "newUserProfiles"
                          value = "#{usersAdministrationController.newUser.profiles}"
                          var = "profile"
                          sourceCaption = "#{messages.availableProfiles}"
                          targetCaption = "#{messages.ownedProfiles}"
                          sourceListWidth = "450px"
                          targetListWidth = "450px"
                          listHeight = "100px"
                          orderable = "true"
                          rendered = "true"
                          disabled = "false"
                          required = "false"
                          requiredMessage = "#{messages.profileMandatory}"
                          addText  = "→"
                          addAllText  = "⇒"
                          removeText  = "←"
                          removeAllText  = "⇐"
                          upTopText  = "⇑"
                          upText  = "↑"
                          downText  = "↓"
                          downBottomText  = "⇓"
                          collectionType="java.util.ArrayList"
                        >
                          <!--
                            collectionType="java.util.ArrayList" is necessary
                            see http://manuel-palacio.blogspot.com/2011/02/hibernate-jsf-2-and-manytomany.html
                          -->
                          <f:selectItems value="#{profilesSelectItems.values}" />
                          <f:converter binding="#{profileConverter}" />
                          <rich:column
                            rendered = "true"
                          >
                            <f:facet name = "header">
                              <h:outputText
                                value = "#{messages.id}"
                                rendered = "true"
                                styleClass = "title-text"
                              />
                            </f:facet>
                            <h:outputText
                              value = "#{profile.id}"
                              rendered = "true"
                              styleClass = "data-text"
                            />
                          </rich:column>
                          <rich:column
                            rendered = "true"
                          >
                            <f:facet name = "header">
                              <h:outputText
                                value = "#{messages.title}"
                                rendered = "true"
                                styleClass = "title-text"
                              />
                            </f:facet>
                            <h:outputText
                              value = "#{profile.title}"
                              rendered = "true"
                              styleClass = "data-text"
                            />
                          </rich:column>
                        </rich:pickList>                                    
                      <h:message for = "newUserProfiles" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:commandButton value = "#{messages.add}" action = "#{usersAdministrationController.addUser()}" />
                      </td>
                    </tr>
                  </table>
                </h:form>
              </rich:accordionItem>      
              <rich:accordionItem
                id = "manageExistingUsersAccordionItem"
                header = "#{messages.manageExistingUsers}"
                rendered="true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "manageExistingUsersAccordionItemForm">
                  <h:dataTable
                    value="#{usersAdministrationController.manageUserService.findAllButConnectedUser()}"
                    var="user"
                    styleClass="order-table"
                    headerClass="order-table-header"
                    rowClasses="order-table-odd-row,order-table-even-row"
                  >
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.userId}" styleClass="title-text"/>
                      </f:facet>
                      <h:outputText value = "#{user.id}" styleClass="data-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.userName}" styleClass="title-text"/>
                      </f:facet>
                      <h:inputText id = "userLogin" value = "#{user.login}" required = "true" size = "8" maxlength = "8" />
                      <h:message for = "userLogin" styleClass = "error-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.password}" styleClass="title-text"/>
                      </f:facet>
                      <h:inputSecret id = "userPassword" value = "#{user.password}" required = "true" size = "8" maxlength = "8" redisplay="true" />
                      <h:message for = "userPassword" styleClass = "error-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.userIdentity}" styleClass="title-text"/>
                      </f:facet>
                      <h:inputText id = "userIdentity" value = "#{user.identity}" required = "true" size = "25" maxlength = "256"  />
                      <h:message for = "userIdentity" styleClass = "error-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.userProfiles}" styleClass="title-text"/>
                      </f:facet>
                      <rich:pickList
                        id = "userProfiles"
                        value = "#{user.profiles}"
                        var = "profile"
                        sourceCaption = "#{messages.availableProfiles}"
                        targetCaption = "#{messages.ownedProfiles}"
                        sourceListWidth = "450px"
                        targetListWidth = "450px"
                        listHeight = "100px"
                        orderable = "true"
                        rendered = "true"
                        disabled = "false"
                        required = "false"
                        requiredMessage = "#{messages.profileMandatory}"
                        addText  = "→"
                        addAllText  = "⇒"
                        removeText  = "←"
                        removeAllText  = "⇐"
                        upTopText  = "⇑"
                        upText  = "↑"
                        downText  = "↓"
                        downBottomText  = "⇓"
                        collectionType="java.util.ArrayList"
                      >
                        <!--
                          collectionType="java.util.ArrayList" is necessary
                          see http://manuel-palacio.blogspot.com/2011/02/hibernate-jsf-2-and-manytomany.html
                        -->
                        <f:selectItems value="#{profilesSelectItems.values}" />
                        <f:converter binding="#{profileConverter}" />
                        <rich:column
                          rendered = "true"
                        >
                          <f:facet name = "header">
                            <h:outputText
                              value = "#{messages.id}"
                              rendered = "true"
                              styleClass = "title-text"
                            />
                          </f:facet>
                          <h:outputText
                            value = "#{profile.id}"
                            rendered = "true"
                            styleClass = "data-text"
                          />
                        </rich:column>
                        <rich:column
                          rendered = "true"
                        >
                          <f:facet name = "header">
                            <h:outputText
                              value = "#{messages.title}"
                              rendered = "true"
                              styleClass = "title-text"
                            />
                          </f:facet>
                          <h:outputText
                            value = "#{profile.title}"
                            rendered = "true"
                            styleClass = "data-text"
                          />
                        </rich:column>
                      </rich:pickList>                                    
                      <h:message for = "userProfiles" styleClass = "error-text" />
                    </h:column>              
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.administration}" styleClass="title-text"/>
                      </f:facet>
                      <h:commandButton value = "#{messages.update}" action = "#{usersAdministrationController.updateUser(user)}" />
                      <h:commandButton value = "#{messages.delete}" action = "#{usersAdministrationController.deleteUser(user)}" />
                    </h:column>
                  </h:dataTable>
                </h:form>
              </rich:accordionItem>
            </rich:accordion>
          </td>
        </tr>
      </table>
    </ui:define>
    
  </ui:composition>
  
</html>


. ProfilesAdministration.xhtml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.profilesAdministration}</h3>
    </ui:define>
    
    <ui:define name = "content">
      <table border = "0" align = "center">
        <tr>
          <td align = "center">
            <rich:accordion
              id = "profilesAdministrationAccordion"
              rendered = "true"
              switchType = "client"
            >
              <rich:accordionItem
                id = "addNewProfileAccordionItem"
                header = "#{messages.addNewProfile}"
                rendered = "true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "addNewProfileAccordionItemForm" >
                  <table border = "0" align = "center">
                    <tr>
                      <th align = "center">
                        <h:outputText value = "#{messages.profileId}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.profileTitle}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.profileRoles}" styleClass="title-text"/>
                      </th>
                      <th align = "center"/>
                    </tr>
                    <tr>
                      <td align = "center">
                        <h:inputText id = "newProfileId" value = "#{profilesAdministrationController.newProfile.id}" required = "true" size = "2" maxlength = "2" />
                        <h:message for = "newProfileId" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputText id = "newProfileTitle" value = "#{profilesAdministrationController.newProfile.title}" required = "true" size = "50" maxlength = "50" />
                        <h:message for = "newProfileTitle" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <rich:pickList
                          id = "newProfileRoles"
                          value = "#{profilesAdministrationController.newProfile.roles}"
                          var = "role"
                          sourceCaption = "#{messages.availableRoles}"
                          targetCaption = "#{messages.ownedRoles}"
                          sourceListWidth = "450px"
                          targetListWidth = "450px"
                          listHeight = "100px"
                          orderable = "true"
                          rendered = "true"
                          disabled = "false"
                          required = "false"
                          requiredMessage = "#{messages.roleMandatory}"
                          addText  = "→"
                          addAllText  = "⇒"
                          removeText  = "←"
                          removeAllText  = "⇐"
                          upTopText  = "⇑"
                          upText  = "↑"
                          downText  = "↓"
                          downBottomText  = "⇓"
                          collectionType="java.util.ArrayList"
                        >
                          <!--
                            collectionType="java.util.ArrayList" is necessary
                            see http://manuel-palacio.blogspot.com/2011/02/hibernate-jsf-2-and-manytomany.html
                          -->
                          <f:selectItems value="#{rolesSelectItems.values}" />
                          <f:converter binding="#{roleConverter}" />
                          <rich:column
                            rendered = "true"
                          >
                            <f:facet name = "header">
                              <h:outputText
                                value = "#{messages.id}"
                                rendered = "true"
                                styleClass = "title-text"
                              />
                            </f:facet>
                            <h:outputText
                              value = "#{role.id}"
                              rendered = "true"
                              styleClass = "data-text"
                            />
                          </rich:column>
                          <rich:column
                            rendered = "true"
                          >
                            <f:facet name = "header">
                              <h:outputText
                                value = "#{messages.title}"
                                rendered = "true"
                                styleClass = "title-text"
                              />
                            </f:facet>
                            <h:outputText
                              value = "#{role.title}"
                              rendered = "true"
                              styleClass = "data-text"
                            />
                          </rich:column>
                        </rich:pickList>                                    
                      <h:message for = "newProfileRoles" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:commandButton value = "#{messages.add}" action = "#{profilesAdministrationController.addProfile()}" />
                      </td>
                    </tr>
                  </table>
                </h:form>
              </rich:accordionItem>      
              <rich:accordionItem
                id = "manageExistingProfilesAccordionItem"
                header = "#{messages.manageExistingProfiles}"
                rendered="true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "manageExistingProfilesAccordionItemForm" >
                  <h:dataTable
                    value="#{profilesAdministrationController.profileService.findAllProfiles()}"
                    var="profile"
                    styleClass="order-table"
                    headerClass="order-table-header"
                    rowClasses="order-table-odd-row,order-table-even-row"
                  >
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.profileId}" styleClass="title-text"/>
                      </f:facet>
                      <h:outputText value = "#{profile.id}" styleClass="data-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.profileTitle}" styleClass="title-text"/>
                      </f:facet>
                      <h:inputText id = "profileTitle" value = "#{profile.title}" required = "true" size = "50" maxlength = "50" />
                      <h:message for = "profileTitle" styleClass = "error-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.profileRoles}" styleClass="title-text"/>
                      </f:facet>
                      <rich:pickList
                        id = "profileRoles"
                        value = "#{profile.roles}"
                        var = "role"
                        sourceCaption = "#{messages.availableRoles}"
                        targetCaption = "#{messages.ownedRoles}"
                        sourceListWidth = "450px"
                        targetListWidth = "450px"
                        listHeight = "100px"
                        orderable = "true"
                        rendered = "true"
                        disabled = "false"
                        required = "false"
                        requiredMessage = "#{messages.roleMandatory}"
                        addText  = "→"
                        addAllText  = "⇒"
                        removeText  = "←"
                        removeAllText  = "⇐"
                        upTopText  = "⇑"
                        upText  = "↑"
                        downText  = "↓"
                        downBottomText  = "⇓"
                        collectionType="java.util.ArrayList"
                      >
                        <!--
                          collectionType="java.util.ArrayList" is necessary
                          see http://manuel-palacio.blogspot.com/2011/02/hibernate-jsf-2-and-manytomany.html
                        -->
                        <f:selectItems value="#{rolesSelectItems.values}" />
                        <f:converter binding="#{roleConverter}" />
                        <rich:column
                          rendered = "true"
                        >
                          <f:facet name = "header">
                            <h:outputText
                              value = "#{messages.id}"
                              rendered = "true"
                              styleClass = "title-text"
                            />
                          </f:facet>
                          <h:outputText
                            value = "#{role.id}"
                            rendered = "true"
                            styleClass = "data-text"
                          />
                        </rich:column>
                        <rich:column
                          rendered = "true"
                        >
                          <f:facet name = "header">
                            <h:outputText
                              value = "#{messages.title}"
                              rendered = "true"
                              styleClass = "title-text"
                            />
                          </f:facet>
                          <h:outputText
                            value = "#{role.title}"
                            rendered = "true"
                            styleClass = "data-text"
                          />
                        </rich:column>
                      </rich:pickList>                                    
                      <h:message for = "profileRoles" styleClass = "error-text" />
                    </h:column>              
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.administration}" styleClass="title-text"/>
                      </f:facet>
                      <h:commandButton value = "#{messages.update}" action = "#{profilesAdministrationController.updateProfile(profile)}" />
                      <h:commandButton value = "#{messages.delete}" action = "#{profilesAdministrationController.deleteProfile(profile)}" />
                    </h:column>
                  </h:dataTable>
                </h:form>
              </rich:accordionItem>
            </rich:accordion>
          </td>
        </tr>
      </table>
    </ui:define>
    
  </ui:composition>
  
</html>


. RolesAdministration.xhtml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.rolesAdministration}</h3>
    </ui:define>
    
    <ui:define name = "content">
      <table border = "0" align = "center">
        <tr>
          <td align = "center">
            <rich:accordion
              id = "rolesAdministrationAccordion"
              rendered = "true"
              switchType = "client"
            >
              <rich:accordionItem
                id = "addNewRoleAccordionItem"
                header = "#{messages.addNewRole}"
                rendered = "true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "addNewRoleAccordionItemForm" >
                  <table border = "0" align = "center">
                    <tr>
                      <th align = "center">
                        <h:outputText value = "#{messages.roleId}" styleClass="title-text"/>
                      </th>
                      <th align = "center">
                        <h:outputText value = "#{messages.roleTitle}" styleClass="title-text"/>
                      </th>
                      <th align = "center"/>
                    </tr>
                    <tr>
                      <td align = "center">
                        <h:inputText id = "newRoleId" value = "#{rolesAdministrationController.newRole.id}" required = "true" size = "2" maxlength = "2" />
                        <h:message for = "newRoleId" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:inputText id = "newRoleTitle" value = "#{rolesAdministrationController.newRole.title}" required = "true" size = "50" maxlength = "50" />
                        <h:message for = "newRoleTitle" styleClass = "error-text" />
                      </td>
                      <td align = "center">
                        <h:commandButton value = "#{messages.add}" action = "#{rolesAdministrationController.addRole()}" />
                      </td>
                    </tr>
                  </table>
                </h:form>
              </rich:accordionItem>      
              <rich:accordionItem
                id = "manageExistingRolesAccordionItem"
                header = "#{messages.manageExistingRoles}"
                rendered="true"
                disabled = "false"
                switchType = "client"
              >
                <h:form id = "manageExistingRolesAccordionItemForm" >
                  <h:dataTable
                    value="#{rolesAdministrationController.roleService.findAllRoles()}"
                    var="role"
                    styleClass="order-table"
                    headerClass="order-table-header"
                    rowClasses="order-table-odd-row,order-table-even-row"
                  >
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.roleId}" styleClass="title-text"/>
                      </f:facet>
                      <h:outputText value = "#{role.id}" styleClass="data-text" />
                    </h:column>
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.roleTitle}" styleClass="title-text"/>
                      </f:facet>
                      <h:inputText id = "roleTitle" value = "#{role.title}" required = "true" size = "50" maxlength = "50" />
                      <h:message for = "roleTitle" styleClass = "error-text" />
                    </h:column>            
                    <h:column>
                      <f:facet name="header">
                        <h:outputText value = "#{messages.administration}" styleClass="title-text"/>
                      </f:facet>
                      <h:commandButton value = "#{messages.update}" action = "#{rolesAdministrationController.updateRole(role)}" />
                      <h:commandButton value = "#{messages.delete}" action = "#{rolesAdministrationController.deleteRole(role)}" />
                    </h:column>
                  </h:dataTable>
                </h:form>
              </rich:accordionItem>
            </rich:accordion>
          </td>
        </tr>
      </table>
    </ui:define>
    
  </ui:composition>
  
</html>


In the following we describe the different JSF components used in the three pages. To fix ideas we provide explanations only for the UsersAdministration.xhtml page as the other pages expose more or less the sames features.


b - RichFaces Accordion:

The <rich:accordion /> contains a set of panels (<rich:accordionItems />) where one panel is expanded, while the other ones are collapsed. When a collapsed panel's header is clicked, the panel is expanded and the previously expanded one is in turn collapsed (i.e. at any instant only one panel is expanded).
In the UsersAdministration.xhtml we define such a component with two panels: one that holds the adding a new user feature, the second for managing existing users. Read more about <rich:accordion /> ... Online demo.


c - JSF DataTable:


The <h:dataTable /> component is meant for rendering tables. Lines of the table are based on a underlying collection of objects and its columns can be defined using <h:column /> tag and are possibly depending on the underlying object's fields.
In the UsersAdministration.xhtml we use such a component (Lines 157-267) to display the list of all users. We discuss two important attributes of the <h:dataTable /> component:

- value: specifies the underlying collection of objects that will be displayed. Example: in Line 158, we define this value as the List of User objects returned by the ManageUserService's findAllButConnectedUser() method.

- var: specifies an alias for an iterator over the underlying collection of objects. This alias can then be called inside <h:column /> to get for example a specific field of an object in the underlying collection.

In Lines 164-169 we declare the first column to be displayed by the data table. For the <h:column /> tag a header facet (the title of the column) can be defined: in our case using a <h:outputText /> tag in Line 166. Then the content of the column itself is specified, again a <h:outputText /> in Line 168. Note how the content of the latter <h:outputText /> is using the alias: value = "#{user.id}", meaning here that the <h:outputText /> will display the id field of the current User object.

Read more about <h:dataTable /> ...


d - RichFaces Pick Lists:

<rich: pickList /> is a component permitting to handle selecting multiple options from a list of available ones. Indeed it displays two lists: the available options and the selected ones. Using dedicated buttons (copy, copyAll, remove, removeAll) the user can move elements from one list to the other thus selecting or deselecting options.
In the UsersAdministration.xhtml we use pick lists to handle the profiles owned by a user: when adding a new one (Lines 79-141) and when managing an existing one (Lines 195-257). For the latter this means that we will display as many pick lists as users displayed by the data table.
We discuss here some of the interesting attributes and children of the pick list (on the basis of the pick list in Lines 79-141): 

Attributes:

- value: tells JSF where to store the selected options of the pick list. In our case we set this attribute to #{usersAdministrationController.newUser.profiles} since we want to collect the selected profiles for the user to be created in the profiles field of the newUser field of the controller.

- var: defines an alias that will be used to refer to an item in the pick list's both available or selected options lists.

- sourceCaption: defines the title of the available options list.

- targetCaption: defines the title of the selected options list.

- requiredMessage: defines the validation message that is displayed to the user if the required 

attribute is set to true and if the user selects no item in the pick list.

- other attributes: do what their names say they do :) Ranging from styling of the pick list to providing titles of the different buttons (copy, remove, copyAll, removeAll ...)

- collectionType: In some settings (included ours) this attribute is vital ! It says to JSF the type of collection used for the available and selected options lists. If you do not specify this attribute with the correct concrete (no interfaces) type (in our case it is java.list.ArrayList and certainly not java.list.List) you'll have the following exception raised:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

It seems this is an old RichFaces bug which has been fixed in the RichFaces 4.3.0.CR1 release and that curiously reappeared in newer releases !


Children Elements:

- <f:selectItems />: tells JSF how to populate the list of available options for the pick list. The value attribute of this element should refer to a collection (in our case java.util.ArrayList) of javax.faces.model.SelectItem. In the first pick list of UsersAdministration.xhtml (Line 107) we use a dedicated controller (ProfilesSelectItems) to provide the list of all available profiles through its values field.
In fact two such controllers are needed for the super user admin features, the one for providing available profiles used here and another one for providing available roles used in the ProfilesAdministration.xhtml page. We provide their implementations below:

- Under the package mshj.tutorial.controller.si create the two following controllers:

. ProfileSelectItems.java:

package mshj.tutorial.controller.si;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.model.SelectItem;
import mshj.tutorial.model.Profile;
import mshj.tutorial.service.ProfileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class ProfilesSelectItems implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private ProfileService profileService;

  public ProfileService getProfileService() {
    return profileService;
  }
  public void setProfileService(ProfileService profileService) {
    this.profileService = profileService;
  }
  
  private List<SelectItem> values;
  
  public List<SelectItem> getValues() {
    return values;
  }
  public void setValues(List<SelectItem> values) {
    this.values = values;
  }
  
  @PostConstruct
  public void init()
  {
    setValues(new ArrayList<SelectItem>());
    
    for (Profile p : getProfileService().findAll())
    {
      getValues().add(new SelectItem(p,p.getTitle()));
    }
  }
}


. RolesSelectItems.java

package mshj.tutorial.controller.si;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.model.SelectItem;
import mshj.tutorial.model.Role;
import mshj.tutorial.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

@Controller
@Scope("session")
public class RolesSelectItems implements Serializable
{
  private static final long serialVersionUID = 1L;
  
  @Autowired
  private RoleService roleService;

  public RoleService getRoleService() {
    return roleService;
  }
  public void setRoleService(RoleService roleService) {
    this.roleService = roleService;
  }
  
  private List<SelectItem> values;
  
  public List<SelectItem> getValues() {
    return values;
  }
  public void setValues(List<SelectItem> values) {
    this.values = values;
  }
  
  @PostConstruct
  public void init()
  {
    setValues(new ArrayList<SelectItem>());
    
    for (Role r : getRoleService().findAll())
    {
      getValues().add(new SelectItem(r,r.getTitle()));
    }
  }
}

Comments: Note that both controllers provide a list of select items (javax.faces.model.SelectItem instances) which will be used in the <f:selectItems /> bound to rich pick lists in the corresponding pages. A javax.faces.model.SelectItem is simply constructed by providing two arguments: the value and its label.

<f:converter />: tells JSF how to convert labels to their values. This is needed to correctly fill in the target value of a rich pick list. The binding attribute refers to a bean implementing javax.faces.convert.Converter.

We need two converters one for converting profiles and another one for converting roles.

e - JSF Converters:

 JSF comes with a set of built-in converters. An example is LongConverter. It is called whenever, let's say, a <h: inputText /> is bound to a field of type Long (or long) in the controller.
Why ? Simply because at the html level only String inputs are supported. The LongConverter is then needed to convert the String value of the input to a Long (or long) one which matches the controller's expectations. By the way if the conversion is not possible (user typed alphanumerical value) the validation phase (which starts after the conversion phase) will prompt the validation message javax.faces.converter.LongConverter.LONG.

We discuss here Custom JSF Converters. So why they are also needed for our case ?
In fact at the html level select options support only mapping String labels to String values.
So what JSF will get as list of selected options will be only the list of their labels and not the list of their values. The task of the JSF converter is to restore complete objects given their labels.

- Under the package mshj.tutorial.converter create the following two converters:

. ProfileConverter.java:

package mshj.tutorial.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import mshj.tutorial.model.Profile;
import mshj.tutorial.service.ProfileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("session")
public class ProfileConverter implements Converter
{
  @Autowired
  private ProfileService profileService;
  
  public ProfileService getProfileService() {
    return profileService;
  }
  public void setProfileService(ProfileService profileService) {
    this.profileService = profileService;
  }

  @Override
  public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2)
  {
    return(getProfileService().findByTitle(arg2));
  }

  @Override
  public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2)
  {
    return (((Profile)arg2).getTitle());
  }
}


. RoleConverter.java

package mshj.tutorial.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import mshj.tutorial.model.Role;
import mshj.tutorial.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("session")
public class RoleConverter implements Converter
{
  @Autowired
  private RoleService roleService;
  
  public RoleService getRoleService() {
    return roleService;
  }
  public void setRoleService(RoleService roleService) {
    this.roleService = roleService;
  }

  @Override
  public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2)
  {
    return(getRoleService().findByTitle(arg2));
  }

  @Override
  public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2)
  {
    return (((Role)arg2).getTitle());
  }
}

Comments: A Faces Converter implements the javax.faces.convert.Converter interface thus it should implement two methods:
Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) and String getAsString(FacesContext arg0, UIComponent arg1, String arg2) which respectively return the object giving its label and the label given the object.
Since we used the title field (for both profile and role objects) as label (see ProfileSelectItems.java and RoleSelectItems.java above), we rely in implementing the getAsObject methods on services that provide the object given its title.
What should be noticed here is that the services are auto-wired to the converter and that the converter is annotated as a Spring component (@Component).

- Still some configuration is needed to have our pick lists working, JSF should be able to compare objects appearing in our pick lists. For both Role and Profile model classes we have to define boolean equals(Object o) and int hashCode() methods, as follows:

. Edit mshj.tutorial.model.Profile and add the following methods:

  @Override
  public boolean equals(Object o)
  {
    if (o instanceof Profile)
    {
      return(((Profile)o).getId() == getId());
    }
    else
    {
      return(false);
    }
  }
  
  @Override
  public int hashCode()
  {
    return(new Long(getId()).intValue());
  }


. Edit mshj.tutorial.model.Role and add the following methods:

  @Override
  public boolean equals(Object o)
  {
    if (o instanceof Role)
    {
      return(((Role)o).getId() == getId());
    }
    else
    {
      return(false);
    }
  }
  
  @Override
  public int hashCode()
  {
    return(new Long(getId()).intValue());
  }

f - JSF Navigation:

Now that we have four pages in our application it is time to design a menu bar to navigate from one page to another. To have the menu bar available to all pages we only have to add it once to the template Default.xhtml.

- Edit the src/main/webapp/templates/Default.xhtml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>

    <h:head>
    <link type="text/css" rel="stylesheet" href="../resources/css/myCSS.css" />
    </h:head>

  <body>

    <div id = "header_div">
      <div id = "banner_div" align = "center">
        <ui:insert name = "title">
          <h:graphicImage value="../resources/images/logo.png" />
        </ui:insert>
      </div>
      
      <div id = "subtitle_div" align = "center">
        <ui:insert name = "subtitle"/>
      </div>
      
      <div id = "status_div" align = "center">
        <ui:insert name = "status" />
      </div>
          
    </div>
    
    <div id = "menu_div"  align = "center">
      <ui:insert name = "menu">
        <h:form id = "menuForm">
          <table border = "0" align = "center">
            <tr>
              <td align = "center">
                <rich:menuItem action = "PasswordUpdate" >
                  <h:outputText value = "#{messages.updatePassword}" styleClass = "big-title-text-with-bg" />
                </rich:menuItem>
              </td>
              <td align = "center">
                <rich:menuItem action = "UsersAdministration" >
                  <h:outputText value = "#{messages.usersAdministration}" styleClass = "big-title-text-with-bg" />
                </rich:menuItem>
              </td>
              <td align = "center">
                <rich:menuItem action = "ProfilesAdministration" >
                  <h:outputText value = "#{messages.profilesAdministration}" styleClass = "big-title-text-with-bg" />
                </rich:menuItem>
              </td>
              <td align = "center">
                <rich:menuItem action = "RolesAdministration" >
                  <h:outputText value = "#{messages.rolesAdministration}" styleClass = "big-title-text-with-bg" />
                </rich:menuItem>
              </td>
              <td align = "center">
                <rich:menuItem action = "#{loginController.doLogout()}" >
                  <h:graphicImage value="../resources/images/logout.png" width = "64" height = "64" />
                </rich:menuItem>
              </td>
            </tr>
          </table>
          
        </h:form>      
      </ui:insert>
    </div>
    
    <div id = "messages_div"  align = "center">
      <ui:insert name = "messages">
        <table border = "0" align = "center">
          <tr>
            <td align = "center">
              <h:messages globalOnly="true" styleClass="error-text" />
            </td>
          </tr>
        </table>
      </ui:insert>
    </div>
    
    <div id = "content_div" align = "center">
      <ui:insert name = "content"/>
    </div>
    
    <div id = "footer_div"  align = "center">
      <ui:insert name = "footer">
        <h:graphicImage value="../resources/images/footer.png" />
      </ui:insert>
    </div>

  </body>
  
</html>


We added in the menu insertion different entries permitting to navigate to the different application pages. To define a menu entry we use the <rich:menuItem /> tag. Each <rich:menuItem /> has an action attribute which indicates the target of the entry (the page relative path for example) and encapsulates a JSF component which represents the element to be displayed for that menu entry (in our case simple <h:outputText />'s).

Since we do not want the Connection.xhtml to display the menu bar we will have to override its menu insertion by an empty one:

- Edit src/main/webapp/pages/Connection.xhtml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j"
>
  
  <ui:composition template="../templates/Default.xhtml">
  
    <ui:define name = "subtitle">
      <h3>#{messages.login}</h3>
    </ui:define>
    
    <ui:define name = "menu" />
    
    <ui:define name = "content">
      <h:form id = "loginForm">
        <table align = "center" border = "0">
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.userName}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputText 
                id = "j_username"
                value = "#{loginController.j_username}"
                label = "J_username"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">  
              <h:message for = "j_username" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <th align = "center">
              <h:outputText value = "#{messages.password}" styleClass="title-text" />
            </th>
            <td align = "center">
              <h:inputSecret
                id = "j_password"
                value = "#{loginController.j_password}"
                label = "J_password"
                size = "8"
                maxlength = "8"
                required = "true"
              />
            </td>
            <td align = "center">
              <h:message for = "j_password" styleClass="error-text" />
            </td>
          </tr>
          <tr>
            <td align = "center" colspan = "3">
              <h:commandButton
                value = "#{messages.login}"
                action = "#{loginController.doLogin()}"
              >
                <f:phaseListener type="mshj.tutorial.listener.LoginErrorPhaseListener" />
              </h:commandButton>
            </td>
          </tr>
        </table>
      </h:form>
    </ui:define>
    
  </ui:composition>
  
</html>

Line 18 : We overrode the menu insertion (by an empty insertion).

Note that in the Default.xhtml page the <rich:menuItem /> action attributes are simple String values. Do JSF know which page to navigate to given a String value ? Yes if we carefully choose the string returned values of action methods: if an action methods returns the name of page without the extension the user will be redirected to that page after the action method is executed. See here for more details about the so-called implicit navigation. Or if we configure JSF well (explicit navigation), see below:

- Edit src/main/webapp/WEB-INF/faces-config.xml as follows:

<?xml version="1.0"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    
  <!-- Resource Bundles (Messages and JSF Messages) -->
  <application>
    <locale-config>
      <default-locale>en</default-locale>
      <supported-locale>fr_FR</supported-locale>
    </locale-config>  
    <resource-bundle>
      <base-name>messages</base-name>
      <var>messages</var>
    </resource-bundle>
    <message-bundle>jsfMessages</message-bundle>
  </application>    
    
    <!-- JSF and Spring are integrated -->
    <application>
      <el-resolver>
        org.springframework.web.jsf.el.SpringBeanFacesELResolver
      </el-resolver>
    </application>
    
  <navigation-rule>
    <from-view-id>*</from-view-id>
    <navigation-case>
      <from-outcome>PasswordUpdate</from-outcome>
      <to-view-id>/pages/PasswordUpdate.xhtml</to-view-id>
      <redirect />
    </navigation-case>
    <navigation-case>
      <from-outcome>UsersAdministration</from-outcome>
      <to-view-id>/pages/UsersAdministration.xhtml</to-view-id>
      <redirect />
    </navigation-case>
    <navigation-case>
      <from-outcome>ProfilesAdministration</from-outcome>
      <to-view-id>/pages/ProfilesAdministration.xhtml</to-view-id>
      <redirect />
    </navigation-case>
    <navigation-case>
      <from-outcome>RolesAdministration</from-outcome>
      <to-view-id>/pages/RolesAdministration.xhtml</to-view-id>
      <redirect />
    </navigation-case>
  </navigation-rule>
    
</faces-config>


In Lines 29-51 we define a navigation rule (one can define many). It says that from any page (or view) (Line 30) several navigation cases are possible. A navigation case (example Lines 31-35) takes a from-outcome which specifies a String value associated to a to-view-id which specifies a page.

Given this configuration, each time JSF will encounter a String value in an action attribute (not only associated to <rich:menuItem /> but also to <h:commandLink /> or <h:commandButton />), it will check if a corresponding navigation case is concerned and if yes it will trigger a redirection to the corresponding page.

3.5 - Spring Security Access Control:


We discuss now how to control the access to the application pages by checking whether a user owns the required roles.

First let's create some additional users and grant them some privileges:

- Execute the following script:

. MySQL:

connect test/test;

USE `mshj` ;

insert into Roles values(0,'ROLE_DUMMY_0');
insert into Roles values(1,'ROLE_DUMMY_1');
insert into Roles values(2,'ROLE_DUMMY_2');
insert into Roles values(3,'ROLE_DUMMY_3');
insert into Roles values(10,'ROLE_UPDATE_OWN_PASSWORD'); 
insert into Roles values(21,'ROLE_ADMIN_USERS');
insert into Roles values(22,'ROLE_ADMIN_PROFILES');
insert into Roles values(23,'ROLE_ADMIN_ROLES');

insert into Profiles values(1,'DUMMY_PROFILE_1');
insert into Profiles values(2,'DUMMY_PROFILE_2');
insert into Profiles values(3,'DUMMY_PROFILE_3');
insert into Profiles values(10,'PROFILE_USER');
insert into Profiles values(20,'PROFILE_ADMIN');

insert into Users values(1,'user1','user1','User One');
insert into Users values(2,'user2','user2','User Two');
insert into Users values(20, 'admin','admin','Administrator');

insert into Profiles_Roles values(1,0);
insert into Profiles_Roles values(1,1);
insert into Profiles_Roles values(2,0);
insert into Profiles_Roles values(2,2);
insert into Profiles_Roles values(3,0);
insert into Profiles_Roles values(3,3);
insert into Profiles_Roles values(10,10);
insert into Profiles_Roles values(20,10);
insert into Profiles_Roles values(20,21);
insert into Profiles_Roles values(20,22);
insert into Profiles_Roles values(20,23);

insert into Users_Profiles values(1,1);
insert into Users_Profiles values(2,2);
insert into Users_Profiles values(0,10);
insert into Users_Profiles values(20,20);

commit; 


. Oracle:

connect test/test;

insert into Roles values(0,'ROLE_DUMMY_0');
insert into Roles values(1,'ROLE_DUMMY_1');
insert into Roles values(2,'ROLE_DUMMY_2');
insert into Roles values(3,'ROLE_DUMMY_3');
insert into Roles values(10,'ROLE_UPDATE_OWN_PASSWORD'); 
insert into Roles values(21,'ROLE_ADMIN_USERS');
insert into Roles values(22,'ROLE_ADMIN_PROFILES');
insert into Roles values(23,'ROLE_ADMIN_ROLES');

insert into Profiles values(1,'DUMMY_PROFILE_1');
insert into Profiles values(2,'DUMMY_PROFILE_2');
insert into Profiles values(3,'DUMMY_PROFILE_3');
insert into Profiles values(10,'PROFILE_USER');
insert into Profiles values(20,'PROFILE_ADMIN');

insert into Users values(1,'user1','user1','User One');
insert into Users values(2,'user2','user2','User Two');
insert into Users values(20, 'admin','admin','Administrator');

insert into Profiles_Roles values(1,0);
insert into Profiles_Roles values(1,1);
insert into Profiles_Roles values(2,0);
insert into Profiles_Roles values(2,2);
insert into Profiles_Roles values(3,0);
insert into Profiles_Roles values(3,3);
insert into Profiles_Roles values(10,10);
insert into Profiles_Roles values(20,10);
insert into Profiles_Roles values(20,21);
insert into Profiles_Roles values(20,22);
insert into Profiles_Roles values(20,23);

insert into Users_Profiles values(1,1);
insert into Users_Profiles values(2,2);
insert into Users_Profiles values(0,10);
insert into Users_Profiles values(20,20);

commit; 


- Now edit src/main/webapp/WEB-INF/applicationContext-security.xml and between the two comment lines:

    <!-- Begin Handling of Security Permission per Page -->
    <!-- End Handling of Security Permission per Page -->


add the following lines:

    <security:intercept-url pattern="/pages/PasswordUpdate.xhtml" access="ROLE_UPDATE_OWN_PASSWORD" />
    <security:intercept-url pattern="/pages/UsersAdministration.xhtml" access="ROLE_ADMIN_USERS" />
    <security:intercept-url pattern="/pages/ProfilesAdministration.xhtml" access="ROLE_ADMIN_PROFILES" />
    <security:intercept-url pattern="/pages/RolesAdministration.xhtml" access="ROLE_ADMIN_ROLES" />


Every <security:intercept-url /> specifies here that every page (or pattern of pages) defined by the pattern attribute should only be accessed if the connected user has the roles (or the set of roles) defined in the attribute access.

- Finally let's update MyAuthenticationSuccessHandler to take into account the new pages and access policy:

package mshj.tutorial.handler;

import java.io.IOException;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException
    {
    Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
    
    if (roles.contains("ROLE_ADMIN_USERS"))
      {
      response.sendRedirect("UsersAdministration.xhtml");
      return;
      }
      
    if (roles.contains("ROLE_ADMIN_PROFILES"))
      {
      response.sendRedirect("UsersAdministration.xhtml");
      return;
      }
    
    if (roles.contains("ROLE_ADMIN_ROLES"))
      {
      response.sendRedirect("UsersAdministration.xhtml");
      return;
      }
    
    if (roles.contains("ROLE_UPDATE_OWN_PASSWORD"))
      {
      response.sendRedirect("PasswordUpdate.xhtml");
      return;
      }
    
    if (roles.isEmpty() || (roles.contains("ROLE_DUMMY_0")) || (roles.contains("ROLE_DUMMY_1")) || (roles.contains("ROLE_DUMMY_2")))
      {
      response.sendRedirect("../index.jsp");
      return;
      }
    
        roles = null;
    }
}



Now you can compile and deploy the application and test the freshly created new users access rights.
If you are not allowed to access a page and try to display it you'll see the AccessDenied.xhtml page.

Here we reach the end of this tutorial which, i hope, was useful for you. I can point you to some perspective work which you can take as an exercise:

The menu bar displays all the application pages even the ones to which the currently connected user has not access. Modify the code such that only the menu entries that will not lead to a denial of access are displayed. (Tip: use the set of roles owned by the connected user to condition the visibility of the <rich:menuItem />'s).


Below the links to download the final Eclipse projects. After importing the project you'll probably have to fix some properties to match your local settings (such as the JDK and the Apache Tomcat Server).

If you need to quickly rename the project and its packages to your custom values take a look at this tutorial.





Previous | Next