Reference Manual for Gracelets - 2.0.0.SP3SourceForge.net Logo
5.2 - Framework : Extensions for Integration
Most technologies written for JSF/Facelets will work fine. However since Gracelets adds Groovy into the mix, you may want other technologies not written specifically for JSF/Facelets to be interactable with gracelet scripts. You can write Extensions that will accomplish this. Extensions allow you to initialize a technology when the application is being deployed. Also you can provide variable resolution, even an entire variable context (see below). Extensions also are able to be notified when the application is being undeployed and thus have a chance to cleanup or to run other house keeping code.

The gracelets.api.extension is the main package holding the interfaces/classes for writing a Gracelet Extension. Since many extensions will rely on the presence of a third part library, to write an extension, you first extend the gracelets.api.extension.GraceletsExtensionInitializer This class is defined as follows:

GraceletsExtensionInitializer.java
Toggle Line Numbers
1 package gracelets.api.extension; 
2  
3 import javax.faces.context.FacesContext; 
4 import javax.servlet.ServletContext; 
5  
6 import org.slf4j.Logger; 
7 import org.slf4j.LoggerFactory; 
8  
9 /** 
10  * An extension initializer will determine in some implementation specific manner whether or not 
11  * a {@link GraceletsExtension} should be loaded or not.<br/><br/>  
12  *  
13  * Since many extensions rely on third party libraries, this initializer concept allows a class that is not  
14  * dependent on such libraries to check for said libraries before any classes that could depend on them  
15  * are loaded, thus easily avoiding {@link ClassNotFoundException} during web application deployment. 
16  *  
17  * @author ponder 
18  * @author $Author: ponderator $ 
19  * @version $Id: GraceletsExtensionInitializer.java 1799 2009-08-08 00:26:22Z ponderator $ 
20  */ 
21 public abstract class GraceletsExtensionInitializer { 
22      
23     private final String name; 
24     private final String className; 
25     protected Logger log = LoggerFactory.getLogger(this.getClass()); 
26  
27     /** 
28      * Sub classes must provide a no-arg constructor that passes the parameters below to its super constructor. 
29      *  
30      * @param name The name of the initializer (must be unique) 
31      * @param className The class name of the extension, a class that implements {@link GraceletsExtension}. 
32      */ 
33     public GraceletsExtensionInitializer(String name, String className) { 
34       this.className = className; 
35       this.name = name; 
36   } 
37  
38     /** 
39      * @param ctx The current servlet context for the application in which this extension is being initialized 
40      * @return True if the extension should be loaded, otherwise false 
41      */ 
42     public abstract boolean shouldLoad (ServletContext ctx); 
43      
44     /** 
45      * @return The name of the extension 
46      */ 
47     public String getName() { return name; } 
48  
49     /** 
50      * This will only be called if {@link #shouldLoad(ServletContext)} returns true. 
51      *  
52      * @return An instance of the extension 
53      */ 
54     public GraceletsExtension createExtension () { 
55     try { 
56         return (GraceletsExtension) Class.forName(className, true, Thread.currentThread().getContextClassLoader()).newInstance(); 
57     } catch (InstantiationException e) { 
58         throw new RuntimeException(e); 
59     } catch (IllegalAccessException e) { 
60         throw new RuntimeException(e); 
61     } catch (ClassNotFoundException e) { 
62         throw new RuntimeException(e); 
63     } catch (Exception e) { 
64         throw new RuntimeException(e); 
65     } 
66     } 
67      
68 } 
This class can use class loaders to determine if a particular class or third party library is available. This allows the initializer to run without being dependant upon the third part library linked classes. The extension in its constructor will pass the class name that refers to the actual extension as mentioned below. For example, the JBoss Seam extension initializer is defined as follows:

SeamExtensionInitializer.java
Toggle Line Numbers
1 package gracelets.api.extension.seam; 
2  
3 import javax.faces.context.FacesContext; 
4 import javax.servlet.ServletContext; 
5  
6 import gracelets.api.extension.GraceletsExtensionInitializer; 
7  
8 /** 
9  * @author elponderador 
10  * 
11  */ 
12 public class SeamExtensionInitializer extends GraceletsExtensionInitializer { 
13  
14     public SeamExtensionInitializer() { 
15         super("seam", "gracelets.api.extension.seam.SeamExtension"); 
16     } 
17  
18     @Override public boolean shouldLoad(ServletContext ctx) { 
19         try { 
20             Class.forName("org.jboss.seam.Component", false, Thread.currentThread().getContextClassLoader()); 
21         } catch (ClassNotFoundException e) { 
22             return false; 
23         } 
24          
25         return true; 
26     } 
27  
28 } 
Above we use the Thread context class loader to determine if the JBoss Seam libraries are available. If they are we return true and Gracelets will load the class referred to in the constructor, otherwise the extension will not be loaded.

The class name passed in the constructor must be a class that implements the gracelets.api.extension.GraceletsExtension interface. This interface is defined as follows:

GraceletExtension.java
Toggle Line Numbers
1 package gracelets.api.extension; 
2  
3 import gracelets.api.Gracelets; 
4 import gracelets.api.binding.GraceletBinding; 
5 import gracelets.api.context.VariableContext; 
6 import gracelets.api.facelets.GraceletsCompiler; 
7 import javax.faces.context.FacesContext; 
8 import javax.servlet.ServletContext; 
9  
10 /** 
11  * This is the basic contract/interface for {@link Gracelets} extensions. Implementations must provide 
12  * a no-arg constructor which will be used when creating the extension. 
13  *  
14  * @author ponder 
15  * @author $Author: ponderator $ 
16  * @version $Id: GraceletsExtension.java 1709 2009-07-10 02:25:59Z ponderator $ 
17  */ 
18 public interface GraceletsExtension { 
19  
20     /** 
21      * This will be called by the implementation in order to allow the extension to initialize itself. This will be 
22      * called during web application deployment. 
23      *  
24      * @param ctx The context access object 
25      * @throws Exception 
26      */ 
27     void initialize (GraceletsExtensionInitializeContext ctx) throws Exception; 
28      
29     /** 
30      * This will be called by the implementation in order to allow the extension to do cleanup. This will be called 
31      * during web application undeployment. 
32      *  
33      * @throws Exception 
34      */ 
35     void destroy () throws Exception; 
36      
37     /** 
38      * This allows extensions to run initialization on the first request, which is used by other components of the system 
39      * that need access to a real JSF request in order to accomplish something. 
40      *  
41      * @param ctx The current faces context for this request 
42      * @param compiler The gracelets compiler 
43      * @throws Exception 
44      */ 
45     void postInitialization (FacesContext ctx, GraceletsCompiler compiler) throws Exception; 
46      
47     /** 
48      * This allows extensions to add/bind default values when bindings are generated for libraries, scripts 
49      * and controllers for example. 
50      *  
51      * @param binding The binding that is being prepared/initialized  
52      * @param owner The owner of the binding, generally a script 
53      * @throws Exception 
54      *  
55      * @see {@link GraceletBinding#bind(String, Object)} 
56      */ 
57     void initializeBinding (GraceletBinding binding, Object owner) throws Exception; 
58      
59     /** 
60      * This allows extensions to provide variable contexts for technology specific variable resolution. 
61      *  
62      * @param ctx The faces context of the current request 
63      * @return A variable context for the request, or null if the extension does not want to generate one for this request 
64      */ 
65     VariableContext generateVariableContext (FacesContext ctx); 
66      
67     /** 
68      * This allows a simple way for implementations to allow extensions to take part in variable resolution. When this is called 
69      * and in what context is implementation dependent. 
70      *  
71      * @param name A name to resolve 
72      * @return A value that this extension wants to provide for the name, or null if it does not want to participate in the resolution 
73      * for this variable. 
74      */ 
75     Object resolve (String name); 
76      
77 } 
The initialize() method will be called when Gracelets itself is being initialized, during the deployment of the web application. You can register servlet Filters and JSF PhaseListeners here so that you can plugin to the system without requiring the user to setup these artifacts in their web.xml or faces-config.xml.

The destroy() method will be called when the application is being undeployed().

The postInitialization() method will be called on the first request when Facelets/Gracelets finishes initialization. You can use this to add a Factory or outject variables to the variable context that you want available to controllers/views from the start.

The initializeBinding() method will be called whenever a GraceletBinding instance is created giving you a chance to bind() certain variables from the beginning, you can use the bind() method on the GraceletBinding instance to do this.

The generateVariableContext() method will be called once every request. You can simply return null if your extension does not provide a variable context.

The resolve() method will allow you to do lazy initialization of certain variables related to your extension. See the bindings section to know when this will actually be called.

A concrete example

The JPA extension is shown below and will give you a concrete example of how an extension can be implemented:

JPAExtension.java
Toggle Line Numbers
1 package gracelets.api.extension.jpa; 
2  
3 import java.io.File; 
4 import java.io.IOException; 
5 import java.io.PrintWriter; 
6 import java.net.MalformedURLException; 
7 import java.net.URL; 
8 import java.net.URLClassLoader; 
9 import java.sql.Connection; 
10 import java.sql.DriverManager; 
11 import java.sql.SQLException; 
12 import java.util.ArrayList; 
13 import java.util.Collections; 
14 import java.util.Enumeration; 
15 import java.util.LinkedHashMap; 
16 import java.util.List; 
17 import java.util.Map; 
18 import java.util.Properties; 
19 import java.util.Set; 
20 import java.util.regex.Pattern; 
21  
22 import javax.faces.context.FacesContext; 
23 import javax.persistence.EntityManagerFactory; 
24 import javax.persistence.spi.ClassTransformer; 
25 import javax.persistence.spi.PersistenceProvider; 
26 import javax.persistence.spi.PersistenceUnitInfo; 
27 import javax.persistence.spi.PersistenceUnitTransactionType; 
28 import javax.servlet.ServletContext; 
29 import javax.sql.DataSource; 
30  
31 import org.slf4j.Logger; 
32 import org.slf4j.LoggerFactory; 
33  
34 import gracelets.api.Gracelets; 
35 import gracelets.api.binding.GraceletBinding; 
36 import gracelets.api.context.Scope; 
37 import gracelets.api.context.VariableContext; 
38 import gracelets.api.context.factory.BeanFactory; 
39 import gracelets.api.context.factory.BeanGeneratorFactory; 
40 import gracelets.api.context.factory.ClosureFactory; 
41 import gracelets.api.context.factory.ELFactory; 
42 import gracelets.api.extension.GraceletsExtension; 
43 import gracelets.api.extension.GraceletsExtensionInitializeContext; 
44 import gracelets.api.facelets.GraceletsCompiler; 
45 import gracelets.api.resource.ComponentScanner; 
46 import gracelets.api.resource.ResourceInfo; 
47 import gracelets.util.GeneralUtil; 
48 import groovy.lang.Binding; 
49 import groovy.lang.Script; 
50  
51 /** 
52  * @author ponder 
53  * @version $Revision$ 
54  */ 
55 public class JPAExtension implements GraceletsExtension, BeanGeneratorFactory<Object> { 
56      
57     Logger log = LoggerFactory.getLogger(JPAExtension.class); 
58      
59     EntityManagerFactory emf; 
60     String scope; 
61     String sname; 
62     boolean autoclose = true; 
63  
64     public void initialize(GraceletsExtensionInitializeContext ctx) throws Exception { 
65         URL gc = null; 
66         URL hc = ctx.getServletContext().getResource("/WEB-INF/classes/persistence.properties"); 
67         if (hc == null) { 
68             gc = ctx.getServletContext().getResource("/WEB-INF/classes/persistence.groovy"); 
69              
70             if (gc == null) { 
71                 log.warn("No persistence.properties detected"); 
72                 return; 
73             } 
74         } 
75          
76         Object result = null; 
77         Properties props = new Properties(); 
78         Map<String, Object> properties = new LinkedHashMap<String, Object>(); 
79          
80         if (hc != null) { 
81             props.load(hc.openStream()); 
82             Enumeration names = props.propertyNames(); 
83             while (names.hasMoreElements()) { 
84                 String name = String.valueOf(names.nextElement()); 
85                 properties.put(name, props.getProperty(name)); 
86             } 
87         } 
88         else { 
89             Class<Script> scriptClass = Gracelets.getClassLoader().parseClass(gc.openStream()); 
90             Script script = scriptClass.newInstance(); 
91             Binding binding = new Binding(); 
92             binding.setVariable("log", log); 
93             binding.setVariable("properties", properties); 
94             script.setBinding(binding); 
95             result = script.run(); 
96         } 
97          
98         scope = "session"; 
99         String scopes = (String) properties.get("gracelets.scope"); 
100         if (scopes != null) scope = scopes.toLowerCase(); 
101          
102         sname = (String) properties.get("gracelets.name"); 
103         if (sname == null) sname = "storage"; 
104          
105         if (result instanceof EntityManagerFactory) { 
106             emf = (EntityManagerFactory) result; 
107             this.autoclose = false; 
108         } else { 
109             DataSource dataSource = null; 
110              
111             Object datasource = properties.get("connection.datasource"); 
112             if (datasource != null && datasource instanceof DataSource) { 
113                 dataSource = (DataSource) datasource; 
114             } else { 
115                 String driver = (String) properties.get("connection.driver"); 
116                 if (driver != null) Class.forName(driver.trim(), true, Thread.currentThread().getContextClassLoader()); 
117                  
118                 String url = (String) properties.get("connection.url"); 
119                 if (url == null) throw new IllegalArgumentException("Must specify connection.url for jpa persistence."); 
120                  
121                 String username = (String) properties.get("connection.username"); 
122                 String password = (String) properties.get("connection.password"); 
123                 properties.remove("connection.driver"); 
124                 properties.remove("connection.url"); 
125                 properties.remove("connection.username"); 
126                 properties.remove("connection.password"); 
127                  
128                 dataSource = new LocalDataSource(url, username, password); 
129             } 
130                  
131             String providerClass = (String) properties.get("provider.class"); 
132             if (providerClass == null) throw new IllegalArgumentException("Must specify provider.class for jpa persistence"); 
133              
134             Properties unitProperties = new Properties(); 
135             for (String key : properties.keySet()) { 
136                 unitProperties.setProperty(key, String.valueOf( properties.get(key) )); 
137             } 
138              
139             PersistenceProvider provider = (PersistenceProvider) Class.forName(providerClass.trim(), true, Thread.currentThread().getContextClassLoader()).newInstance(); 
140             PersistenceUnitInfo info = new UnitInfo(unitProperties, providerClass, sname,  
141                 dataSource, new File(ctx.getServletContext().getRealPath("/WEB-INF/classes")) 
142             ); 
143              
144             emf = provider.createContainerEntityManagerFactory(info, unitProperties); 
145         } 
146     } 
147  
148     public void destroy() throws Exception {  
149         if (this.autoclose) emf.close();  
150     } 
151  
152     public VariableContext generateVariableContext(FacesContext ctx) { 
153         return null; 
154     } 
155  
156     public void initializeBinding(GraceletBinding binding, Object owner) throws Exception {} 
157  
158     public void postInitialization(FacesContext ctx, GraceletsCompiler compiler) throws Exception { 
159         VariableContext vctx = Gracelets.getGraceletsContext().getVariableContext(); 
160         Gracelets.getGraceletsContext().getVariableContext().addFactory(sname, new BeanFactory(scope, this, sname)); 
161     } 
162      
163     public Object generate(String id) { return this.getSession(); } 
164  
165     public JPAEql getSession () { return new JPAEql(emf.createEntityManager()); } 
166  
167     public Object resolve(String name) { 
168       return "session".equals(name) ? this.getSession() : null; 
169   } 
170      
171     public class LocalDataSource implements DataSource { 
172          
173         PrintWriter writer; 
174         int loginTimeout = 10; 
175         String url; 
176         String username; 
177         String password; 
178          
179         public LocalDataSource (String url, String user, String pass) { 
180             this.url = url; 
181             this.username = user; 
182             this.password = pass; 
183         } 
184  
185         public boolean isWrapperFor(Class<?> iface) throws SQLException { 
186             return false; 
187         } 
188  
189         public <T> T unwrap(Class<T> iface) throws SQLException { 
190             return null; 
191         } 
192  
193         public Connection getConnection() throws SQLException { 
194             if (username == null || "".equals(username)) return DriverManager.getConnection(url); 
195             else return this.getConnection(username, password); 
196     } 
197  
198         public Connection getConnection(String username, String password) throws SQLException { 
199         return DriverManager.getConnection(url, username, password); 
200     } 
201  
202         public int getLoginTimeout() throws SQLException { 
203         return this.loginTimeout; 
204     } 
205  
206         public PrintWriter getLogWriter() throws SQLException { 
207         return writer; 
208     } 
209  
210         public void setLoginTimeout(int seconds) throws SQLException { 
211             this.loginTimeout = seconds; 
212         } 
213  
214         public void setLogWriter(PrintWriter out) throws SQLException { 
215             this.writer = out; 
216         } 
217          
218     } 
219  
220     public class UnitInfo implements PersistenceUnitInfo { 
221          
222         Properties props; 
223         String ppCN; 
224         String name; 
225         URL rootURL; 
226         DataSource dataSource; 
227         List<URL> urls; 
228          
229         public UnitInfo (Properties props, String ppClassName, String name, DataSource dataSource, File root) { 
230             try { 
231                 this.props = props; 
232           this.name = name; 
233           this.rootURL = root.toURL(); 
234           this.dataSource = dataSource; 
235           this.ppCN = ppClassName; 
236           ComponentScanner scanner = new ComponentScanner(); 
237           scanner.addPattern(Pattern.compile("^gracelets\\.jpa\\.properties$")); 
238           List<gracelets.api.resource.ResourceScanner.ResourceInfo> resources = scanner.scan(); 
239           this.urls = new ArrayList<URL>(); 
240           for (gracelets.api.resource.ResourceScanner.ResourceInfo info : resources) { 
241             this.urls.add(info.getRootResourceUrl()); 
242           } 
243       } catch (IOException e) { 
244           throw new RuntimeException(e); 
245       } 
246         } 
247  
248         public void addTransformer(ClassTransformer transformer) { 
249             log.warn("Adding class transformer: " + transformer); 
250         } 
251  
252         public boolean excludeUnlistedClasses() { return false; } 
253  
254         public ClassLoader getClassLoader() { 
255         return Thread.currentThread().getContextClassLoader(); 
256     } 
257  
258         public List<URL> getJarFileUrls() { return this.urls; } 
259         public DataSource getJtaDataSource() { return null; } 
260         public List<String> getManagedClassNames() { return new ArrayList<String>(); } 
261         public List<String> getMappingFileNames() { return new ArrayList<String>(); } 
262          
263         public ClassLoader getNewTempClassLoader() { 
264             return new URLClassLoader(new URL[] { rootURL }, Thread.currentThread().getContextClassLoader()); 
265         } 
266          
267         public DataSource getNonJtaDataSource() { return dataSource; } 
268         public String getPersistenceProviderClassName() { return ppCN; } 
269         public String getPersistenceUnitName() { return name; } 
270         public URL getPersistenceUnitRootUrl() { return rootURL; } 
271         public Properties getProperties() { return props; } 
272         public PersistenceUnitTransactionType getTransactionType() { 
273         return PersistenceUnitTransactionType.RESOURCE_LOCAL; 
274     } 
275          
276     } 
277      
278 }