AUTHOR : | |||
E. Bruneton | (France Telecom R&D) |
Released | November 12, 2003 |
Status | Final |
Version | 1.0.2 |
The main characteristics of the Fractal model are
recursion, reflexion and sharing. The Fractal project is made of four
sub projects: model, implementations, components and tools.
|
public class
Server implements Runnable {
private Socket s; public Server (Socket s) { this.s = s; } public static void main (String[] args) throws IOException { ServerSocket s = new ServerSocket(8080); while (true) { new Thread(new Server(s.accept())).start(); } } public void run () { try { InputStreamReader in = new InputStreamReader(s.getInputStream()); PrintStream out = new PrintStream(s.getOutputStream()); String rq = new LineNumberReader(in).readLine(); System.out.println(rq); if (rq.startsWith("GET ")) { File f = new File(rq.substring(5, rq.indexOf(' ', 4))); if (f.exists() && !f.isDirectory()) { InputStream is = new FileInputStream(f); byte[] data = new byte[is.available()]; is.read(data); is.close(); out.print("HTTP/1.0 200 OK\n\n"); out.write(data); } else { out.print("HTTP/1.0 404 Not Found\n\n"); out.print("<html>Document not found.</html>"); } } out.close(); s.close(); } catch (IOException _) { } } } |
The first step to design a component based application
is to define its components. This is done by finding the services
used in this application. The second step is to find the dependencies
and hierarchical levels of these components. The last step is to define
precisely the contracts between the components.
|
public interface
Logger { void log (String msg);
}
public interface Scheduler { void schedule (Runnable task); } public interface RequestHandler { void handleRequest (Request r) throws IOException; } |
public class
BasicLogger implements Logger {
public void log (String msg) { System.out.println(msg); } } public class SequentialScheduler implements Scheduler { public synchronized void schedule (Runnable task) { task.run(); } } public class MultiThreadScheduler implements Scheduler { public void schedule (Runnable task) { new Thread(task).start(); } } |
public class
RequestReceiver {
private Scheduler s = new MultiThreadScheduler(); private RequestHandler rh = new RequestAnalyzer(); // rest of the code not shown } |
public class
RequestReceiver {
private Scheduler s; private RequestHandler rh; public RequestReceiver (Scheduler s, RequestHandler rh) { this.s = s; this.rh = rh; } // rest of the code not shown } |
public class
RequestReceiver implements Runnable,
BindingController {
private Scheduler s; private RequestHandler rh; // configuration concern public String[] listFc () { return new String[] { "s", "rh" }; } public Object lookupFc (String itfName) { if (itfName.equals("s")) { return s; } else if (itfName.equals("rh")) { return rh; } else return null; } public void bindFc (String itfName, Object itfValue) { if (itfName.equals("s")) { s = (Scheduler)itfValue; } else if (itfName.equals("rh")) { rh = (RequestHandler)itfValue; } } public void unbindFc (String itfName) { if (itfName.equals("s")) { s = null; } else if (itfName.equals("rh")) { rh = null; } } // functional concern public void run () { /* see Appendix A */ } } |
public class
RequestDispatcher implements RequestHandler,
BindingController {
private Map handlers = new TreeMap(); // configuration concern public String[] listFc () { return (String[])handlers.keySet().toArray(new String[handlers.size()]); } public Object lookupFc (String itfName) { if (itfName.startsWith("h")) { return handlers.get(itfName); } else return null; } public void bindFc (String itfName, Object itfValue) { if (itfName.startsWith("h")) { handlers.put(itfName, itfValue); } } public void unbindFc (String itfName) { if (itfName.startsWith("h")) { handlers.remove(itfName); } } // functional concern public void handleRequest (Request r) throws IOException { /* see Appendix A */ } } |
Components must be implemented with an appropriate
granularity, resulting from a compromise between adaptability and
performance. Their interfaces must be separated from their implementation
(this rule must also be applied for data structures). The implementation
must not contain explicit dependencies to other components to allow
static and dynamic reconfigurations.
|
public class
Server {
public static void main (String[] args) { RequestReceiver rr = new RequestReceiver(); RequestAnalyzer ra = new RequestAnalyzer(); RequestDispatcher rd = new RequestDispatcher(); FileRequestHandler frh = new FileRequestHandler(); ErrorRequestHandler erh = new ErrorRequestHandler(); Scheduler s = new MultiThreadScheduler(); Logger l = new BasicLogger(); rr.bindFc("rh", ra); rr.bindFc("s", s); ra.bindFc("rh", rd); ra.bindFc("l", l); rd.bindFc("h0", frh); rd.bindFc("h1", erh); rr.run(); } } |
<component-type name="HandlerType">
<provides><interface-type name="rh" signature="comanche.RequestHandler"/></provides> </component-type> |
<component-type name="DispatcherType"
extends="HandlerType">
<requires> <interface-type name="h" signature="comanche.RequestHandler" cardinality="collection"/> </requires> </component-type> |
<primitive-template name="FileHandler"
implements="HandlerType">
<primitive-content class="comanche.FileRequestHandler"/> </primitive-template> |
<composite-template name="Comanche"
implements="RunnableType">
<composite-content> <components> <component name="fe" type="FrontendType" implementation="Frontend"/> <component name="be" type="HandlerType" implementation="Backend"/> </components> <bindings> <binding client="this.r" server="fe.r"/> <binding client="fe.rh" server="be.rh"/> </bindings> </composite-content> </composite-template> |
Components can be configured and deployed in three
different ways. The programmatic approach is the most direct but mixes
different concerns. The ADL based approach correctly separates these
concerns. The graphical and interactive approach also solves this
problem and, in addition, provides a better architectural representation.
|
RequestHandler rh = new FileAndDirectoryRequestHandler();
rd.unbindFc("h0"); rd.bindFc("h0", rh); |
public class
RequestDispatcher implements RequestHandler,
BindingController, LifeCycleController {
private boolean started; private int counter; public String getFcState () { return started ? STARTED : STOPPED; } public synchronized void startFc () { started = true; notifyAll(); } public synchronized void stopFc () { while (counter > 0) { try { wait(); } catch (InterruptedException _) {} } started = false; } public void handleRequest (Request r) throws IOException { synchronized (this) { while (counter == 0 && !started) { try { wait(); } catch (InterruptedException _) {} } ++counter; } try { // original code } finally { synchronized (this) { -counter; if (counter == 0) { notifyAll(); } } } } // rest of the class unchanged } |
public class
Interceptor implements RequestHandler,
LifeCycleController {
public RequestHandler delegate; private boolean started; private int counter; public String getFcState () { /* as above */ } public synchronized void startFc () { /* as above */ } public synchronized void stopFc () { /* as above */ } public void handleRequest (Request r) throws IOException { synchronized (this) { /* as above */ } try { delegate.handleRequest(r); } finally { synchronized (this) { /* as above */ } } } } |
public interface
Component {
Object[] getFcInterfaces (); Object getFcInterface (String itfName); Type getFcType (); } public interface ContentController { Object[] getFcInternalInterfaces (); Object getFcInterfaceInterface(String itfName); Component[] getFcSubComponents (); void addFcSubComponent (Component c); void removeFcSubComponent(Component c); } |
Fractal provides some basic tools to allow dynamic
reconfiguration, namely reflexion and life cycle management. But dynamic
reconfiguration is difficult and not yet completely solved in Fractal
(in particular, state management is not yet specified).
|
/* =============== Component interfaces ===============
*/
public interface RequestHandler { void handleRequest (Request r) throws IOException; } public interface Scheduler { void schedule (Runnable task); } public interface Logger { void log (String msg); } public class Request { public Socket s; public Reader in; public PrintStream out; public String url; public Request (Socket s) { this.s = s; } } /* =============== Component implementations =============== */ public class BasicLogger implements Logger { public void log (String msg) { System.out.println(msg); } } public class SequentialScheduler implements Scheduler { public synchronized void schedule (Runnable task) { task.run(); } } public class MultiThreadScheduler implements Scheduler { public void schedule (Runnable task) { new Thread(task).start(); } } public class FileRequestHandler implements RequestHandler { public void handleRequest (Request r) throws IOException { File f = new File(r.url); if (f.exists() && !f.isDirectory()) { InputStream is = new FileInputStream(f); byte[] data = new byte[is.available()]; is.read(data); is.close(); r.out.print("HTTP/1.0 200 OK\n\n"); r.out.write(data); } else { throw new IOException("File not found"); } } } public class ErrorRequestHandler implements RequestHandler { public void handleRequest (Request r) throws IOException { r.out.print("HTTP/1.0 404 Not Found\n\n"); r.out.print("<html>Document not found.</html>"); } } public class RequestDispatcher implements RequestHandler, BindingController { private Map handlers = new TreeMap(); // configuration concern public String[] listFc () { return (String[])handlers.keySet().toArray(new String[handlers.size()]); } public Object lookupFc (String itfName) { if (itfName.startsWith("h")) { return handlers.get(itfName); } else return null; } public void bindFc (String itfName, Object itfValue) { if (itfName.startsWith("h")) { handlers.put(itfName, itfValue); } } public void unbindFc (String itfName) { if (itfName.startsWith("h")) { handlers.remove(itfName); } } // functional concern public void handleRequest (Request r) throws IOException { Iterator i = handlers.values().iterator(); while (i.hasNext()) { try { ((RequestHandler)i.next()).handleRequest(r); return; } catch (IOException _) { } } } } public class RequestAnalyzer implements RequestHandler, BindingController { private Logger l; private RequestHandler rh; // configuration concern public String[] listFc () { return new String[] { "l", "rh" }; } public Object lookupFc (String itfName) { if (itfName.equals("l")) { return l; } else if (itfName.equals("rh")) { return rh; } else return null; } public void bindFc (String itfName, Object itfValue) { if (itfName.equals("l")) { l = (Logger)itfValue; } else if (itfName.equals("rh")) { rh = (RequestHandler)itfValue; } } public void unbindFc (String itfName) { if (itfName.equals("l")) { l = null; } else if (itfName.equals("rh")) { rh = null; } } // functional concern public void handleRequest (Request r) throws IOException { r.in = new InputStreamReader(r.s.getInputStream()); r.out = new PrintStream(r.s.getOutputStream()); String rq = new LineNumberReader(r.in).readLine(); l.log(rq); if (rq.startsWith("GET ")) { r.url = rq.substring(5, rq.indexOf(' ', 4)); rh.handleRequest(r); } r.out.close(); r.s.close(); } } public class RequestReceiver implements Runnable, BindingController { private Scheduler s; private RequestHandler rh; // configuration concern public String[] listFc () { return new String[] { "s", "rh" }; } public Object lookupFc (String itfName) { if (itfName.equals("s")) { return s; } else if (itfName.equals("rh")) { return rh; } else return null; } public void bindFc (String itfName, Object itfValue) { if (itfName.equals("s")) { s = (Scheduler)itfValue; } else if (itfName.equals("rh")) { rh = (RequestHandler)itfValue; } } public void unbindFc (String itfName) { if (itfName.equals("s")) { s = null; } else if (itfName.equals("rh")) { rh = null; } } // functional concern public void run () { try { ServerSocket ss = new ServerSocket(8080); while (true) { final Socket socket = ss.accept(); s.schedule(new Runnable () { public void run () { try { rh.handleRequest(new Request(socket)); } catch (IOException _) { } } }); } } catch (IOException e) { e.printStackTrace(); } } } |
<!- ============== Component types ==============
->
<component-type name="RunnableType"> <provides><interface-type name="r" signature="java.lang.Runnable"/></provides> </component-type> <component-type name="FrontendType" extends="RunnableType"> <requires><interface-type name="rh" signature="comanche.RequestHandler"/></requires> </component-type> <component-type name="ReceiverType" extends="FrontendType"> <requires><interface-type name="s" signature="comanche.Scheduler"/></requires> </component-type> <component-type name="SchedulerType"> <provides><interface-type name="s" signature="comanche.Scheduler"/></provides> </component-type> <component-type name="HandlerType"> <provides><interface-type name="rh" signature="comanche.RequestHandler"/></provides> </component-type> <component-type name="AnalyzerType"> <provides><interface-type name="a" signature="comanche.RequestHandler"/></provides> <requires> <interface-type name="rh" signature="comanche.RequestHandler"/> <interface-type name="l" signature="comanche.Logger"/> </requires> </component-type> <component-type name="LoggerType"> <provides><interface-type name="l" signature="comanche.Logger"/></provides> </component-type> <component-type name="DispatcherType" extends="HandlerType"> <requires> <interface-type name="h" signature="comanche.RequestHandler" cardinality="collection"/> </requires> </component-type> <!- ============== Primitive components ============== -> <primitive-template name="Receiver" implements="ReceiverType"> <primitive-content class="comanche.RequestReceiver"/> </primitive-template> <primitive-template name="SequentialScheduler" implements="SchedulerType"> <primitive-content class="comanche.SequentialScheduler"/> </primitive-template> <primitive-template name="MultiThreadScheduler" implements="SchedulerType"> <primitive-content class="comanche.MultiThreadScheduler"/> </primitive-template> <primitive-template name="Analyzer" implements="AnalyzerType"> <primitive-content class="comanche.RequestAnalyzer"/> </primitive-template> <primitive-template name="Logger" implements="LoggerType"> <primitive-content class="comanche.BasicLogger"/> </primitive-template> <primitive-template name="Dispatcher" implements="DispatcherType"> <primitive-content class="comanche.RequestDispatcher"/> </primitive-template> <primitive-template name="FileHandler" implements="HandlerType"> <primitive-content class="comanche.FileRequestHandler"/> </primitive-template> <primitive-template name="ErrorHandler" implements="HandlerType"> <primitive-content class="comanche.ErrorRequestHandler"/> </primitive-template> <!- ============== Composite components ============== -> <composite-template name="Handler" implements="HandlerType"> <composite-content> <components> <component name="rd" type="DispatcherType" implementation="Dispatcher"/> <component name="frh" type="HandlerType" implementation="FileHandler"/> <component name="erh" type="HandlerType" implementation="ErrorHandler"/> </components> <bindings> <binding client="this.rh" server="rd.rh"/> <binding client="rd.h0" server="frh.rh"/> <binding client="rd.h1" server="erh.rh"/> </bindings> </composite-content> </composite-template> <composite-template name="Backend" implements="HandlerType"> <composite-content> <components> <component name="ra" type="AnalyzerType" implementation="Analyzer"/> <component name="rh" type="HandlerType" implementation="Handler"/> <component name="l" type="LoggerType" implementation="Logger"/> </components> <bindings> <binding client="this.rh" server="ra.a"/> <binding client="ra.rh" server="rh.rh"/> <binding client="ra.l" server="l.l"/> </bindings> </composite-content> </composite-template> <composite-template name="Frontend" implements="FrontendType"> <composite-content> <components> <component name="rr" type="ReceiverType" implementation="Receiver"/> <component name="s" type="SchedulerType" implementation="MultiThreadScheduler"/> </components> <bindings> <binding client="this.r" server="rr.r"/> <binding client="rr.s" server="s.s"/> <binding client="rr.rh" server="this.rh"/> </bindings> </composite-content> </composite-template> <composite-template name="Comanche" implements="RunnableType"> <composite-content> <components> <component name="fe" type="FrontendType" implementation="Frontend"/> <component name="be" type="HandlerType" implementation="Backend"/> </components> <bindings> <binding client="this.r" server="fe.r"/> <binding client="fe.rh" server="be.rh"/> </bindings> </composite-content> </composite-template> |