1   /*
2    *  Factory.java
3    *
4    *  Copyright (c) 1998-2005, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Hamish Cunningham, 25/May/2000
12   *
13   *  $Id: Factory.java,v 1.82 2005/06/21 14:09:35 valyt Exp $
14   */
15  
16  package gate;
17  
18  import java.io.BufferedInputStream;
19  import java.io.IOException;
20  import java.io.InputStreamReader;
21  import java.io.Serializable;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.InvocationTargetException;
24  import java.net.URL;
25  import java.util.*;
26  
27  import gate.creole.*;
28  import gate.event.CreoleEvent;
29  import gate.event.CreoleListener;
30  import gate.jape.parser.ParseCpsl;
31  import gate.persist.PersistenceException;
32  import gate.persist.SerialDataStore;
33  import gate.security.*;
34  import gate.security.SecurityException;
35  import gate.util.*;
36  
37  /** Provides static methods for the creation of Resources.
38    */
39  public abstract class Factory {
40    /** Debug flag */
41    private static final boolean DEBUG = false;
42  
43    /** The CREOLE register */
44    private static CreoleRegister reg = Gate.getCreoleRegister();
45  
46    /** The DataStore register */
47    private static DataStoreRegister dsReg = Gate.getDataStoreRegister();
48  
49    /** An object to source events from. */
50    private static CreoleProxy creoleProxy;
51  
52    /** An object to source events from. */
53    private static HashMap accessControllerPool;
54  
55    /** Create an instance of a resource using default parameter values.
56      * @see #createResource(String,FeatureMap)
57      */
58    public static Resource createResource(String resourceClassName)
59    throws ResourceInstantiationException
60    {
61      // get the resource metadata
62      ResourceData resData = (ResourceData) reg.get(resourceClassName);
63      if(resData == null)
64        throw new ResourceInstantiationException(
65          "Couldn't get resource data for " + resourceClassName
66        );
67  
68      // get the parameter list and default values
69      ParameterList paramList = resData.getParameterList();
70      FeatureMap parameterValues = null;
71      try {
72        parameterValues = paramList.getInitimeDefaults();
73      } catch(ParameterException e) {
74        throw new ResourceInstantiationException(
75          "Couldn't get default parameters for " + resourceClassName + ": " + e
76        );
77      }
78  
79      return createResource(resourceClassName, parameterValues);
80    } // createResource(resClassName)
81  
82    /** Create an instance of a resource, and return it.
83      * Callers of this method are responsible for
84      * querying the resource's parameter lists, putting together a set that
85      * is complete apart from runtime parameters, and passing a feature map
86      * containing these parameter settings.
87      *
88      * @param resourceClassName the name of the class implementing the resource.
89      * @param parameterValues the feature map containing intialisation time
90      *   parameterValues for the resource.
91      * @return an instantiated resource.
92      */
93    public static Resource createResource(
94      String resourceClassName, FeatureMap parameterValues
95    ) throws ResourceInstantiationException
96    {
97      return createResource(resourceClassName, parameterValues, null, null);
98    } // createResource(resClassName, paramVals, listeners)
99  
100   /** Create an instance of a resource, and return it.
101     * Callers of this method are responsible for
102     * querying the resource's parameter lists, putting together a set that
103     * is complete apart from runtime parameters, and passing a feature map
104     * containing these parameter settings.
105     *
106     * @param resourceClassName the name of the class implementing the resource.
107     * @param parameterValues the feature map containing intialisation time
108     *   parameterValues for the resource.
109     * @param features the features for the new resource
110     * @return an instantiated resource.
111     */
112   public static Resource createResource(
113     String resourceClassName, FeatureMap parameterValues,
114     FeatureMap features
115     ) throws ResourceInstantiationException
116    {
117       return createResource(resourceClassName, parameterValues,
118                             features, null);
119    }
120 
121   /** Create an instance of a resource, and return it.
122     * Callers of this method are responsible for
123     * querying the resource's parameter lists, putting together a set that
124     * is complete apart from runtime parameters, and passing a feature map
125     * containing these parameter settings.
126     *
127     * In the case of ProcessingResources they will have their runtime parameters
128     * initialised to their default values.
129     *
130     * @param resourceClassName the name of the class implementing the resource.
131     * @param parameterValues the feature map containing intialisation time
132     *   parameterValues for the resource.
133     * @param features the features for the new resource
134     * @return an instantiated resource.
135     */
136   public static Resource createResource(
137     String resourceClassName, FeatureMap parameterValues,
138     FeatureMap features, String resourceName
139   ) throws ResourceInstantiationException
140    {
141     // get the resource metadata
142     ResourceData resData = (ResourceData) reg.get(resourceClassName);
143     if(resData == null)
144       throw new ResourceInstantiationException(
145         "Couldn't get resource data for " + resourceClassName
146       );
147     // get the default implementation class
148     Class resClass = null;
149     try {
150       resClass = resData.getResourceClass();
151     } catch(ClassNotFoundException e) {
152       throw new ResourceInstantiationException(
153         "Couldn't get resource class from the resource data:"+Strings.getNl()+e
154       );
155     }
156 
157     //create a pointer for the resource
158     Resource res = null;
159 
160     //if the object is an LR and it should come from a DS then create that way
161     DataStore dataStore;
162     if(LanguageResource.class.isAssignableFrom(resClass) &&
163        ((dataStore = (DataStore)parameterValues.
164                      get(DataStore.DATASTORE_FEATURE_NAME)) != null)
165       ){
166       //ask the datastore to create our object
167       if(dataStore instanceof SerialDataStore) {
168         // SDS doesn't need a wrapper class; just check for serialisability
169         if(! Serializable.class.isAssignableFrom(resClass))
170           throw new ResourceInstantiationException(
171             "Resource cannot be (de-)serialized: " + resClass.getName()
172           );
173       }
174 
175       // get the datastore instance id and retrieve the resource
176       Object instanceId = parameterValues.get(DataStore.LR_ID_FEATURE_NAME);
177       if(instanceId == null)
178         throw new
179           ResourceInstantiationException("No instance id for " + resClass);
180       try {
181         res = dataStore.getLr(resClass.getName(), instanceId);
182       } catch(PersistenceException pe) {
183         throw new ResourceInstantiationException("Bad read from DB: " + pe);
184       } catch(SecurityException se) {
185         throw new ResourceInstantiationException("Insufficient permissions: " + se);
186       }
187       resData.addInstantiation(res);
188       if(features != null){
189         if(res.getFeatures() == null){
190           res.setFeatures(newFeatureMap());
191         }
192         res.getFeatures().putAll(features);
193       }
194 
195       //set the name
196       if(res.getName() == null){
197         res.setName(resourceName == null ?
198                     resData.getName() + "_" + Gate.genSym() :
199                     resourceName);
200       }
201 
202       // fire the event
203       creoleProxy.fireResourceLoaded(
204         new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
205       );
206 
207       return res;
208     }
209 
210     //The resource is not a persistent LR; use a constructor
211 
212     // create an object using the resource's default constructor
213     try {
214       if(DEBUG) Out.prln("Creating resource " + resClass.getName());
215       res = (Resource) resClass.newInstance();
216     } catch(IllegalAccessException e) {
217       throw new ResourceInstantiationException(
218         "Couldn't create resource instance, access denied: " + e
219       );
220     } catch(InstantiationException e) {
221       throw new ResourceInstantiationException(
222         "Couldn't create resource instance due to newInstance() failure: " + e
223       );
224     }
225 
226     //set the name
227     if(resourceName == null){
228       resourceName = resData.getName() + "_" + Gate.genSym();
229     }
230     res.setName(resourceName);
231 
232     if(LanguageResource.class.isAssignableFrom(resClass)) {
233       // type-specific stuff for LRs
234       if(DEBUG) Out.prln(resClass.getName() + " is a LR");
235     } else if(ProcessingResource.class.isAssignableFrom(resClass)) {
236       // type-specific stuff for PRs
237       if(DEBUG) Out.prln(resClass.getName() + " is a PR");
238       //set the runtime parameters to their defaults
239       try{
240         FeatureMap parameters = newFeatureMap();
241         parameters.putAll(resData.getParameterList().getRuntimeDefaults());
242         res.setParameterValues(parameters);
243       }catch(ParameterException pe){
244         throw new ResourceInstantiationException(
245                   "Could not set the runtime parameters " +
246                   "to their default values for: " + res.getClass().getName() +
247                   " :\n" + pe.toString()
248                   );
249       }
250     // type-specific stuff for VRs
251     } else if(VisualResource.class.isAssignableFrom(resClass)) {
252       if(DEBUG) Out.prln(resClass.getName() + " is a VR");
253 
254     // we have a resource which is not an LR, PR or VR
255     } else if(Controller.class.isAssignableFrom(resClass)){
256       //type specific stuff for Controllers
257     } else {
258       Err.prln("WARNING: instantiating resource which is not a PR, LR or VR:");
259       Err.prln(resData + "END OF WARNING" + Strings.getNl());
260     }
261 
262 
263 
264     //set the parameterValues of the resource
265     try{
266       FeatureMap parameters = newFeatureMap();
267       //put the defaults
268       parameters.putAll(resData.getParameterList().getInitimeDefaults());
269       //overwrite the defaults with the user provided values
270       parameters.putAll(parameterValues);
271       res.setParameterValues(parameters);
272     }catch(ParameterException pe){
273         throw new ResourceInstantiationException(
274                     "Could not set the init parameters for: " +
275                     res.getClass().getName() + " :\n" + pe.toString()
276                   );
277     }
278 
279     Map listeners = new HashMap(gate.gui.MainFrame.getListeners());
280     // set the listeners if any
281     if(listeners != null && !listeners.isEmpty()) {
282       try {
283         if(DEBUG) Out.prln("Setting the listeners for  " + res.toString());
284         AbstractResource.setResourceListeners(res, listeners);
285       } catch(Exception e) {
286         if(DEBUG) Out.prln("Failed to set listeners for " + res.toString());
287         throw new
288           ResourceInstantiationException("Parameterisation failure" + e);
289       }
290     }
291 
292     // if the features of the resource have not been explicitly set,
293     // set them to the features of the resource data
294     if(res.getFeatures() == null || res.getFeatures().isEmpty()){
295       FeatureMap fm = newFeatureMap();
296       fm.putAll(resData.getFeatures());
297       res.setFeatures(fm);
298     }
299 
300     // initialise the resource
301     if(DEBUG) Out.prln("Initialising resource " + res.toString());
302     res = res.init();
303 
304     // remove the listeners if any
305     if(listeners != null && !listeners.isEmpty()) {
306       try {
307         if(DEBUG) Out.prln("Removing the listeners for  " + res.toString());
308         AbstractResource.removeResourceListeners(res, listeners);
309       } catch(Exception e) {
310         if (DEBUG) Out.prln(
311           "Failed to remove the listeners for " + res.toString()
312         );
313         throw new
314           ResourceInstantiationException("Parameterisation failure" + e);
315       }
316     }
317     // record the instantiation on the resource data's stack
318     resData.addInstantiation(res);
319     // add the features specified by the user
320     if(features != null) res.getFeatures().putAll(features);
321     // fire the event
322     creoleProxy.fireResourceLoaded(
323       new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
324     );
325     return res;
326   } // create(resourceClassName, parameterValues, features, listeners)
327 
328   /** Delete an instance of a resource. This involves removing it from
329     * the stack of instantiations maintained by this resource type's
330     * resource data. Deletion does not guarantee that the resource will
331     * become a candidate for garbage collection, just that the GATE framework
332     * is no longer holding references to the resource.
333     *
334     * @param resource the resource to be deleted.
335     */
336   public static void deleteResource(Resource resource) {
337     ResourceData rd =
338       (ResourceData) reg.get(resource.getClass().getName());
339     if(rd!= null)
340       rd.removeInstantiation(resource);
341     creoleProxy.fireResourceUnloaded(
342       new CreoleEvent(resource, CreoleEvent.RESOURCE_UNLOADED)
343     );
344     resource.cleanup();
345   } // deleteResource
346 
347   /** Create a new transient Corpus. */
348   public static Corpus newCorpus(String name)
349                                           throws ResourceInstantiationException
350   {
351     FeatureMap parameterValues = newFeatureMap();
352     parameterValues.put(Corpus.CORPUS_NAME_PARAMETER_NAME, name);
353 //    parameterValues.put("features", Factory.newFeatureMap());
354     return (Corpus) createResource("gate.corpora.CorpusImpl", parameterValues);
355   } // newCorpus
356 
357   /** Create a new transient Document from a URL. */
358   public static Document newDocument(URL sourceUrl)
359                                           throws ResourceInstantiationException
360   {
361     FeatureMap parameterValues = newFeatureMap();
362     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
363     return
364       (Document) createResource("gate.corpora.DocumentImpl", parameterValues);
365   } // newDocument(URL)
366 
367   /** Create a new transient Document from a URL and an encoding. */
368   public static Document newDocument(URL sourceUrl, String encoding)
369                                           throws ResourceInstantiationException
370   {
371     FeatureMap parameterValues = newFeatureMap();
372     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
373     parameterValues.put(Document.DOCUMENT_ENCODING_PARAMETER_NAME, encoding);
374     return
375       (Document) createResource("gate.corpora.DocumentImpl", parameterValues);
376   } // newDocument(URL)
377 
378   /** Create a new transient textual Document from a string. */
379   public static Document newDocument(String content)
380                                           throws ResourceInstantiationException
381   {
382     FeatureMap params = newFeatureMap();
383     params.put(Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, content);
384     Document doc =
385       (Document) createResource("gate.corpora.DocumentImpl", params);
386 /*
387     // laziness: should fit this into createResource by adding a new
388     // document parameter, but haven't time right now...
389     doc.setContent(new DocumentContentImpl(content));
390 */
391     // various classes are in the habit of assuming that a document
392     // inevitably has a source URL...  so give it a dummy one
393 /*    try {
394       doc.setSourceUrl(new URL("http://localhost/"));
395     } catch(MalformedURLException e) {
396       throw new ResourceInstantiationException(
397         "Couldn't create dummy URL in newDocument(String): " + e
398       );
399     }
400 */
401     doc.setSourceUrl(null);
402     return doc;
403   } // newDocument(String)
404 
405   static Class japeParserClass = ParseCpsl.class;
406   public static Class getJapeParserClass() {
407       return japeParserClass;
408   }
409   public static void setJapeParserClass(Class newClass) {
410       if (! ParseCpsl.class.isAssignableFrom(newClass))
411           throw new IllegalArgumentException("Parser class must inherit from " + ParseCpsl.class);
412       japeParserClass = newClass;
413   }
414   
415   public static ParseCpsl newJapeParser(java.io.Reader stream, HashMap existingMacros) {
416       try {
417           Constructor c = japeParserClass.getConstructor
418               (new Class[] {java.io.Reader.class, existingMacros.getClass()});
419           return (ParseCpsl) c.newInstance(new Object[] {stream, existingMacros});
420       } catch (NoSuchMethodException e) { // Shouldn't happen
421           throw new RuntimeException(e);
422       } catch (IllegalArgumentException e) { // Shouldn't happen
423           throw new RuntimeException(e);
424       } catch (InstantiationException e) { // Shouldn't happen
425           throw new RuntimeException(e);
426       } catch (IllegalAccessException e) { // Shouldn't happen
427           throw new RuntimeException(e);
428       } catch (InvocationTargetException e) { // Happens if the constructor throws an exception
429           throw new RuntimeException(e);
430       }
431   }
432   
433   public static ParseCpsl newJapeParser(URL japeURL, String encoding) throws IOException {
434       java.io.Reader stream = new InputStreamReader
435         (new BufferedInputStream(japeURL.openStream()), encoding);
436       
437       ParseCpsl parser = newJapeParser(stream, new HashMap());
438       parser.setBaseURL(japeURL);
439       parser.setEncoding(encoding);
440       return parser;
441   }
442   
443   /** Create a new FeatureMap. */
444   public static FeatureMap newFeatureMap() {
445     return new SimpleFeatureMapImpl();
446   } // newFeatureMap
447 
448   /** Open an existing DataStore. */
449   public static DataStore openDataStore(
450     String dataStoreClassName, String storageUrl
451   ) throws PersistenceException {
452     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
453     ds.open();
454     if(dsReg.add(ds))
455       creoleProxy.fireDatastoreOpened(
456         new CreoleEvent(ds, CreoleEvent.DATASTORE_OPENED)
457       );
458 
459     return ds;
460   } // openDataStore()
461 
462   /** Create a new DataStore and open it. <B>NOTE:</B> for some data stores
463     * creation is an system administrator task; in such cases this
464     * method will throw an UnsupportedOperationException.
465     */
466   public static DataStore createDataStore(
467     String dataStoreClassName, String storageUrl
468   ) throws PersistenceException, UnsupportedOperationException {
469     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
470     ds.create();
471     ds.open();
472     if(dsReg.add(ds))
473       creoleProxy.fireDatastoreCreated(
474         new CreoleEvent(ds, CreoleEvent.DATASTORE_CREATED)
475       );
476 
477     return ds;
478   } // createDataStore()
479 
480   /** Instantiate a DataStore (not open or created). */
481   protected static DataStore instantiateDataStore(
482     String dataStoreClassName, String storageUrl
483   ) throws PersistenceException {
484     DataStore godfreyTheDataStore = null;
485     try {
486       godfreyTheDataStore =
487         (DataStore) Gate.getClassLoader().
488                     loadClass(dataStoreClassName).newInstance();
489     } catch(Exception e) {
490       throw new PersistenceException("Couldn't create DS class: " + e);
491     }
492 
493     if(dsReg == null) // static init ran before Gate.init....
494       dsReg = Gate.getDataStoreRegister();
495     godfreyTheDataStore.setStorageUrl(storageUrl.toString());
496 
497     return godfreyTheDataStore;
498   } // instantiateDS(dataStoreClassName, storageURL)
499 
500   /** Add a listener */
501   public static synchronized void addCreoleListener(CreoleListener l){
502     creoleProxy.addCreoleListener(l);
503   } // addCreoleListener(CreoleListener)
504 
505   /** Static initialiser to set up the CreoleProxy event source object */
506   static {
507     creoleProxy = new CreoleProxy();
508     accessControllerPool = new HashMap();
509   } // static initialiser
510 
511 
512   /**
513    * Creates and opens a new AccessController (if not available in the pool).
514   */
515   public static synchronized AccessController createAccessController(String jdbcURL)
516     throws PersistenceException {
517 
518     if (false == accessControllerPool.containsKey(jdbcURL)) {
519       AccessController ac = new AccessControllerImpl(jdbcURL);
520       ac.open();
521       accessControllerPool.put(jdbcURL,ac);
522     }
523 
524     return (AccessController)accessControllerPool.get(jdbcURL);
525   } // createAccessController()
526 
527 } // abstract Factory
528 
529 
530 /**
531  * Factory is basically a collection of static methods but events need to
532  * have as source an object and not a class. The CreolProxy class addresses
533  * this issue acting as source for all events fired by the Factory class.
534  */
535 class CreoleProxy {
536 
537   public synchronized void removeCreoleListener(CreoleListener l) {
538     if (creoleListeners != null && creoleListeners.contains(l)) {
539       Vector v = (Vector) creoleListeners.clone();
540       v.removeElement(l);
541       creoleListeners = v;
542     }// if
543   }// removeCreoleListener(CreoleListener l)
544 
545   public synchronized void addCreoleListener(CreoleListener l) {
546     Vector v =
547       creoleListeners == null ? new Vector(2) : (Vector) creoleListeners.clone();
548     if (!v.contains(l)) {
549       v.addElement(l);
550       creoleListeners = v;
551     }// if
552   }// addCreoleListener(CreoleListener l)
553 
554   protected void fireResourceLoaded(CreoleEvent e) {
555     if (creoleListeners != null) {
556       Vector listeners = creoleListeners;
557       int count = listeners.size();
558       for (int i = 0; i < count; i++) {
559         ((CreoleListener) listeners.elementAt(i)).resourceLoaded(e);
560       }// for
561     }// if
562   }// fireResourceLoaded(CreoleEvent e)
563 
564   protected void fireResourceUnloaded(CreoleEvent e) {
565     if (creoleListeners != null) {
566       Vector listeners = creoleListeners;
567       int count = listeners.size();
568       for (int i = 0; i < count; i++) {
569         ((CreoleListener) listeners.elementAt(i)).resourceUnloaded(e);
570       }// for
571     }// if
572   }// fireResourceUnloaded(CreoleEvent e)
573 
574   protected void fireDatastoreOpened(CreoleEvent e) {
575     if (creoleListeners != null) {
576       Vector listeners = creoleListeners;
577       int count = listeners.size();
578       for (int i = 0; i < count; i++) {
579         ((CreoleListener) listeners.elementAt(i)).datastoreOpened(e);
580       }// for
581     }// if
582   }// fireDatastoreOpened(CreoleEvent e)
583 
584   protected void fireDatastoreCreated(CreoleEvent e) {
585     if (creoleListeners != null) {
586       Vector listeners = creoleListeners;
587       int count = listeners.size();
588       for (int i = 0; i < count; i++) {
589         ((CreoleListener) listeners.elementAt(i)).datastoreCreated(e);
590       }// for
591     }// if
592   }// fireDatastoreCreated(CreoleEvent e)
593 
594   protected void fireDatastoreClosed(CreoleEvent e) {
595     if (creoleListeners != null) {
596       Vector listeners = creoleListeners;
597       int count = listeners.size();
598       for (int i = 0; i < count; i++) {
599         ((CreoleListener) listeners.elementAt(i)).datastoreClosed(e);
600       }// for
601     }// if
602   }// fireDatastoreClosed(CreoleEvent e)
603 
604   private transient Vector creoleListeners;
605 }//class CreoleProxy
606