1 /* Copyright 2004-2005 the original author or authors.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 package org.codehaus.groovy.grails.cli.support;
16 
17 import grails.build.GrailsBuildListener;
18 import grails.util.BuildSettings;
19 import grails.util.GrailsNameUtils;
20 import grails.util.GrailsUtil;
21 import grails.util.PluginBuildSettings;
22 import groovy.lang.Binding;
23 import groovy.lang.Closure;
24 import groovy.lang.GroovyClassLoader;
25 import groovy.lang.MissingPropertyException;
26 import groovy.lang.Script;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 import org.apache.tools.ant.BuildEvent;
39 import org.apache.tools.ant.BuildListener;
40 import org.springframework.core.io.Resource;
41 
42 /**
43  * @author Graeme Rocher
44  * @since 1.1
45  */
46 public class GrailsBuildEventListener implements BuildListener{
47 
48     private static final Pattern EVENT_NAME_PATTERN = Pattern.compile("event([A-Z]\\w*)");
49     private GroovyClassLoader classLoader;
50     private Binding binding;
51     protected Map<String, List<Closure>> globalEventHooks = new HashMap<String, List<Closure>>();
52     private BuildSettings buildSettings;
53 
54     /**
55      * The objects that are listening for build events
56      */
57     private List<GrailsBuildListener> buildListeners = new LinkedList<GrailsBuildListener>();
58 
GrailsBuildEventListener(GroovyClassLoader scriptClassLoader, Binding binding, BuildSettings buildSettings)59     public GrailsBuildEventListener(GroovyClassLoader scriptClassLoader, Binding binding, BuildSettings buildSettings) {
60         this.classLoader = scriptClassLoader;
61         this.binding = binding;
62         this.buildSettings = buildSettings;
63     }
64 
initialize()65     public void initialize() {
66         loadEventHooks(buildSettings);
67         loadGrailsBuildListeners();
68     }
69 
setClassLoader(GroovyClassLoader classLoader)70     public void setClassLoader(GroovyClassLoader classLoader) {
71         this.classLoader = classLoader;
72     }
73 
setGlobalEventHooks(Map<String, List<Closure>> globalEventHooks)74     public void setGlobalEventHooks(Map<String, List<Closure>> globalEventHooks) {
75         this.globalEventHooks = globalEventHooks;
76     }
77 
loadEventHooks(@uppressWarningsR) BuildSettings buildSettings)78     protected void loadEventHooks(@SuppressWarnings("hiding") BuildSettings buildSettings) {
79         if (buildSettings == null) {
80             return;
81         }
82 
83         loadEventsScript(findEventsScript(new File(buildSettings.getUserHome(),".grails/scripts")));
84         loadEventsScript(findEventsScript(new File(buildSettings.getBaseDir(), "scripts")));
85 
86         PluginBuildSettings pluginSettings = (PluginBuildSettings) binding.getVariable("pluginSettings");
87         for (Resource pluginBase : pluginSettings.getPluginDirectories()) {
88             try {
89                 loadEventsScript(findEventsScript(new File(pluginBase.getFile(), "scripts")));
90             }
91             catch (IOException ex) {
92                 throw new RuntimeException(ex);
93             }
94         }
95     }
96 
loadGrailsBuildListeners()97     protected void loadGrailsBuildListeners() {
98         for (Object listener : buildSettings.getBuildListeners()) {
99             if (listener instanceof String) {
100                 addGrailsBuildListener((String)listener);
101             }
102             else if (listener instanceof Class<?>) {
103                 addGrailsBuildListener((Class<?>)listener);
104             }
105             else {
106                 throw new IllegalStateException("buildSettings.getBuildListeners() returned a " + listener.getClass().getName());
107             }
108         }
109     }
110 
loadEventsScript(File eventScript)111     public void loadEventsScript(File eventScript) {
112         if (eventScript == null) {
113             return;
114         }
115 
116         try {
117             Class<?> scriptClass = classLoader.parseClass(eventScript);
118             if (scriptClass == null) {
119                System.err.println("Could not load event script (script may be empty): " + eventScript);
120                return;
121             }
122 
123             Script script = (Script) scriptClass.newInstance();
124             script.setBinding(new Binding(binding.getVariables()) {
125                 @Override
126                 public void setVariable(String var, Object o) {
127                     final Matcher matcher = EVENT_NAME_PATTERN.matcher(var);
128                     if (matcher.matches() && (o instanceof Closure)) {
129                         String eventName = matcher.group(1);
130                         List<Closure> hooks = globalEventHooks.get(eventName);
131                         if (hooks == null) {
132                             hooks = new ArrayList<Closure>();
133                             globalEventHooks.put(eventName, hooks);
134                         }
135                         hooks.add((Closure) o);
136                     }
137                     super.setVariable(var, o);
138                 }
139             });
140             script.run();
141         }
142         catch (Throwable e) {
143             GrailsUtil.deepSanitize(e);
144             e.printStackTrace();
145             System.out.println("Error loading event script from file [" + eventScript + "] " + e.getMessage());
146         }
147     }
148 
findEventsScript(File dir)149     protected File findEventsScript(File dir) {
150         File f = new File(dir, "_Events.groovy");
151         if (!f.exists()) {
152             f = new File(dir, "Events.groovy");
153             if (f.exists()) {
154                 GrailsUtil.deprecated("Use of 'Events.groovy' is DEPRECATED.  Please rename to '_Events.groovy'.");
155             }
156         }
157 
158         return f.exists() ? f : null;
159     }
160 
buildStarted(BuildEvent buildEvent)161     public void buildStarted(BuildEvent buildEvent) {
162         // do nothing
163     }
164 
buildFinished(BuildEvent buildEvent)165     public void buildFinished(BuildEvent buildEvent) {
166         // do nothing
167     }
168 
targetStarted(BuildEvent buildEvent)169     public void targetStarted(BuildEvent buildEvent) {
170         String targetName = buildEvent.getTarget().getName();
171         String eventName = GrailsNameUtils.getClassNameRepresentation(targetName) + "Start";
172         triggerEvent(eventName, binding);
173     }
174 
175     /**
176      * Triggers and event for the given name and binding
177      * @param eventName The name of the event
178      */
triggerEvent(String eventName)179     public void triggerEvent(String eventName) {
180         triggerEvent(eventName, binding);
181     }
182 
183     /**
184      * Triggers an event for the given name and arguments
185      * @param eventName The name of the event
186      * @param arguments The arguments
187      */
triggerEvent(String eventName, Object... arguments)188     public void triggerEvent(String eventName, Object... arguments) {
189         List<Closure> handlers = globalEventHooks.get(eventName);
190         if (handlers != null) {
191             for (Closure handler : handlers) {
192                 handler.setDelegate(binding);
193                 try {
194                     handler.call(arguments);
195                 }
196                 catch (MissingPropertyException mpe) {
197                     // ignore
198                 }
199             }
200         }
201 
202         for (GrailsBuildListener buildListener : buildListeners) {
203             buildListener.receiveGrailsBuildEvent(eventName, arguments);
204         }
205     }
206 
targetFinished(BuildEvent buildEvent)207     public void targetFinished(BuildEvent buildEvent) {
208         String targetName = buildEvent.getTarget().getName();
209         String eventName = GrailsNameUtils.getClassNameRepresentation(targetName) + "End";
210         triggerEvent(eventName, binding);
211     }
212 
taskStarted(BuildEvent buildEvent)213     public void taskStarted(BuildEvent buildEvent) {
214         // do nothing
215     }
216 
taskFinished(BuildEvent buildEvent)217     public void taskFinished(BuildEvent buildEvent) {
218         // do nothing
219     }
220 
messageLogged(BuildEvent buildEvent)221     public void messageLogged(BuildEvent buildEvent) {
222         // do nothing
223     }
224 
addGrailsBuildListener(String listenerClassName)225     protected void addGrailsBuildListener(String listenerClassName) {
226         Class<?> listenerClass;
227         try {
228             listenerClass = classLoader.loadClass(listenerClassName);
229         }
230         catch (ClassNotFoundException e) {
231             throw new RuntimeException("Could not load grails build listener class", e);
232         }
233         addGrailsBuildListener(listenerClass);
234     }
235 
236     @SuppressWarnings("rawtypes")
addGrailsBuildListener(Class listenerClass)237     protected void addGrailsBuildListener(Class listenerClass) {
238         if (!GrailsBuildListener.class.isAssignableFrom(listenerClass)) {
239             throw new RuntimeException("Intended grails build listener class of " + listenerClass.getName() + " does not implement " + GrailsBuildListener.class.getName());
240         }
241 
242         try {
243             GrailsBuildListener listener = (GrailsBuildListener)listenerClass.newInstance();
244             addGrailsBuildListener(listener);
245         }
246         catch (Exception e) {
247             throw new RuntimeException("Could not instantiate " + listenerClass.getName(), e);
248         }
249     }
250 
addGrailsBuildListener(GrailsBuildListener listener)251     public void addGrailsBuildListener(GrailsBuildListener listener) {
252         buildListeners.add(listener);
253     }
254 }
255