SerialAnalyserController.java
001 /*
002  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Valentin Tablan 08/10/2001
011  *
012  *  $Id: SerialAnalyserController.java 18563 2015-02-09 16:38:02Z johann_p $
013  *
014  */
015 
016 package gate.creole;
017 
018 import gate.Controller;
019 import gate.CorpusController;
020 import gate.Document;
021 import gate.Factory;
022 import gate.Gate;
023 import gate.LanguageAnalyser;
024 import gate.ProcessingResource;
025 import gate.creole.metadata.CreoleParameter;
026 import gate.creole.metadata.CreoleResource;
027 import gate.creole.metadata.Optional;
028 import gate.creole.metadata.RunTime;
029 import gate.event.CreoleEvent;
030 import gate.util.Benchmark;
031 import gate.util.GateRuntimeException;
032 import gate.util.Out;
033 
034 import java.lang.reflect.UndeclaredThrowableException;
035 import java.util.ArrayList;
036 import java.util.Iterator;
037 import java.util.List;
038 
039 /**
040  * This class implements a SerialController that only contains
041  {@link gate.LanguageAnalyser}s. It has a {@link gate.Corpus} and its execute
042  * method runs all the analysers in turn over each of the documents in the
043  * corpus.
044  <p>
045  * NOTE: if at the time when execute() is invoked, the document is not null,
046  * it is assumed that this controller is invoked from another controller and
047  * only this document is processed while the corpus (which must still be
048  * non-null) is ignored. Also, if the document is not null, the CorpusAwarePRs
049  * are not notified at the beginning, end, or abnormal termination of the pipeline. 
050  <p>
051  * If the document is null, all documents in the corpus
052  * are processed in sequence and CorpusAwarePRs are notified
053  * before the processing of the documents and after all documents
054  * have been processed or an abnormal termination occurred.
055  
056  */
057 @CreoleResource(name = "Corpus Pipeline",
058     comment = "A serial controller for PR pipelines over corpora.",
059     helpURL = "http://gate.ac.uk/userguide/sec:developer:apps")
060 public class SerialAnalyserController extends SerialController 
061        implements CorpusController, LanguageAnalyser, ControllerAwarePR {
062 
063   private static final long serialVersionUID = -7736138658476400031L;
064 
065 
066   /** Debug flag */
067   private static final boolean DEBUG = false;
068 
069   /**
070    @return the document
071    */
072   @Override
073   public Document getDocument() {
074     return document;
075   }
076 
077   protected boolean runningAsSubPipeline = false;
078   
079   
080   /**
081    @param document the document to set
082    */
083   @Override
084   @Optional
085   @RunTime
086   @CreoleParameter
087   public void setDocument(Document document) {
088     this.document = document;
089   }
090 
091   @Override
092   public gate.Corpus getCorpus() {
093     return corpus;
094   }
095 
096   @Override
097   public void setCorpus(gate.Corpus corpus) {
098     this.corpus = corpus;
099   }
100   
101   @Override
102   public void execute() throws ExecutionException {
103 
104     // Our assumption of if we run as a subpipeline of another corpus pipeline or
105     // not is based on whether or not the document is null or not:
106     if(document != null) {
107       runningAsSubPipeline = true;
108     else {
109       runningAsSubPipeline = false;
110     }
111     // inform ControllerAware PRs that execution has started, but only if we are not
112     // running as a subpipeline of another corpus pipeline.
113     if(!runningAsSubPipeline) {
114       if(controllerCallbacksEnabled) {
115         invokeControllerExecutionStarted();
116       }
117     }
118     Throwable thrown = null;
119     try {
120       if(Benchmark.isBenchmarkingEnabled()) {
121         // write a start marker to the benchmark log for this
122         // controller as a whole
123         Benchmark.startPoint(getBenchmarkId());
124       }
125       // do the real work
126       this.executeImpl();
127     }
128     catch(Throwable t) {
129       thrown = t;
130     }
131     finally {
132       if(thrown == null) {
133         // successfully completed
134         if(!runningAsSubPipeline) {
135           if(controllerCallbacksEnabled) {
136             invokeControllerExecutionFinished();
137           }
138         }
139       }
140       else {
141         // aborted
142         if(!runningAsSubPipeline) {
143           if(controllerCallbacksEnabled) {
144             invokeControllerExecutionAborted(thrown);
145           }
146         
147         // rethrow the aborting exception or error
148         if(thrown instanceof Error) {
149           throw (Error)thrown;
150         }
151         else if(thrown instanceof RuntimeException) {
152           throw (RuntimeException)thrown;
153         }
154         else if(thrown instanceof ExecutionException) {
155           throw (ExecutionException)thrown;
156         }
157         else {
158           // we have a checked exception that isn't one executeImpl can
159           // throw. This shouldn't be possible, but just in case...
160           throw new UndeclaredThrowableException(thrown);
161         }
162       }
163     }
164   }
165   
166   
167   
168 
169   /** Run the Processing Resources in sequence. */
170   @Override
171   protected void executeImpl() throws ExecutionException {
172     interrupted = false;
173     if(corpus == null)
174       throw new ExecutionException("(SerialAnalyserController) \"" + getName()
175         "\":\n" "The corpus supplied for execution was null!");
176 
177     benchmarkFeatures.put(Benchmark.CORPUS_NAME_FEATURE, corpus.getName());
178 
179     // reset the prTimeMap that keeps track of the time
180     // taken by each PR to process the entire corpus
181     super.resetPrTimeMap();
182     
183     if(document == null){
184       //running as a top-level controller -> execute over all documents in 
185       //sequence
186       // iterate through the documents in the corpus
187       for(int i = 0; i < corpus.size(); i++) {
188         String savedBenchmarkId = getBenchmarkId();
189         try {
190           if(isInterrupted()) {
191             throw new ExecutionInterruptedException("The execution of the "
192               + getName() " application has been abruptly interrupted!");
193           }
194   
195           boolean docWasLoaded = corpus.isDocumentLoaded(i);
196   
197           // record the time before loading the document
198           long documentLoadingStartTime = Benchmark.startPoint();
199   
200           Document doc = corpus.get(i);
201   
202           // include the document name in the benchmark ID for sub-events
203           setBenchmarkId(Benchmark.createBenchmarkId("doc_" + doc.getName(),
204                   getBenchmarkId()));
205           // report the document loading
206           benchmarkFeatures.put(Benchmark.DOCUMENT_NAME_FEATURE, doc.getName());
207           Benchmark.checkPoint(documentLoadingStartTime,
208                   Benchmark.createBenchmarkId(Benchmark.DOCUMENT_LOADED,
209                           getBenchmarkId()), this, benchmarkFeatures);
210   
211           // run the system over this document
212           // set the doc and corpus
213           for(int j = 0; j < prList.size(); j++) {
214             ((LanguageAnalyser)prList.get(j)).setDocument(doc);
215             ((LanguageAnalyser)prList.get(j)).setCorpus(corpus);
216           }
217   
218           try {
219             if(DEBUG)
220               Out.pr("SerialAnalyserController processing doc=" + doc.getName()
221                 "...");
222   
223             super.executeImpl();
224             if(DEBUGOut.prln("done.");
225           }
226           finally {
227             // make sure we unset the doc and corpus even if we got an exception
228             for(int j = 0; j < prList.size(); j++) {
229               ((LanguageAnalyser)prList.get(j)).setDocument(null);
230               ((LanguageAnalyser)prList.get(j)).setCorpus(null);
231             }
232           }
233   
234           if(!docWasLoaded) {
235             long documentSavingStartTime = Benchmark.startPoint();
236             // trigger saving
237             corpus.unloadDocument(doc);
238             Benchmark.checkPoint(documentSavingStartTime,
239                     Benchmark.createBenchmarkId(Benchmark.DOCUMENT_SAVED,
240                             getBenchmarkId()), this, benchmarkFeatures);
241             
242             // close the previoulsy unloaded Doc
243             Factory.deleteResource(doc);
244           }
245         }
246         finally {
247           setBenchmarkId(savedBenchmarkId);
248         }
249       }      
250     }else{
251       //document is set, so we run as a contained controller (i.e. as a compound
252       //Language Analyser
253       // run the system over this document
254       // set the doc and corpus
255       for(int j = 0; j < prList.size(); j++) {
256         ((LanguageAnalyser)prList.get(j)).setDocument(document);
257         ((LanguageAnalyser)prList.get(j)).setCorpus(corpus);
258       }
259 
260       try {
261         if(DEBUG)
262           Out.pr("SerialAnalyserController processing doc=" + document.getName()
263             "...");
264 
265         super.executeImpl();
266         if(DEBUGOut.prln("done.");
267       }
268       finally {
269         // make sure we unset the doc and corpus even if we got an exception
270         for(int j = 0; j < prList.size(); j++) {
271           ((LanguageAnalyser)prList.get(j)).setDocument(null);
272           ((LanguageAnalyser)prList.get(j)).setCorpus(null);
273         }
274       }
275     }//document was not null
276     
277 
278 
279     // remove the features that we added
280     benchmarkFeatures.remove(Benchmark.DOCUMENT_NAME_FEATURE);
281     benchmarkFeatures.remove(Benchmark.CORPUS_NAME_FEATURE);
282   }
283 
284   /**
285    * Overidden from {@link SerialController} to only allow
286    {@link LanguageAnalyser}s as components.
287    */
288   @Override
289   public void add(ProcessingResource pr){
290     checkLanguageAnalyser(pr);
291     super.add(pr);
292   }
293   
294   /**
295    * Overidden from {@link SerialController} to only allow
296    {@link LanguageAnalyser}s as components.
297    */
298   @Override
299   public void add(int index, ProcessingResource pr) {
300     checkLanguageAnalyser(pr);
301     super.add(index, pr);
302   }
303 
304   /**
305    * Throw an exception if the given processing resource is not
306    * a LanguageAnalyser.
307    */
308   protected void checkLanguageAnalyser(ProcessingResource pr) {
309     if(!(pr instanceof LanguageAnalyser)) {
310       throw new GateRuntimeException(getClass().getName() +
311                                      " only accepts " +
312                                      LanguageAnalyser.class.getName() +
313                                      "s as components\n" +
314                                      pr.getClass().getName() +
315                                      " is not!");
316     }
317   }
318 
319   /**
320    * Sets the current document to the memeber PRs
321    */
322   protected void setDocToPrs(Document doc) {
323     Iterator<ProcessingResource> prIter = getPRs().iterator();
324     while(prIter.hasNext()) {
325       ProcessingResource pr = prIter.next();
326       
327       // Not all PRs are LRs so we need to check carefully before
328       // assuming they are
329       if (pr instanceof LanguageAnalyser)
330         ((LanguageAnalyser)pr).setDocument(doc);
331     }
332   }
333 
334   /**
335    * Checks whether all the contained PRs have all the required runtime
336    * parameters set. Ignores the corpus and document parameters as these will be
337    * set at run time.
338    
339    @return {@link List} of {@link ProcessingResource}s that have required
340    *         parameters with null values if they exist <tt>null</tt>
341    *         otherwise.
342    @throws {@link ResourceInstantiationException}
343    *           if problems occur while inspecting the parameters for one of the
344    *           resources. These will normally be introspection problems and are
345    *           usually caused by the lack of a parameter or of the read accessor
346    *           for a parameter.
347    */
348   @Override
349   public List<ProcessingResource> getOffendingPocessingResources()
350     throws ResourceInstantiationException {
351     // take all the contained PRs
352     List<ProcessingResource> badPRs = new ArrayList<ProcessingResource>(getPRs());
353     // remove the ones that no parameters problems
354     Iterator<ProcessingResource> prIter = getPRs().iterator();
355     while(prIter.hasNext()) {
356       ProcessingResource pr = prIter.next();
357       ResourceData rData =
358         Gate.getCreoleRegister().get(pr.getClass().getName());
359       // this is a list of lists
360       List<List<Parameter>> parameters = rData.getParameterList().getRuntimeParameters();
361       // remove corpus and document
362       List<List<Parameter>> newParameters = new ArrayList<List<Parameter>>();
363       Iterator<List<Parameter>> pDisjIter = parameters.iterator();
364       while(pDisjIter.hasNext()) {
365         List<Parameter> aDisjunction = pDisjIter.next();
366         List<Parameter> newDisjunction = new ArrayList<Parameter>(aDisjunction);
367         Iterator<Parameter> internalParIter = newDisjunction.iterator();
368         while(internalParIter.hasNext()) {
369           Parameter parameter = internalParIter.next();
370           if(parameter.getName().equals("corpus")
371             || parameter.getName().equals("document"))
372             internalParIter.remove();
373         }
374         if(!newDisjunction.isEmpty()) newParameters.add(newDisjunction);
375       }
376 
377       if(AbstractResource.checkParameterValues(pr, newParameters)) {
378         badPRs.remove(pr);
379       }
380     }
381     return badPRs.isEmpty() null : badPRs;
382   }
383 
384   
385   /**
386    * The corpus being processed by this controller.
387    */
388   protected gate.Corpus corpus;
389 
390   
391   /**
392    * The document being processed. This is part of the {@link LanguageAnalyser} 
393    * interface, so this value is only used when the controller is used as a 
394    * member of another controller.
395    */
396   protected Document document;
397   
398   
399   /**
400    * Overridden to also clean up the corpus value.
401    */
402   @Override
403   public void resourceUnloaded(CreoleEvent e) {
404     super.resourceUnloaded(e);
405     if(e.getResource() == corpus) {
406       setCorpus(null);
407     }
408   }
409 
410   @Override
411   public void controllerExecutionStarted(Controller c)
412       throws ExecutionException {
413     for(ControllerAwarePR pr : getControllerAwarePRs()) {
414       if(pr instanceof LanguageAnalyser) {
415         ((LanguageAnalyser)pr).setCorpus(corpus);
416       }
417       pr.controllerExecutionStarted(this);
418     }
419   }
420 
421   @Override
422   public void controllerExecutionFinished(Controller c)
423       throws ExecutionException {
424     for(ControllerAwarePR pr : getControllerAwarePRs()) {
425       if(pr instanceof LanguageAnalyser) {
426         ((LanguageAnalyser)pr).setCorpus(corpus);
427       }
428       pr.controllerExecutionFinished(this);
429       if(pr instanceof LanguageAnalyser) {
430         ((LanguageAnalyser)pr).setCorpus(null);
431       }
432     }    
433   }
434 
435   @Override
436   public void controllerExecutionAborted(Controller c, Throwable t)
437       throws ExecutionException {
438     for(ControllerAwarePR pr : getControllerAwarePRs()) {
439       if(pr instanceof LanguageAnalyser) {
440         ((LanguageAnalyser)pr).setCorpus(corpus);
441       }
442       pr.controllerExecutionAborted(c, t);
443       if(pr instanceof LanguageAnalyser) {
444         ((LanguageAnalyser)pr).setCorpus(null);
445       }
446     }    
447   }
448   
449    /**
450    * Invoke the controllerExecutionStarted method on this controller and all nested PRs and controllers. 
451    * This method is intended to be used after if the automatic invocation of the controller
452    * callback methods has been disabled with a call setControllerCallbackEnabled(false).  Normally
453    * the callback methods are automatically invoked at the start and end of execute().  
454    @throws ExecutionException 
455    */
456   @Override
457   public void invokeControllerExecutionStarted() throws ExecutionException {
458     for (ControllerAwarePR pr : getControllerAwarePRs()) {
459       // If the pr is a nested corpus controller, make sure its corpus is set 
460       // This is necessary because the nested corpus controller will immediately 
461       // notify its own controller aware PRs and those should be able to know about 
462       // the corpus.
463       if (pr instanceof LanguageAnalyser) {
464         ((LanguageAnalyserpr).setCorpus(getCorpus());
465       }
466       pr.controllerExecutionStarted(this);
467     }
468   }
469 
470    /**
471    * Invoke the controllerExecutionFinished method on this controller and all nested PRs and controllers. 
472    * This method is intended to be used after if the automatic invocation of the controller
473    * callback methods has been disabled with a call setControllerCallbackEnabled(false).  Normally
474    * the callback methods are automatically invoked at the start and end of execute().  
475    @throws ExecutionException 
476    */
477   @Override
478   public void invokeControllerExecutionFinished() throws ExecutionException {
479     for (ControllerAwarePR pr : getControllerAwarePRs()) {
480       if (pr instanceof LanguageAnalyser) {
481         ((LanguageAnalyserpr).setCorpus(getCorpus());
482       }
483       pr.controllerExecutionFinished(this);
484       if (pr instanceof LanguageAnalyser) {
485         ((LanguageAnalyserpr).setCorpus(null);
486       }
487     }
488   }
489   
490    /**
491    * Invoke the controllerExecutionAborted method on this controller and all nested PRs and controllers. 
492    * This method is intended to be used after if the automatic invocation of the controller
493    * callback methods has been disabled with a call setControllerCallbackEnabled(false).  Normally
494    * the callback methods are automatically invoked at the start and end of execute().  
495    @param thrown The Throwable to pass on to the controller callback method.
496    @throws ExecutionException 
497    */
498   @Override
499   public void invokeControllerExecutionAborted(Throwable thrownthrows ExecutionException {
500     for (ControllerAwarePR pr : getControllerAwarePRs()) {
501       if (pr instanceof LanguageAnalyser) {
502         ((LanguageAnalyserpr).setCorpus(getCorpus());
503       }
504       pr.controllerExecutionAborted(this, thrown);
505       if (pr instanceof LanguageAnalyser) {
506         ((LanguageAnalyserpr).setCorpus(null);
507       }
508     }
509   }
510   
511   
512   
513   
514 }