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