1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package nl.toolforge.karma.core.cmd;
20
21 import nl.toolforge.core.util.listener.ChangeListener;
22 import nl.toolforge.core.util.listener.ListenerManager;
23 import nl.toolforge.core.util.listener.ListenerManagerException;
24 import nl.toolforge.karma.core.KarmaRuntimeException;
25 import nl.toolforge.karma.core.boot.WorkingContext;
26 import nl.toolforge.karma.core.boot.WorkingContextConfiguration;
27 import nl.toolforge.karma.core.cmd.event.CommandFailedEvent;
28 import nl.toolforge.karma.core.cmd.event.CommandFinishedEvent;
29 import nl.toolforge.karma.core.cmd.event.CommandResponseListener;
30 import nl.toolforge.karma.core.cmd.event.CommandStartedEvent;
31 import nl.toolforge.karma.core.cmd.event.ErrorEvent;
32 import nl.toolforge.karma.core.cmd.event.MessageEvent;
33 import nl.toolforge.karma.core.cmd.event.SimpleMessage;
34 import nl.toolforge.karma.core.location.LocationException;
35 import nl.toolforge.karma.core.manifest.Manifest;
36 import nl.toolforge.karma.core.manifest.ManifestException;
37 import nl.toolforge.karma.core.manifest.ManifestFactory;
38 import nl.toolforge.karma.core.manifest.ManifestLoader;
39 import nl.toolforge.karma.core.manifest.ManifestStructure;
40 import nl.toolforge.karma.core.module.Module;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.Map;
50
51 /***
52 * <p>The command context is the class that provides a runtime for commands to run in. The command context maintains
53 * access to the current manifest and all commands that are valid. A <code>CommandContext</code> must be initialized
54 * through its {@link #init} method so it can initialize all resources it requires to properly run commands. The
55 * <code>init</code> method can only be run once.
56 *
57 * @author D.A. Smedes
58 * @version $Id: CommandContext.java,v 1.83 2004/11/16 22:31:57 asmedes Exp $
59 */
60 public final class CommandContext implements ChangeListener {
61
62 private static final Log logger = LogFactory.getLog(CommandContext.class);
63
64 private Manifest currentManifest;
65 private Map modificationMap = new HashMap();
66 private boolean managed = false;
67
68 private static ListenerManager manager;
69
70 private CommandResponseHandler handler = null;
71 private WorkingContext workingContext = null;
72 private CommandResponse commandResponse = null;
73
74 /***
75 * Constructs a <code>CommandContext</code>, in which commands are run.
76 */
77 public CommandContext(WorkingContext workingContext) {
78 this.workingContext = workingContext;
79 }
80
81 public WorkingContext getWorkingContext() {
82 return workingContext;
83 }
84
85 /***
86 * Initializes the context to run commands. This method should only be called once on a <code>CommandContext</code>.
87 *
88 * @param handler The {@link CommandResponseHandler} object that will be passed to all commands run through this
89 * context.
90 * @param updateStores If this paramter is true, the CommandContext will update the local manifest and location store
91 * with the latest manifests and locations.
92 */
93 public synchronized void init(CommandResponseHandler handler, boolean updateStores) throws CommandException {
94
95 if (handler == null) {
96 throw new IllegalArgumentException("CommandResponseHandler may not be null.");
97 }
98
99
100
101 commandResponse = new CommandResponse();
102 commandResponse.addCommandResponseListener(handler);
103 setHandler(handler);
104
105
106
107 Command command = new KarmaInitializationCommand(updateStores);
108
109 command.setContext(this);
110 command.registerCommandResponseListener(getHandler());
111
112 CommandStartedEvent startEvent = new CommandStartedEvent(command);
113
114
115 try {
116 command.execute();
117 } catch (CommandException c) {
118 commandResponse.addEvent(new ErrorEvent(command, c.getErrorCode(), c.getMessageArguments()));
119
120 throw c;
121 }
122
123 command.deregisterCommandResponseListener(handler);
124 command.cleanUp();
125 }
126
127
128 private synchronized void setFileModificationTimes() {
129
130 Manifest manifest = currentManifest;
131
132 WorkingContextConfiguration config = workingContext.getConfiguration();
133
134 Long lastMod = new Long(new File(config.getManifestStore().getModule().getBaseDir(), manifest.getName() + ".xml").lastModified());
135 modificationMap.put(manifest, lastMod);
136
137 try {
138 Collection includes = manifest.getIncludes();
139 for (Iterator i = includes.iterator(); i.hasNext();) {
140
141 manifest = (Manifest) i.next();
142
143 lastMod = new Long(new File(config.getManifestStore().getModule().getBaseDir(), manifest.getName() + ".xml").lastModified());
144 modificationMap.put(manifest, lastMod);
145 }
146 } catch (Exception e) {
147 logger.error(e);
148 modificationMap.clear();
149 }
150 }
151
152 /***
153 * Implementation of the {@link ChangeListener} interface. This method reloads the
154 * current manifest to allow changes to be reflected without having to restart Karma.
155 */
156 public synchronized void process() {
157
158 boolean reload = false;
159
160 try {
161
162 Collection manifests = new ArrayList();
163 manifests.add(currentManifest);
164 manifests.addAll(currentManifest.getIncludes());
165
166 for (Iterator i = manifests.iterator(); i.hasNext();) {
167
168 Manifest m = (Manifest) i.next();
169 long lastMod = ((Long) modificationMap.get(m)).longValue();
170
171 WorkingContextConfiguration config = workingContext.getConfiguration();
172
173
174 File f = new File(config.getManifestStore().getModule().getBaseDir(), m.getName() + ".xml");
175 if (!f.exists()) {
176 currentManifest = null;
177 throw new ManifestException(ManifestException.MANIFEST_FILE_NOT_FOUND, new Object[] {m.getName()});
178 }
179
180 if (f.lastModified() > lastMod) {
181 reload = true;
182 break;
183 }
184 }
185
186 if (reload) {
187
188
189
190 ManifestStructure reloadedStructure =
191 getWorkingContext().getManifestLoader().load(currentManifest.getName());
192 currentManifest = new ManifestFactory().create(workingContext, reloadedStructure);
193
194 setFileModificationTimes();
195
196 String message = "\nManifest " + getCurrentManifest().getName() + " has changed on disk. Reloaded automatically.\n";
197 logger.info(message);
198
199 commandResponse.addEvent(new MessageEvent(new SimpleMessage(message)));
200
201 return;
202 }
203
204 } catch (ManifestException m) {
205
206
207
208 managed = false;
209 manager.suspendListener(this);
210
211 logger.error(m);
212
213
214
215 throw new KarmaRuntimeException(m.getErrorCode(), m.getMessageArguments());
216 } catch (Exception e) {
217
218 managed = false;
219 manager.suspendListener(this);
220
221 logger.error("Error while processing manifests during automatic reload; " + e.getMessage(), e);
222 }
223 }
224
225 /***
226 * Gets the currently active manifest.
227 *
228 * @return The currently active manifest, or <code>null</code> when no manifest is current.
229 */
230 public Manifest getCurrentManifest() {
231 return currentManifest;
232 }
233
234 /***
235 * Changes the current manifest for this context. This method loads the manifest with the <code>manifestName</code>
236 * name.
237 *
238 * @param manifestName
239 * @throws ManifestException When the manifest could not be changed. See {@link ManifestException#MANIFEST_LOAD_ERROR}.
240 */
241 public void changeCurrentManifest(String manifestName) throws ManifestException, LocationException {
242
243 ManifestFactory manifestFactory = new ManifestFactory();
244 ManifestLoader loader = new ManifestLoader(workingContext);
245 Manifest newManifest = manifestFactory.create(workingContext, loader.load(manifestName));
246
247
248
249 currentManifest = newManifest;
250
251 register();
252 }
253
254 /***
255 * Changes the current manifest for this context. This method assumes a loaded manifest.
256 *
257 * @param newManifest
258 */
259 public void changeCurrentManifest(Manifest newManifest) {
260 currentManifest = newManifest;
261
262 if (currentManifest != null) {
263 register();
264 }
265 }
266
267 /***
268 * Registers this <code>CommandContext</code> for automatic manifest file update changes.
269 */
270 synchronized void register() {
271
272 setFileModificationTimes();
273
274 if (!managed) {
275
276 manager = ListenerManager.getInstance();
277 try {
278 manager.register(this);
279 } catch (ListenerManagerException e) {
280 logger.error(e);
281 throw new KarmaRuntimeException(e.getMessage());
282 }
283
284 manager.start();
285
286 managed = true;
287 }
288 }
289
290 /***
291 * Gets all manifests.
292 *
293 * @return See <code>ManifestLoader.getAllManifests()</code>.
294 */
295 public Collection getAllManifests() {
296 return workingContext.getManifestCollector().getAllManifests();
297 }
298
299 /***
300 * <p>Executes a command. Interface applications should use this method to actually execute a command. When a
301 * <code>KarmaException</code> is thrown an interface applications should <b>*** NOT ***</b> quit program execution as
302 * a result of this exception. It should be handled nicely.
303 *
304 * @param commandLine The command to execute. A full command line is passed as a parameter.
305 * @throws CommandException A whole lot. Interface applications should <b>*** NOT ***</b> quit program execution as a
306 * result of this exception. It should be handled nicely.
307 */
308 public void execute(String commandLine) throws CommandException {
309
310 Command command = null;
311 try {
312 command = CommandFactory.getInstance().getCommand(commandLine);
313 } catch (CommandException c) {
314 logger.error(c.getMessage());
315 commandResponse.addEvent(new ErrorEvent(c.getErrorCode(), c.getMessageArguments()));
316 throw c;
317 } catch (CommandLoadException e) {
318 logger.error(e.getMessage());
319 throw new CommandException(e, e.getErrorCode(), e.getMessageArguments());
320 }
321 execute(command);
322 }
323
324 /***
325 * Exceutes <code>command</code>.
326 *
327 * @param command The command to execute.
328 * @throws CommandException
329 */
330 public void execute(Command command) throws CommandException {
331
332 if (command == null) {
333 throw new IllegalArgumentException("Invalid command; command cannot be null.");
334 }
335
336
337
338 command.setContext(this);
339 command.registerCommandResponseListener(getHandler());
340
341
342
343
344 CommandStartedEvent startEvent = new CommandStartedEvent(command);
345 commandResponse.addEvent(startEvent);
346
347 try {
348 command.execute();
349 } catch (CommandException c) {
350 logger.error(c.getMessage());
351 commandResponse.addEvent(new ErrorEvent(command, c.getErrorCode(), c.getMessageArguments()));
352 commandResponse.addEvent(new CommandFailedEvent(command, c));
353 throw c;
354 }
355 commandResponse.addEvent(new CommandFinishedEvent(command, startEvent.getTime()));
356
357 command.deregisterCommandResponseListener(getHandler());
358 command.cleanUp();
359 }
360
361 private void setHandler(CommandResponseHandler handler) {
362 this.handler = handler;
363 }
364
365 private CommandResponseListener getHandler() {
366 return handler;
367 }
368
369 /***
370 * Checks if a manifest is active for this context.
371 *
372 * @return <code>true</code> if a manifest is active for the context, or <code>false</code> if no manifest is active.
373 */
374 public boolean isManifestLoaded() {
375 return currentManifest != null;
376 }
377
378 /***
379 * <p>Some module-types (e.g. source modules) have a physical location on disk where the module can be located. This
380 * method returns a valid reference to that location. When the module-root is located at
381 * <code>/home/jensen/dev/modules/CORE-conversion</code>, <code>getLocalPath()</code> will return a <code>File</code>
382 * handle to that directory.
383 *
384 * @param module The module for which the local path should be retrieved.
385 *
386 * @return A <code>File</code> handle to the module directory on a local disk.
387 *
388 * todo consider moving it to Module.
389 */
390 public File getLocalPath(Module module) {
391
392 File localPath = new File(getBase(), module.getName());
393 logger.debug("getLocalPath() = " + localPath.getPath());
394
395 return localPath;
396 }
397
398 /***
399 * Helper to get the module base for the current manifest.
400 */
401 private File getBase() {
402 return new File(workingContext.getProjectBaseDirectory(), getCurrentManifest().getName());
403 }
404
405 /***
406 * Sets the workingContext for this command context.
407 *
408 * @param workingContext
409 */
410 public void setWorkingContext(WorkingContext workingContext) {
411 this.workingContext = workingContext;
412 }
413 }