This framework will be very easy understanding if you are familiar with Java Reflection.
It just do four things:
- Construct argument values of the method you written, by getting them from ObjectSource.
- Invoke the method on the target object.
- Save the method result into ObjectSource.
- Process the URL dispatching.
ObjectSource is the core interface of this framework, for getting and setting objects. The most work you are doing is to tell this framework how to get object from ObjectSource, and save object into ObjectSource.
Executable is another core interface. It can be executed only with an ObjectSource.There are two implementations of this interface, one is Invoke, and another is Action. They both are advanced encapsulation of java.lang.reflect.Method object, an Action can have one or more Invokes, and an Invoke are corresponding to a java.lang.reflect.Method object.
Resolver is an important concept, it is the target of Invoke's method. But there is no interface defined for it, because it is writing by you. You may call it "Business object".
Converter can convert any object stored in ObjectSource to object that match Invoke's method argument type.
TargetHandler handles the target URL and controls the view type of an Action。
The detail structure of SoybeanMilk configuration file is listing bellow:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE soybean-milk PUBLIC "-//SoybeanMilk//DTD soybeanMilk web//EN" "http://soybeanmilk.googlecode.com/files/soybeanMilk-web-1.0.dtd">
-
<soybean-milk>
Root element
-
<global-config>
Global configuration block
-
<generic-converter>
Generic Converter
- <converter> Support Converter
- <interceptor> Interceptor
-
<type-target-handler>
Target handler by type
- <target-handler> Target handler
-
<generic-converter>
Generic Converter
-
<includes>
Included modules
- <file> Module file
-
<resolvers>
Resolver configuration block
- <resolver> Resolver
- <executables> Executable configuration block
-
<global-config>
Global configuration block
Global configuration block. It is just defined as a parent tag with no attribute.
<generic-converter> (Optional)
Generic Converter configuration. Type conversion is a nest function of this framework, it works when you want to get an object which has different type with the object stored in ObjectSource (See GenericConverter interface).
Especially in web environment, the request values are all Strings, but the argument of Invoke method may be any type.
It contains the following attributes:
-
class (Optional)
Customized generic converter class with which you can completely replace the nest generic converter. This customized generic converter must be an implementaion of GenericConverter interface.
The nest generic converter WebGenericConverter will be used if you didn't set this attribute.
The nested generic converter implementation WebGenericConverter has add most common support converters (see DefaultGenericConverter class), and it can also convert "java.util.Map<String, Object>" to JavaBean object, JavaBean array and JavaBean collection(List, Set).
Support Converter object.
You can add new support converters or replace old converters to enhance the generic converter.
It contains the following attributes:
-
src (Required)
The source type it can convert from. Such as "java.lang.String" (String class), "[Ljava.lang.String;" (String[] class). -
target (Required)
The target type it can convert to. Such as "int" (int class), "[I" (int[] class) -
class (Required)
The class of this converter.
The framework defines some shortname for src and target attribute value, The rule is listing below:
- "request" means javax.servlet.http.HttpServletRequest class
- "session" means javax.servlet.http.HttpSession class
- "application" means javax.servlet.ServletContext class
- "response" means javax.servlet.http.HttpServletResponse class
-
Primitive type, their wrapper type, and String, BigDecimal, BigInteger, java.util.Date type, also with their array type,
are the same with their Java syntax, and do not need its package prefix, such as :
"int", "Integer", "int[]", "Integer[]", "String", "String[]", "Date[]", "BigDecimal[]". -
The following types
java.sql.Date
java.sql.Time
java.sql.Timestamp
are familiar with above, but package prefix is required, such as :
"java.sql.Time", "java.sql.Time[]".
The following is a support converter example, the nested will be replaced:
<generic-converter> <converter src="String" target="Date" class="your.converter.DateConverter" /> <converter src="request" target="java.io.File" class="your.converter.FileConverter" /> </generic-converter>
Normally, you only need to replace or define support converters for atomic types mentioned above, the WebGenericConverter will automatically decompose JavaBean convertion or array convertion to these atomic type convertions.
Interceptor configuration. You can add "before", "after", "exception" interceptor for Executor using this configuration.
It contains the following attributes:
-
before (Optional)
Set the "before" interceptor's global Executable name. Executor will execute it first before the current Executable. -
after (Optional)
Set the "after" interceptor's global Executable name. Executor will execute it after correctly executing the current Executable. -
exception (Optional)
Set the "exception" interceptor's global Executable name. Executor will execute it if exception thrown when executing the current Executable. Usually, you only need to handle InvocationExecuteException and ConvertExecuteException in your exception interceptor. -
execution-key (Required)
Saving keyword of the execution context object Execution. With this object, you can get the execution context information in your interceptor.
The following is an "exception" interceptor example, the framework will execute Action "/handleException.do" when exception occuring:
<global-config> <interceptor exception="/handleException.do" execution-key="session.execution" /> </global-config> <executables> <action name="/handleException.do"> <invoke method="handleException" resolver="exceptionResolver"> <arg>session.execution</arg> </invoke> <target url="/error.jsp" /> </action> ... ... </executables>
It's corresponding Java method must be:
public void handleException(Execution execution)
{
...
}
<type-target-handler> (Optional)
Type target handler configuration. It makes you can define your own target handler for handling action's target by its type(See <target> tag's type attribute).
The framework has supplied a default implementation class DefaultTypeTargetHandler, which can handle "forward" type and "redirect" type target. And you can add more target handler through <target-handler> tag below, or set the class attribute of this tag for completely replacing the default type target handler.
It contains the following attributes:
-
class (Optional)
Set your own type target handler class name, it must implement interface TypeTargetHandler. The default DefaultTypeTargetHandler will be used if you do not set it.
Target handler configuration. It is used for set spefific target handler.
It contains the following attributes:
-
handle-type(Required)
Set the target type this handler can handle, such as "json", "pdf" or "json, pdf, forward". -
class(Required)
Set the class name of this target handler, it must implement the TargetHandler interface. You can also create extension class from AbstractTargetHandler, it supplies very useful methods.
The following is an example of target handler configuration, the nest "redirect" target handler will be replaced:
<type-target-handler> <target-handler handle-type="redirect" class="your.th.RedirectTargetHander" /> <target-handler handle-type="json" class="your.th.JsonTargetHander" /> </type-target-handler>
Included modules configuration. It is just defined as a parent tag with no attribute.
The file of module configuration. It can be a resource file in classpath, or file in "/WEB-INF" folder of your application.
Note that only <resolvers> and <executables> tags are valid in module configuration files.
It's an example below:
<includes> <file>your/config/module.1.xml</file> <file>your/config/module.2.xml</file> <file>/WEB-INF/config/module.3.xml</file> <file>/WEB-INF/config/module.4.xml</file> <file>/home/yourapp/config/module.5.xml</file> </includes>
Resolver configuration block. It is just defined as a parent tag with no attribute.
Resolver configuration. You may add many resolvers here and using later.
It contains the following attributes:
-
id (Required)
The id of Resolver, and will be used when you defining Executables. -
class (Required)
The class of Resolver. It single instance will be created by the parser of this framework, and using by all referenced Executables.
This is an example below:
<resolvers> <resolver id="yourResolver0" class="your.resolver.MyResolver0" /> <resolver id="yourResolver1" class="your.resolver.MyResolver0" /> <resolver id="yourResolver2" class="your.resolver.MyResolver1" /> </resolvers>
Executable configuration block.
It contains the following attributes:
-
prefix (Optional)
Set the prefix of all the Executables in this configuration file scope, it will be useful in module configuration files.
Invoke configuration. It can be directly added under the <executables> tag (as global), or added under the <action> tag (as local).
It has tow configuration mode:
It contains the following attributes:
-
name (Optional)
Your must set this attribute if this Invoke is global, and it must be unique together with Action's name attribute. It is optional if this Invoke is local. -
method (Required)
Set the method name of this Invoke, it must be defined in resolver-class or resolver class. The configuration parser will use this attribute to find the Method object. If you set the resolver-class attribute, it's class will be first used for finding, or the resolver attribute's class will be used. -
resolver-class (Alternatively)
Set the Resolver class name of this Invoke. If the Invoke method is static, you can only set this attribute, because static method can be exeucted without instance. -
resolver (Alternatively)
Set the Resolver id of this Invoke defined in <resolvers> tag. All the ID defined in module files can be used here. If the Invoke method is not static, this attribute is required. -
result-key (Optional)
The saving keyword of this Invoke method result. The result will always be saved if you set this attribute, and "null" is saved if the method has no return value. Contrarily, nothing will be saved.
Iegal result keywords are listing below:-
yourResultKey
The result will be saved in "request" scope with keyword "yourResultKey". -
request.yourResultKey
The same as above. -
session.yourResultKey
The result will be saved in "session" scope with keyword "yourResultKey". -
application.yourResultKey
The result will be saved in "application" scope with keyword "yourResultKey".
-
yourResultKey
-
breaker (Optional)
The breaker of this Invoke method, for controlling the executing of this Invoke method. It has two means :-
"true" or "false"
If "true", this Invoke method will not be executed, and will be executed if "false". -
Any other string
This string will be treated as a key in the current WebObjectSource, and if the value of this key is Boolean.TRUE object, this Invoke method will not be executed, if it is null or any other object, this Invoke method will be executed.
-
"true" or "false"
It makes your configuration for Invoke just like writing Java codes. The syntax is listed below:
<invoke name="yourInvokeName"> resultKey = resolver.method(arg, arg, ...) </invoke>
-
resultKey
The same meaning to the above attribute result-key of <invoke>. -
resolver
The same meaning to the above attribute resolver or resolver-class of <invoke>. The framework can recognise which attribute it is. -
method
The same meaning to the above attribute method of <invoke>. -
arg
The same meaning to the below tag <arg>.
These are some configuration example using Java mode:
<action name="/multiply.do"> <invoke> request.multiplyResult = calculator.multiply( param.a, 100) </invoke> <invoke> your.util.Utils.printToConsole(request.multiplyResult); </invoke> </action>
<invoke name="yourGlobalInvokeName"> request.yourResultKey = yourResolver.yourMethod( param.arg1, true, 'a', '\u00EF', "hello\t hello", 100 ) </invoke>
Note that in Java mode, the name and breaker still have to be written in <invoke> tag's attribute.
The argument configuration of an Invoke method. It may be needed only if you are using XML mode to configure your <invoke>.
It contains no attributes, but only content. There are two types of <arg> content, one is keyword, and another is value. The framework can recognize them.
The legal content is listing below:
- Keyword
-
param
The entire request parameter map. If the Invoke method argument is java.util.Map type, then this entire map will be directly passed to the method. And if the argument is other type, the framework will first convert it to the target type, by WebGenericConverter, then pass it to the method. -
yourArgKey
The parameters start with "yourArgKey" in request parameter map. The framework will simplely convert it to the Invoke method type and then pass it, if it is explicitly (only one value). Otherwise, the framework will first creat a new map who contains parameters only start with "yourArgKey", and it's key will also be truncated from "beanName.propertyName.subPropertyName" to "propertyName.subPropertyName". Then, it will directly pass this new map or pass the converted object by WebGenericConverter mentioned as above. -
param.yourArgKey
The same as above. -
request
The HttpServletRequest object. By default, the framework can not convert it. So, you must define your own support converter who converting "javax.servlet.http.HttpServletRequest" source to your destination type if the Invoke method argument is not HttpServletRequest. -
request.yourArgKey
The object whose key is "yourArgKey" in request scope. The framework will try to convert it to the Invoke method argument if they are not match. -
session
The HttpSession object. By default, the framework can not convert it. So, you must define your own support converter who converting "javax.servlet.http.HttpSession" source to your destination type if the Invoke method argument is not HttpSession. -
session.yourArgKey
The object whose key is "yourArgKey" in session scope. The framework will try to convert it to the Invoke method argument if they are not match. -
application
The ServletContext object. The framework can not convert it by default. So, you must define your own support converter who converting "javax.servlet.ServletContext" source to your destination type if the Invoke method argument is not ServletContext. -
application.yourArgKey
The object whose key is "yourArgKey" in application scope. The framework will try to convert it to the Invoke method argument if they are not match. -
response
The HttpServletResponse object. The framework can not convert it by default. So, you must define your own support converter who converting "javax.servlet.http.HttpServletResponse" source to your destination type if the Invoke method argument is not HttpServletResponse. -
objectSource
The current WebObjectSource object, you can get all the servlet objects from it. The framework can not convert it by default. So, you must define your own support converter who converting "org.soybeanMilk.web.os.WebObjectSource" source to your destination type if the Invoke method argument is not WebObjectSource.
-
param
-
Value
It can only be Java primitive type, String or null with the same syntax as Java language.-
boolean
Boolean value (true or false). The coresponding argument must be "boolean" or "Boolean" type. -
byte
Byte value. The coresponding argument must be "byte" or "Byte" type. -
char
Character. It can be normal character (such as 'a', 'b', 'c', '1', '5'), Java ESC character ('\n', '\r', '\t', '\'', '\\', '\"'), or unicode character ('\uxxxx'). The coresponding argument must be "char" or "Character" type. -
double
Double value. The coresponding argument must be "double" or "Double" type. -
float
Float value. The coresponding argument must be "float" or "Float" type. -
int
Int value. The coresponding argument must be "int" or "Integer" type. -
long
Long value. The coresponding argument must be "long" or "Long" type. -
short
Short value. The coresponding argument must be "short" or "Short" type. -
String
String value. It can contain characters defined in char segment above. The coresponding argument must be "String" type. -
null
Null. The coresponding argument can be any type except primitive.
-
boolean
The following are some examples:
<invoke name="yourGlobalInvoke" method="yourMethod" resolver="yourResolver" result-key="yourResultKey"> <arg>arg1</arg> <arg>param.arg2</arg> <arg>request.arg3</arg> <arg>100</arg> <arg>2.35f</arg> <arg>'a'</arg> <arg>"hello\t hello"</arg> <arg>true</arg> <arg>null</arg> </invoke>
<invoke name="/multiply.do"> multiplyResult = calculator.multiply(a, param.b); </invoke>
<invoke name="/doMultiply.do"> calculator.doMultiply(request, response); </invoke>
The Invoke method argument can be any type, but the framework default can only convert request parameters to these types:
primitive and their wrapper types, BigInteger and BigDecimal, java.util.Date and its sub types, JavaBean, and array type of the above;
or generic types, such as :
void genericMethod(List<Integer> list); void genericMethod(List<JavaBeanClass> list, Set<JavaBeanClass> set); void genericMethod(List<T> list, Set<G> set); void genericMethod(T t, G g); void genericMethod(T[] array);
Note that for type variable "T" or "G", will be thought as Object type, if there is no actual type for it in its class context.
If the framework can not recognize your Invoke method argument type, one solution is to define a support convert, or, another simple solution is to add a Invoke for type convertion before this Invoke:
<action name="/complexArgMethod.do"> <invoke> request.myArg = myResolver.paramToMyArg(param); </invoke> <invoke> myResolver.complexArgMethod(request.myArg); </invoke> </action>
Action configuration. You can add many <invoke> and <ref> tags into it, and they will be executed orderly.
It contains the following attributes:
-
name (Required)
Set the name of this Action, often the servlet path you want it to process.
You can also define variables in it, for RESTful support. For example:<action name="/user/{request.userId}/edit"> <invoke> userResolver.edit(request.userId) </invoke> </action>It will be able to process requests such as "/user/jack/edit" or "/user/tom/edit", and "jack" and "tom" will be put into WebObjectSource with key "request.userId" by the framework.
It is an example below:
<action name="/yourAction.do"> <invoke method="method0" resolver="yourResolver" result-key="result0" /> <invoke method="method1" resolver="yourResolver"> <arg>param.a<arg/> <arg>request.result0<arg/> <arg>true<arg/> </invoke> <ref name="gloablInvoke0" /> <ref name="gloablInvoke1" /> <target url="/action_result.jsp" /> </action>
Reference configuration. It helps you add one or more global Executable into this Action.
It contains the following attributes:
-
name (Required)
Set the global name of Executable you want to reference.
You must set the full name (with prefix) of the target Executable if it is not in this configuration file scope.
The target configuration of this Action.
It contains the following attributes:
-
url (Optional)
The target URL. It must be start with "/" if it is an URL in your application.
You can also use the keywords the WebObjectSource supported here for dynamic URL (With "{" prefix and "}" suffix). For example:<action name="/user/{request.userId}/edit"> <invoke> userResolver.edit(request.userId) </invoke> <target url="/user/{request.userId}/view" type="redirect" /> </action>The framework will redirect to "/user/jack/view" for request "/user/jack/edit". -
type (Optional)
The target type. It tells the type target handler (see <type-target-handler> tag) which target handler must handle this target.
It will be set to "forward" by the framework if you do not set it here.