Videoautomat Desktop
Aus Salespoint
Gordon (Diskussion | Beiträge)
(Die Seite wurde neu angelegt: „==Die Nutzerregistrierung== Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur b...“)
Zum nächsten Versionsunterschied →
Version vom 18:54, 5. Apr. 2009
Inhaltsverzeichnis |
Die Nutzerregistrierung
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung zugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.
public class VideoShop extends Shop {
.
.
public void initializeData() {
.
initializeUsers();
}
.
public static void initializeUsers() {
try {
UserManager.getInstance().addUser(
new AutomatUser("Administrator", "", true));
} catch (DuplicateUserException e) {
}
for (int i = 0; i < 10; i++) {
try {
UserManager.getInstance().addUser(
new AutomatUser("Customer" + i, "", false));
} catch (DuplicateUserException e) {
}
}
}
}
An dieser Stelle sind nun die Nutzerverwaltung und einige Testnutzer angelegt. Es sollen sich jedoch auch weitere Kunden registrieren können, die sich in geeigneter Weise anmelden müssen. Hierzu wird der Prozess SaleProcessRegister erzeugt, der nun im Weiteren erläutert wird.
Der Prozess fordert zur Eingabe des Nutzernamens, Passwort und Passwortwiederholung auf. Sind alle Angaben korrekt und der Nutzer existiert noch nicht, so wird er beim Klick auf OK angelegt und der Prozess endet erfolgreich. Tritt ein Fehler auf, so springt man zur Eingabemaske zurück und bekommt eine Fehlermeldung. Durch Abbrechen kann der Prozess zu jeder Zeit beendet werden, ohne dass dabei eine Änderung an den internen Daten erfolgt.
Deklaration des Prozesses
Wie im Technischen Überblick erläutert wird, ist ein SaleProcess eine Folge von Zuständen und Zustandsübergängen. Die Zustände werden dabei durch das Interface Gate, die Zustandsübergänge durch das Interface Transition beschrieben. Während eine Transition atomar behandelt, d.h. nicht unterbrochen wird, kann an einem Gate der Kontrollfluss unterbrochen werden und somit auch Interaktion mit dem Anwender stattfinden. Eine Implementation von Gate, die die Kommunikation über ein FormSheet und/oder MenuSheet ermöglicht, ist die Klasse UIGate.
Im Folgenden wird die Klasse SaleProcessRegister als Spezialisierung von SaleProcess implementiert. Dabei muss die abstrakte Methode getInitialGate() implementiert werden, welche das Start-Gate zurückliefert. Würde der Prozess weitere Gates haben, wären analog dazu weitere Methoden hinzuzufügen. Das ist sinnvoll, um die Definition und Erstellung eines Gates möglichst zentral zu haben. Alternativ wäre es auch möglich dies am Ende einer Transition auszuführen, jedoch würde das u.U. erneut die Übersichtlichkeit mindern. Bei den weiteren Prozessen im Tutorial mit mehreren Gates findet immer die erste Alternative ihre Anwendung. Etwas abweichend davon wird in diesem Prozess der ContentCreator des initialen FormSheets bei einem Zustandsübergang wiederverwendet und nicht jedesmal durch die getInitialGate()-Methode neu erzeugt. Doch mehr dazu im letzten Unterkapitel.
package videoautomat;
public class SaleProcessRegister extends SaleProcess {
private static final long serialVersionUID = -609214820437461076L;
public SaleProcessRegister() {
super("Register Proecess");
}
protected Gate getInitialGate() {
FormSheet register = new FormSheet("Register",
new RegisterContentCreator("Please type in your data!"),
false);
return new UIGate(register, null);
}
}
Ähnlich wie bei der Anzeige des Videobestandes wird ein FormSheet erstellt. Ihm wird im Konstruktor der Titel und ein FormSheetContentCreator übergeben. Das FormSheet selbst würde nur ein leeres Fenster darstellen. Der Inhalt wird durch die Klasse RegisterContentCreator erzeugt. Würde man nun die Anwendung übersetzen, bekäme man einen Fehler oder, falls sie doch zu starten geht, spätestens bei der Betätigung des Register-Buttons. Dies passiert, weil die Klasse RegisterContentCreator noch nicht erstellt wurde. Dies geschieht nun im nächsten Abschnitt.
Die Klasse RegisterContentCreator
Wie schon beim SingleTableFormSheet des Videobestandes, erweitert auch die Klasse RegisterContentCreator die Klasse FormSheetContentCreator. Jedoch kann diesmal ein String übergeben werden, der als Text über den Eingabefeldern erscheint.
package videoautomat.contentcreator;
public class RegisterContentCreator extends FormSheetContentCreator {
public RegisterContentCreator(String message){
.
.
.
}
protected void createFormSheetContent(FormSheet fs) {
.
.
.
}
}
Diese Klasse ist also konfigurierbar und man kann ihre Darstellung von außen verändern. Damit bei einer Falscheingabe der Nutzername nicht erneut eingegeben werden muss und die Werte ausgelesen werden können, bietet diese Klasse zahlreiche get- und set-Methoden. Diese stehen nur für die Daten, die gesetzt oder ausgelesen werden können. Nach außen hin sieht man also nur diese Methoden ohne eigentlich zu wissen, wie die Darstellung der Daten erfolgt. Dies ist ein wichtiger Beitrag die Oberfläche von der Anwendungslogik zu trennen. Die Klassen, die die Daten weiterverarbeiten, sind nur an diesen Methoden interessiert und nicht an ihrer visuellen Darstellung. So wird auch die Änderungsfreundlichkeit erhöht, da so leicht Oberflächenkomponenten ausgetauscht werden können ohne andere Stellen im Programm anzupassen. Die Klasse bietet folgende Methoden, deren Benutzung später gezeigt wird.
package videoautomat.contentcreator;
public class RegisterContentCreator extends FormSheetContentCreator {
private JTextArea errorMessage;
private JLabel message;
private JTextField userName;
private JPasswordField password;
private JPasswordField confirmedPassword;
.
.
.
public String getUserName(){
return userName.getText();
}
public char[] getPassword(){
return password.getPassword();
}
public char[] getConfirmedPassword(){
return confirmedPassword.getPassword();
}
public void setUserName(String userName){
this.userName.setText(userName);
}
public void setErrorMessage(String message){
errorMessage.setText(message);
}
}
Die createComponent Methode erzeugt wie bisher den darzustellenden Inhalt. Diesmal wird aber mit zusätzlichen Swing-Komponenten gearbeitet, um eine eigene Eingabemaske zu erzeugen. Sie verwendet nun die Daten, die mit den set- Methoden gesetzt wurden, um die Daten entsprechend zu visualisieren. Die get-Methoden lesen die Daten wieder aus den Swing-Komponenten aus. Die Verwendung von Swing soll an dieser Stelle nicht weiter erläutert werden, dazu finden sich zahlreiche Tutorials im Internet. Das Interessante liegt hier bei der Zuweisung der Buttons.
package videoautomat.contentcreator;
public class RegisterContentCreator extends FormSheetContentCreator {
.
.
.
public RegisterContentCreator(String message){
errorMessage = new JTextArea();
this.message = new JLabel();
userName = new JTextField();
password = new JPasswordField();
confirmedPassword = new JPasswordField();
this.message.setText(message);
errorMessage.setForeground(Color.RED);
errorMessage.setBackground(this.message.getBackground());
errorMessage.setEditable(false);
}
protected void createFormSheetContent(FormSheet fs) {
JOptionPanel panel = new JOptionPanel(null, 16, 4, JOptionPanel.CENTER, JOptionPanel.CENTER);
userName.setPreferredSize(new Dimension(150, 20));
panel.addOption("User Name", userName);
password.setPreferredSize(new Dimension(150, 20));
panel.addOption("Password", password);
confirmedPassword.setPreferredSize(new Dimension(150, 20));
panel.addOption("Confirm Password", confirmedPassword);
errorMessage.setPreferredSize(new Dimension(200, 50));
panel.addOption("", errorMessage);
fs.setComponent(panel);
fs.removeAllButtons();
fs.addButton("OK", 1, new TransitWithAction(new RegisterOKTransition(this)));
fs.addButton("Cancel", 2, new RollBackAction());
}
}
Wie bereits erwähnt, werden den Buttons wieder Action Klassen übergeben. Auch diesmal sind es wieder Hilfsklassen, die sich wiederverwenden lassen. In diesem Fall lösen diese Aktionen einen Zustandsübergang aus. Die beiden Klassen sind wieder selbst implementiert und sehen wie folgt aus:
.
.
.
public class TransitWithAction implements Action {
private static final long serialVersionUID = 2264074797074037762L;
private Transition transition;
public TransitWithAction(Transition transition){
this.transition = transition;
}
public void doAction(SaleProcess saleProcess, SalesPoint salePoint) throws Throwable {
UIGate currentGate = (UIGate)saleProcess.getCurrentGate();
currentGate.setNextTransition(transition);
}
}
.
.
.
public class RollBackAction implements Action {
private static final long serialVersionUID = -6870469433795382578L;
public void doAction(SaleProcess saleProcess, SalesPoint salePoint) throws Throwable {
UIGate currentGate = (UIGate)saleProcess.getCurrentGate();
currentGate.setNextTransition(GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
}
}
Im beiden Fällen wird auf dem aktuellen Gate die Methode setNextTransition ausgeführt, um den Zustandsübergang mit einer entsprechenden Transition durchzuführen.
Im Falle des OK-Buttons wurde der Action eine spezielle Transition übergeben und dieser zusätzlich die this Referenz des RegisterContentCreator mitgegeben. Warum dies gemacht wurde und wie die Transition aussieht, wird im nächsten Abschitt erklärt.
Einen Zustandsübergang definieren
Der Technischen Überblick beschreibt, dass die internen Bearbeitungsvorgänge in einer Transition durchzuführen sind. Für diesen Fall ist das die RegisterOKTransition, die prüft, ob der Nutzer schon vorhanden ist und die beiden Passwörter übereinstimmen. Die Klasse liegt im Package transition und implementiert das Interface Transition des Frameworks. Das Interface gibt vor, dass die Methode perform(SaleProcess sp, User user) zu implementieren ist.
package videoautomat.transition;
public class RegisterOKTransition implements Transition {
private static final long serialVersionUID = -600112154098222643L;
private RegisterContentCreator creator;
public RegisterOKTransition(RegisterContentCreator creator) {
this.creator = creator;
}
public Gate perform(SaleProcess sp, User user) {
.
.
.
}
}
Wie zu erkennen ist, wird im Konstruktor die Referenz des RegisterContentCreator übergeben. Dies ist die this-Referenz aus dem vorherigen Abschnitt. Da die Transition die Daten aus der Eingabe verarbeitet, braucht sie Zugriff auf dieses Objekt. Durch die Übergabe im Konstruktor kann die perform(SaleProcess sp, User user)-Methode nun auf die Daten zugreifen und diese verarbeiten.
public Gate perform(SaleProcess sp, User user) {
StringBuffer errors = new StringBuffer("");
if ("".equals(creator.getUserName())){
errors.append("You have to choose a user name!n");
}
if (!Arrays.equals(creator.getPassword(), creator.getConfirmedPassword())){
errors.append("The passwords are different!n");
}
if (UserManager.getGlobalUM().getUserNames().contains(creator.getUserName())){
errors.append("User already exists!n");
}
if (errors.length() != 0){
creator.setErrorMessage(errors.toString());
FormSheet register = new FormSheet("Register", creator, false);
return new UIGate(register, null);
}
UserManager.getGlobalUM().addUser(new AutomatUser(creator.getUserName(), creator.getPassword(), false));
return sp.getCommitGate();
}
Zuerst wird geprüft, ob die Daten korrekt eingegeben wurden. Der erste Vergleich prüft, ob überhaupt ein Nutzername eingeben wurde. Fehlt der Nutzername, so wird eine Fehlermeldung vorbereitet. Der zweite Vergleich prüft die beiden Passwörter auf Gleichheit und der dritte, ob der Nutzer schon existiert. Zum Auslesen der Daten werden die jeweiligen Methoden genutzt, die der RegisterContentCreator dafür zur Verfügung stellt.
Der vierte Vergleich prüft nun, ob ein Fehler auftrat und bestimmt dann die Verzweigung. Man hätte natürlich beim ersten Auftreten eines Fehlers darauf reagieren können. So werden aber gleich alle Fehler erkannt und dem Nutzer mitgeteilt. So spart er sich u.U. mehrere Durchläufe, bis alles korrekt eingegeben ist.
Laut Zustandsübergangsdiagramm geht der Prozess zum ersten Gate zurück, wenn ein Fehler auftritt. Dies passiert im Rumpf des vierten Vergleiches. In diesem Fall wird der RegisterContentCreator wiederverwendet und nur der Fehlertext gesetzt. D.h. alle bisherigen Eingaben bleiben bestehen. Alternative hätte man auch eine neue Instanz erstellen können und über die Methoden die Eingaben gesetzt. Je nach Anwendungsfall kann man sich für den einen oder anderen Weg entscheiden.
Nach dem Setzen der Fehlernachricht wird ein neues Gate erstellt und mit dem alten RegisterContentCreator verknüpft. Anschließen wird das Gate zurückgegeben und somit als nächstes angezeigt. Anstatt das Gate hier zu erstellen, könnte man auch eine selbst definierte getGate-Methode im Prozess nutzen, und dann dieses Gate zurückgeben. Die Methode könnte auch einige Paramter haben, um, wie in diesem Beispiel, Daten an das FormSheet übermitteln zu können. Dieses Verfahren wird an späterer Stelle im Tutorial gezeigt.
Trat kein Fehler auf, so wird der Nutzer, unter Verwendung der eingegebenen und geprüften Daten, erstellt und der Prozess wechselt zum CommitGate, womit er erfolgreich beendet wäre.