HowTos Application Architecture
Aus Salespoint
Inhaltsverzeichnis |
Shop
Implement a Shop
Description: The Shop is the central class of a SalesPoint application. You can run Processes on the Shop itself, you can add SalesPoints where you can run Processes too. The Shop provides a central MenuSheet, where important actions could be invoked (database manager, quit etc.). You may wish to change this MenuSheet in order to open SalesPoints or to offer additional user interaction. All sorts of central data should be stored here, like Catalogs, Stocks, Currencys, MoneyBags, UserManagers and other attributes that are of global use. It is important to make a singleton instance of Shop and declare it with the static Shop.setTheShop() method. If you need the Shop instance, call Shop.getInstance(). Don´t try to use the instance, you can get in your Shops methods, because very unlovely runtime errors may be the result.
ToDo:
- Create a subclass of Shop.
- Add constructor to create singleton instance of Shop. You can initialize some data in the constructor
- For invoking the Shop, create a simple class with the public static void main (String[] noArgs) method.
- Therein create an instance of the Shop and initialize its attribute of the singleton Shop instance by the setTheShop(Shop TutorialShop) method. Furthermore call the start() method to start the Shop.
- The main class is also used to add a SalesPoint (or even more) to the Shop here.
Example Source Code:
Shop class:
import org.salespointframework.sale.AutoTimer;
import org.salespointframework.sale.CalendarTime;
import org.salespointframework.sale.Shop;
import org.salespointframework.sale.events.TimerEvent;
import org.salespointframework.sale.events.TimerListener;
1
public class ArchitectureShop extends Shop
{
2
public ArchitectureShop()
{
super();
CalendarTime calendarTime = new CalendarTime();
calendarTime.setTimeToCount(CalendarTime.SECOND);
AutoTimer autoTimer = new AutoTimer(calendarTime, (long) 992);
autoTimer.addTimerListener(new TimerListener()
{
public void onGoneAhead(TimerEvent timerEvent)
{
System.out.println(timerEvent.getTime());
}
public void onTimeSet(TimerEvent timerEvent)
{
}
public void onIntervalSet(TimerEvent timerEvent)
{
}
});
autoTimer.start();
}
}
Main class:
// necessary imports
import org.salespointframework.sale.Shop;
import application_architecture.ArchitectureSalesPoint;
import application_architecture.ArchitectureShop;
import display.DisplaySalesPoint;
3
public class Tutorial
{
public static void main(String[] args)
{
4
ArchitectureShop myTutorialShop = new ArchitectureShop();
Shop.setTheShop(myTutorialShop);
myTutorialShop.start();
5
myTutorialShop.addSalesPoint(new ArchitectureSalesPoint("ArchitectureSalesPoint"));
myTutorialShop.addSalesPoint(new DisplaySalesPoint("DisplaySalesPoint"));
}
}
React on adding and removing SalesPoints
Description: This is required whenever reaction on adding or removing SalesPoints is needed, such as manipulating menus or notifying SalesPoints already opened.
ToDo:
- Open the designated Shop class.
- Override the protected void onSalesPointAdded() and/or protected void onSalesPointRemoved() methods to react on the adding or removing. Be sure to synchronize your method with the one of the original Shop by first calling it's super.onSalesPoint....
Example Source Code:
1
public class ArchitectureShop extends Shop
{
.
.
.
2
protected void onSalesPointAdded(SalesPoint sp) {
super.onSalesPointAdded(sp);
. . . react on adding a SalesPoint . . .
}
protected void onSalesPointRemoved(SalesPoint sp) {
super.onSalesPointRemoved(sp);
. . . react on removing a SalesPoint . . .
}
}
Draw your own Background in Shop Window
Description:
By default a Shop draws the standard background, the Salespoint logo and a version string into the background of the Shop frame. The WindowRepaintListener class provides a way to hook into this draw process. This hook is called after the logo was drawn. You can draw anything you want into the given graphics context, even draw over background and logo which was already painted. Notice that the version string is still painted aftwards to brand the application. you can set the WindowRepaintListener at any point of time, but note that the update will not be visible before the next repaint event.
Tasks:
- define a constructor for your Shop class
- set the WindowRepaintListener
- draw a white rectangle
Example Code:
public class MyShop extends Shop {
1
public MyShop() {
2
setWindowRepaintListener(new WindowRepaintListener() {
public void paintWindow(Graphics2D g, JComponent c) {
3
g.setColor(Color.white);
g.fillRect(10, 10, 100, 100);
}
});
}
}
Respond to failed close Requests
Description:
In some cases it is not possible to close the shop, because the Shop respectively the SalesPoint has running processes, which could not be stopped or the user refuses to close a SalesPoint. To give the user feedback about the fact, that nothing happens, a Hint is displayed in the upper right corner of the Shop frame. You can override or extend this behaviour by overriding the showShutdownPreventedHint method.
Tasks:
- override showShutdownPreventedHint method in Shop
Example Code:
public class MyShop extends Shop {
1
protected void showShutdownPreventedHint(MultiWindow mw) {
//give some feedback to user
}
}
Use the Config file
Description:
The Shop instance is associated with a so called PreferenceStorage which saves key-value pairs to some persistent location (File, Registry, ...). The default implementation is the XMLPreferenceStorage which writes a XML file named "salespoint.config" to the current working directory. It saves for example size and position of the Shop frame and database connections. Your can use it to save your own information and even implement the PreferenceStorage interface to make your own one.
Tasks:
- write a information.
- read it.
Example Code:
1
Shop.getInstance().getPreferenceStorage().save("myapp.testkey", "Hello PreferenceStorage");
2
String value = Shop.getInstance().getPreferenceStorage().loadString("myapp.testkey");
Initialize your Data
Description:
In most cases you have some default data for your catalogs, stocks and for the UserManager. In order to reset your data source to this data, you can use the button "initalize data" in the Databasemanager. A click on this button will clear the datasource and calls the initializeData method of your shop instance. Just override this method to fill the data source with your own data.
Tasks:
- override initializeData method
- fill data source with some data
Example Code:
public class MyShop extends Shop {
1
public void initializeData() {
2
UserManager.getInstance().addUser(new MyUser("Butch", "secret"));
getSomeCatalog().add(new MyCatalogItem("TV", 99900));
getSomeCountingStock().add("TV", 100, null);
}
}
SalesPoint
Implement a SalesPoint
Description: The SalesPoint is a part of the Shop. A Shop usually consists of one SalesPoint at least. SaleProcesses take place at a Point of Sale in most cases. They could be startet easily with the public void runProcess(SaleProcess p) method. The SalesPoint appeares in a seperate Frame, a JDisplayFrame. In the JTabbedPane of the Shop appeares the SalesPoints StatusDisplay. It could be used for different purposes. The FormSheets could be both added with MenuSheets, which could be used to start Processes or to open other SalesPoints or to close them.
ToDo:
- Create a class that extends SalesPoint
- Add the class constructor. You can also set the SalesPoints frame size here for example.
- Implement protected FormSheet getDefaultFormSheet() to define the SalesPoints FormSheet. Change the FormSheet content by adding a FormSheetContentCreator. This can be done by creating a subclass of FormSheetContentCreator.
- Implement protected MenuSheet getDefaultMenuSheet() to define the SalesPoints MenuSheet.
Example Source Code:
SalesPoint class:
// necessary imports
import java.awt.Rectangle;
import org.salespointframework.sale.FormSheet;
import org.salespointframework.sale.MenuSheet;
import org.salespointframework.sale.SalesPoint;
1
public class ArchitectureSalesPoint extends SalesPoint
{
2
public ArchitectureSalesPoint(String sPointName)
{
super(sPointName);
setSalesPointFrameBounds(new Rectangle(0, 0, 640, 480));
}
3
protected FormSheet getDefaultFormSheet()
{
FormSheet fs = new FormSheet("TutorialFormSheet",
new ArchitectureSPointFormSheetCC(), false);
return fs;
}
4
protected MenuSheet getDefaultMenuSheet()
{
return null;
}
}
FormSheetContentCreator class:
public class ArchitectureSPointFormSheetCC extends FormSheetContentCreator
{
protected void createFormSheetContent(FormSheet fs)
{
fs.removeAllButtons();
// add ui code here
}
}
Change quit behaviour
Description: A SalesPoint quit´s with a MessageForm, asking "Are you sure, you want to close this SalesPoint? YES/NO". If you want to change this behavior, overwrite the protected boolean onCanQuit() method.
ToDo:
- Open the designated SalesPoint class.
- Implement the protected boolean onCanQuit() method to change the quit behaviour. The return of true will close the SalesPoint against the return of false will keep the SalesPoint opened.
Example Source Code:
1
public class ArchitectureSalesPoint extends SalesPoint
{
2
protected boolean onCanQuit()
{
return true;
}
}
Processes
Implement a SalesProcess
Description: SalesProcesses are the implementation of a deterministic finite automat. It shall be deemed to be a vectored graph with nodes called Gates and edges called Transitions. Make sure that user interaction only takes place in Gates while Transitions should be rather short without any user interaction. The reason is that a Process could only be suspended at a Gate and so the Shop is only able to persistify the Process at a Gate. Often the display of a Gate is to be influenced by the user interaction before. In this case, Gates should be prepared with a Transition leading to this Gate. SaleProcesses could be startet in the Shop itself, but in most cases Processes take place in a SalesPointFrame and are startet from the SalesPoint.
ToDo:
- Create a subclass of SalesProcess.
- Add the constructor and therein call the super method to inherit from the Superclass, the SaleProcess. You can add any attribute to the constructor, most common is the Process' name.
- Every Process needs a starting Gate, the getInitialGate(). Add this method to the SaleProcess.
Define the UIGate itself and add the necessary FormSheet etc. (see related topics). A basic implementation of SaleProcess is now done. - To start the SaleProcess you can add a button to your SalesPoint's FormSheet and therewith lead to an action that runs the Process on your SalesPoint. Therefore an action was defined that runs any SalesProcess on a SalesPoint by passing the relevant process and a possible necessary DataBasket. (This structure was for example used in the "Videoautomat" tutorial).
Example Source Code:
SaleProcess class:
1
public class ArchitectureProcess extends SaleProcess
{
2
public ArchitectureProcess(String sName) {
super(sName);
// your (initializing) code here
}
3
protected Gate getInitialGate()
{
UIGate uig_initial = new UIGate(null, null);
// set FormSheet and other necessary things
return uig_initial;
}
}
SalesPoint class and relevant:
4
// SalesPoint class:
// set the FormSheet and call FormSheetContentCreator
protected FormSheet getDefaultFormSheet()
{
FormSheet fs = new FormSheet("TutorialFormSheet",
new ArchitectureSPointFormSheetCC(), false);
return fs;
}
// FormSheetContentCreator class:
public class ArchitectureSPointFormSheetCC extends FormSheetContentCreator
{
protected void createFormSheetContent(FormSheet fs)
{
fs.removeAllButtons();
// add a button that calls the TutorialRunProcessAction for running the TutorialProcess
fs.addButton(
"Start Tutorial Process",
2,
new ArchitectureRunProcessAction(new ArchitectureProcess("TutorialProcess"), null));
}
}
// TutorialRunProcessAction:
public class ArchitectureRunProcessAction implements Action
{
private SaleProcess process;
private DataBasket basket;
public ArchitectureRunProcessAction(SaleProcess process, DataBasket basket)
{
this.process = process;
this.basket = basket;
}
public void doAction(SaleProcess p, SalesPoint spoint) throws Throwable
{
if(basket != null)
spoint.runProcess(process, basket);
else
spoint.runProcess(process);
}
}
Change quit behaviour
Description: If a Process should have a certain task before finishing, you have to change its quit behaviour. Therefore overwrite the method onFinished().
ToDo:
- Choose the SaleProcess you want to modify.
- implement the method protected void onFinished().
- add the code that should be run on quiting the process to the newly created method.
Example Source Code:
1
// SaleProcess class
public class ArchitectureProcess extends SaleProcess
{
2
// method to be overwritten
protected void onFinished()
{
3
// your code here
}
}
Change start behaviour
Description: If a Process should have a certain task after being resumed or started, you have to change its start behaviour. Therefore overwrite the method onResumeOrStart().
ToDo:
Choose the SaleProcess you want to modify. Implement the method protected void onResumeOrStart(boolean fIsResume). Add the code that should be run on starting/resuming the process to the newly created method.
Example Source Code:
1
// SaleProcess class
public class ArchitectureProcess extends SaleProcess
{
2
// method to be overwritten
protected void onStartOrFinish(boolean fIsResume)
{
3
// your code here
}
}
Define a UIGate
Description: Gates are a part of SalesProcesses. In contrast to normal Gates, UIGates are made for user interaction and can display FormSheets and MenuSheets. If you need to display something, use UIGates. Use JOptionPanes or JDialogs only to display short user interaction and information, because after serialisation they won´t be restored. If you want to incorporate a UIGate not as StartGate, it may be mandatory to affect the view of the UIGate during Process. In this case, you have to use a Transition.
ToDo:
- Select the SaleProcess where you want to define your UIGate.
- Implement a method that returns a Gate as the class UIGate implements Gate.
- Create an instance of UIGate. Set the attributes to null as they will be set later.
- Define a FormSheet for the UIGate and add a FormSheetContentCreator that modifies the FormSheet.Use any FormSheet-Class and modify the FormSheetContentCreator and possible data accordingly.Available FormSheet-Classes are: LogOnForm, LogTableForm, MessageForm, SingleTableFormSheet, ListViewFormSheet, TextInputForm, DataExchangeFormSheet, UserTableFormSheet.
- Assign the recently instantiated FormSheet to the UIGate.
- Return the instance of UIGate.
Example Source Code:
1
public class ArchitectureProcess extends SaleProcess
{
2
protected Gate getTutorialInteractionGate()
{
3
UIGate uig_tutorial = new UIGate(null, null);
4
FormSheet fs_tutorial = new FormSheet("Tutorial", new ArchitectureSProcessFormSheetCC(), false);
5
uig_tutorial.setFormSheet(fs_tutorial);
6
return uig_tutorial;
}
}
Define a Gate
Description: Gates are a part of SalesProcesses. If you want to implement user interaction, use UIGates. Normal Gates are preferably used to decide at which UIGate the Process continues. They are also very suitable for the implementation of background Processes. If you need data to prepared before the Gate goes into action, implement a Transition to the Gate.
ToDo:
- Select the SaleProcess where you want to define your Gate.
- Implement a method that returns a Gate.
- Instantiate the Gate and add the needed public Transition getNextTransition(SaleProcess process, User user) throws InterruptedException method.
- Add your code that selects the next GateChangeTransition.
- Return the Gate.
Example Source Code:
1
public class ArchitectureProcess extends SaleProcess
{
2
protected Gate getTutorialNoInteractionGate()
{
3
Gate decisionGate = new Gate()
{
public Transition getNextTransition(SaleProcess process, User user)
throws InterruptedException
{
4
if(myCatalog.size(myDataBasket) == 0)
{
return GateChangeTransition.CHANGE_TO_QUIT_GATE;
}
else
{
return new GateChangeTransition(targetGate);
}
}
};
5
return decisionGate;
}
}
Define a Transition
Description: Transitions main task is to prepare Gates while the application is running. The idea is to manipulate the view of a Gate in order to react to user interaction or collected data of the past. It is possible to prepare normal Gates and UIGates as well.
ToDo:
- Create a class that implements Transition.
- In this class add the public Gate perform(SaleProcess process, User user) method. It's the place where the actions between two gates will be defined.
- Add your code to this method and return the Gate that will be the next target. Therefore you should cast the SaleProcess in the perform method to your Process class that currently calls the Transition. Then simply return the needed Gate from there.
Example Source Code:
Transition class:
1
public class ArchitectureTransition implements Transition
{
2
public Gate perform(SaleProcess process, User user)
{
3
ArchitectureProcess processTutorial = (ArchitectureProcess) process;
// your code here
return processTutorial.getReturnGate();
}
}
SalesProcess class:
public Gate getReturnGate()
{
UIGate uig_return = new UIGate(null, null);
// some other code
return uig_return;
}
Define Transition that just changes to a Gate
Description: During a running Process it may be volitional to change just to a Gate. This could be a not user interactive Gate, which has no Transition leading to it. It could be also an already prepared UIGate, to which you want to change after a "Back"-Button, when it should be not refreshed by the Transition. This might be the case, when the user interaction should be not erased.
ToDo:
- Use constructor of class GateChangeTransition: Transition t = new GateChangeTransition(Gate gTarget);.
Example Source Code:
// ...
Gate tmpGate0 = new Gate()
{
public Transition getNextTransition(SaleProcess process, User user)
throws InterruptedException
{
return new GateChangeTransition(targetGate);
}
};
// ...
Define Transition that changes to a special Gate
Description: There are six predefined Process Gates. All of them except the ErrorGate have a predefined Transition, leading to it.
- If you need the singelton instance of a special Process Gate, use the get-method of class SaleProcess:
getCommitGate()
getErrorGate(int nErrorNesting)
getLogGate()
getRollbackGate()
getStopGate()
getQuitGate() - If you need a Transition, leading to a special Process Gate, use the static attributes of class GateChangeTransition:
GateChangeTransition.CHANGE_TO_COMMIT_GATE
GateChangeTransition.CHANGE_TO_LOG_GATE
GateChangeTransition.CHANGE_TO_ROLLBACK_GATE
GateChangeTransition.CHANGE_TO_STOP_GATE
GateChangeTransition.CHANGE_TO_QUIT_GATE
Example Source Code:
// ...
Gate tmpGate1 = new Gate()
{
public Transition getNextTransition(SaleProcess process, User user)
throws InterruptedException
{
return GateChangeTransition.CHANGE_TO_QUIT_GATE;
}
};
// ...
Time Management
Incorporate a Timer
Description:
A Timer is able to manage the current time in your application. A Timer needs a Time Object to be referenced on.
If you want to react to different TimerEvents, you will have to incorporate a TimerListener.
Time management in SalesPoint Framework is abutted on several Java classes:
java.util.Calendar
java.util.GregorianCalendar
java.util.Timer
java.util.TimerTask
java.util.TimeZone
java.sql.TimeStamp
ToDo:
- Create a new instance of Time. In this case CalendarTime is used. Set the time interval.
- Create a new instance of Timer. In this case AutoTimer is used.
- If you need, add a TimerListener and add the methods the class must inherit from the base class. Add your code to react to the events.
- Finally start the timer.
Example Source Code:
1
// initialize with current system time and increase time by second
CalendarTime calendarTime = new CalendarTime();
calendarTime.setTimeToCount(CalendarTime.SECOND);
2
// initialize Timer with CalendarTime and 992ms delay from step to step
(1000ms are too much, because the autotimer is delayed additionally)
AutoTimer autoTimer = new AutoTimer(calendarTime, (long) 992);
3
// if it becomes more complicate it will be clearer to realize
a subclass of TimerAdapter or to implement TimerListener
autoTimer.addTimerListener(new TimerListener()
{
public void onGoneAhead(TimerEvent timerEvent)
{
System.out.println(timerEvent.getTime());
}
public void onTimeSet(TimerEvent timerEvent)
{
}
public void onIntervalSet(TimerEvent timerEvent)
{
}
});
4
// start the timer
autoTimer.start();
Select a time type
Description: A Time Object is used by the Timer. It gives the Timer it´s certain shape. The Time Object defines which time field will be increased by goAhead method of the Timer. Choose the following timer types:
- Date: "01.01.00" a simple date of the format: "dd.mm.yy".
- Step: "26" a Long value is used to represent the time.
- CalendarTime: "Sat Jul 20 15:38:53 CEST 2002" a time which is represented as a Gregorian Calendar.
ToDo:
- Create a new instance of Time. In this case CalendarTime is used. Set the time interval.
Example Source Code:
1
// initialize with current system time and increase time by second
CalendarTime calendarTime = new CalendarTime();
calendarTime.setTimeToCount(CalendarTime.SECOND);
Select a timer type
Description: A Timer is able to manage the current time in your application. It is referenced to a Time which gives the Timer it´s shape. Choose of following Timer types:
- StepTimer: very simple implementation, is increased manually by goAhead() method.
- AutoTimer: special step timer which inceases automatically with a certain time delay.
ToDo:
- Create a new instance of Timer. In this case AutoTimer is used.
Example Source Code:
1
// initialize Timer with CalendarTime and 992ms delay from step to step
(1000ms are too much, because the autotimer is delayed additionally)
AutoTimer autoTimer = new AutoTimer(calendarTime, (long) 992);
Common
Set default Databasket for SalesPoints
Description: This is required if processes executed on a SalesPoint are to run in a specific transactional context. The specified DataBasket will be attached to every process running on the SalesPoint and will determine its transactional context. By default, no DataBasket is attached to a SalesPoint so that processes run outside any transactional context.
ToDo: This can be achieved in several ways. In any case, you need to attach the DataBasket to the SalesPoint:
- a. Open the designated Shop class.
b. Override the protected void onSalesPointAdded() method according to your needs. - a. Open the designated SalesPoint class.
b. Attach the DataBasket in it's constructor.
Example Source Code:
1 a
public class ArchitectureShop extends Shop
{
.
.
.
1 b
protected void onSalesPointAdded(SalesPoint sp) {
super.onSalesPointAdded(sp);
sp.attach(new DataBasketImpl());
}
}
2 a
public class ArchitectureSalesPoint extends SalesPoint
{
.
.
.
2 b
public ArchitectureSalesPoint(String sPointName, DataBasket db) {
super(sPointName);
attach(db);
}
}