001 /*
002 * Factory.java
003 *
004 * Copyright (c) 1995-2010, The University of Sheffield. See the file
005 * COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006 *
007 * This file is part of GATE (see http://gate.ac.uk/), and is free
008 * software, licenced under the GNU Library General Public License,
009 * Version 2, June 1991 (in the distribution as file licence.html,
010 * and also available at http://gate.ac.uk/gate/licence.html).
011 *
012 * Hamish Cunningham, 25/May/2000
013 *
014 * $Id: Factory.java 13570 2011-03-28 14:16:13Z ian_roberts $
015 */
016
017 package gate;
018
019 import java.io.BufferedInputStream;
020 import java.io.IOException;
021 import java.io.InputStreamReader;
022 import java.io.Serializable;
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.InvocationTargetException;
025 import java.net.*;
026 import java.util.*;
027
028 import gate.creole.*;
029 import gate.creole.gazetteer.Gazetteer;
030 //import gate.creole.ontology.owlim.OWLIMOntologyLR;
031 import gate.event.CreoleEvent;
032 import gate.event.CreoleListener;
033 import gate.jape.constraint.ConstraintFactory;
034 import gate.jape.parser.ParseCpsl;
035 import gate.persist.PersistenceException;
036 import gate.persist.SerialDataStore;
037 import gate.security.*;
038 import gate.security.SecurityException;
039 import gate.util.*;
040 import gate.annotation.ImmutableAnnotationSetImpl;
041
042 /** Provides static methods for the creation of Resources.
043 */
044 public abstract class Factory {
045 /** Debug flag */
046 private static final boolean DEBUG = false;
047
048 /** The CREOLE register */
049 private static CreoleRegister reg = Gate.getCreoleRegister();
050
051 /** The DataStore register */
052 private static DataStoreRegister dsReg = Gate.getDataStoreRegister();
053
054 /** An object to source events from. */
055 private static CreoleProxy creoleProxy;
056
057 /** An object to source events from. */
058 private static HashMap accessControllerPool;
059
060 /** Create an instance of a resource using default parameter values.
061 * @see #createResource(String,FeatureMap)
062 */
063 public static Resource createResource(String resourceClassName)
064 throws ResourceInstantiationException
065 {
066 // get the resource metadata
067 ResourceData resData = (ResourceData) reg.get(resourceClassName);
068 if(resData == null)
069 throw new ResourceInstantiationException(
070 "Couldn't get resource data for " + resourceClassName + ".\n\n" +
071 "You may need first to load the plugin that contains your resource.\n" +
072 "For example, to create a gate.creole.tokeniser.DefaultTokeniser\n" +
073 "you need first to load the ANNIE plugin.\n\n" +
074 "Go to the menu File->Manage CREOLE plugins or use the method\n" +
075 "Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL)."
076 );
077
078 // get the parameter list and default values
079 ParameterList paramList = resData.getParameterList();
080 FeatureMap parameterValues = null;
081 try {
082 parameterValues = paramList.getInitimeDefaults();
083 } catch(ParameterException e) {
084 throw new ResourceInstantiationException(
085 "Couldn't get default parameters for " + resourceClassName + ": " + e
086 );
087 }
088
089 return createResource(resourceClassName, parameterValues);
090 } // createResource(resClassName)
091
092 /** Create an instance of a resource, and return it.
093 * Callers of this method are responsible for
094 * querying the resource's parameter lists, putting together a set that
095 * is complete apart from runtime parameters, and passing a feature map
096 * containing these parameter settings.
097 *
098 * @param resourceClassName the name of the class implementing the resource.
099 * @param parameterValues the feature map containing intialisation time
100 * parameterValues for the resource.
101 * @return an instantiated resource.
102 */
103 public static Resource createResource(
104 String resourceClassName, FeatureMap parameterValues
105 ) throws ResourceInstantiationException
106 {
107 return createResource(resourceClassName, parameterValues, null, null);
108 } // createResource(resClassName, paramVals, listeners)
109
110 /** Create an instance of a resource, and return it.
111 * Callers of this method are responsible for
112 * querying the resource's parameter lists, putting together a set that
113 * is complete apart from runtime parameters, and passing a feature map
114 * containing these parameter settings.
115 *
116 * @param resourceClassName the name of the class implementing the resource.
117 * @param parameterValues the feature map containing intialisation time
118 * parameterValues for the resource.
119 * @param features the features for the new resource
120 * @return an instantiated resource.
121 */
122 public static Resource createResource(
123 String resourceClassName, FeatureMap parameterValues,
124 FeatureMap features
125 ) throws ResourceInstantiationException
126 {
127 return createResource(resourceClassName, parameterValues,
128 features, null);
129 }
130
131 /** Create an instance of a resource, and return it.
132 * Callers of this method are responsible for
133 * querying the resource's parameter lists, putting together a set that
134 * is complete apart from runtime parameters, and passing a feature map
135 * containing these parameter settings.
136 *
137 * In the case of ProcessingResources they will have their runtime parameters
138 * initialised to their default values.
139 *
140 * @param resourceClassName the name of the class implementing the resource.
141 * @param parameterValues the feature map containing intialisation time
142 * parameterValues for the resource.
143 * @param features the features for the new resource or null to not assign
144 * any (new) features.
145 * @param resourceName the name to be given to the resource or null to assign
146 * a default name.
147 * @return an instantiated resource.
148 */
149 public static Resource createResource(
150 String resourceClassName, FeatureMap parameterValues,
151 FeatureMap features, String resourceName
152 ) throws ResourceInstantiationException
153 {
154 // get the resource metadata
155 ResourceData resData = (ResourceData) reg.get(resourceClassName);
156 if(resData == null)
157 throw new ResourceInstantiationException(
158 "Couldn't get resource data for " + resourceClassName + ".\n\n" +
159 "You may need first to load the plugin that contains your resource.\n" +
160 "For example, to create a gate.creole.tokeniser.DefaultTokeniser\n" +
161 "you need first to load the ANNIE plugin.\n\n" +
162 "Go to the menu File->Manage CREOLE plugins or use the method\n" +
163 "Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL)."
164 );
165 // get the default implementation class
166 Class resClass = null;
167 try {
168 resClass = resData.getResourceClass();
169 } catch(ClassNotFoundException e) {
170 throw new ResourceInstantiationException(
171 "Couldn't get resource class from the resource data:"+Strings.getNl()+e
172 );
173 }
174
175 //create a pointer for the resource
176 Resource res = null;
177
178 //if the object is an LR and it should come from a DS then create that way
179 DataStore dataStore;
180 if(LanguageResource.class.isAssignableFrom(resClass) &&
181 ((dataStore = (DataStore)parameterValues.
182 get(DataStore.DATASTORE_FEATURE_NAME)) != null)
183 ){
184 //ask the datastore to create our object
185 if(dataStore instanceof SerialDataStore) {
186 // SDS doesn't need a wrapper class; just check for serialisability
187 if(! Serializable.class.isAssignableFrom(resClass))
188 throw new ResourceInstantiationException(
189 "Resource cannot be (de-)serialized: " + resClass.getName()
190 );
191 }
192
193 // get the datastore instance id and retrieve the resource
194 Object instanceId = parameterValues.get(DataStore.LR_ID_FEATURE_NAME);
195 if(instanceId == null)
196 throw new
197 ResourceInstantiationException("No instance id for " + resClass);
198 try {
199 res = dataStore.getLr(resClass.getName(), instanceId);
200 } catch(PersistenceException pe) {
201 throw new ResourceInstantiationException("Bad read from DB: " + pe);
202 } catch(SecurityException se) {
203 throw new ResourceInstantiationException("Insufficient permissions: " + se);
204 }
205 resData.addInstantiation(res);
206 if(features != null){
207 if(res.getFeatures() == null){
208 res.setFeatures(newFeatureMap());
209 }
210 res.getFeatures().putAll(features);
211 }
212
213 //set the name
214 if(res.getName() == null){
215 res.setName(resourceName == null ?
216 resData.getName() + "_" + Gate.genSym() :
217 resourceName);
218 }
219
220 // fire the event
221 creoleProxy.fireResourceLoaded(
222 new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
223 );
224
225 return res;
226 }
227
228 //The resource is not a persistent LR; use a constructor
229
230 // create an object using the resource's default constructor
231 try {
232 if(DEBUG) Out.prln("Creating resource " + resClass.getName());
233 res = (Resource) resClass.newInstance();
234 } catch(IllegalAccessException e) {
235 throw new ResourceInstantiationException(
236 "Couldn't create resource instance, access denied: " + e
237 );
238 } catch(InstantiationException e) {
239 throw new ResourceInstantiationException(
240 "Couldn't create resource instance due to newInstance() failure: " + e
241 );
242 }
243
244
245 if(LanguageResource.class.isAssignableFrom(resClass)) {
246 // type-specific stuff for LRs
247 if(DEBUG) Out.prln(resClass.getName() + " is a LR");
248 } else if(ProcessingResource.class.isAssignableFrom(resClass)) {
249 // type-specific stuff for PRs
250 if(DEBUG) Out.prln(resClass.getName() + " is a PR");
251 //set the runtime parameters to their defaults
252 try{
253 FeatureMap parameters = newFeatureMap();
254 parameters.putAll(resData.getParameterList().getRuntimeDefaults());
255 res.setParameterValues(parameters);
256 }catch(ParameterException pe){
257 throw new ResourceInstantiationException(
258 "Could not set the runtime parameters " +
259 "to their default values for: " + res.getClass().getName() +
260 " :\n" + pe.toString()
261 );
262 }
263 // type-specific stuff for VRs
264 } else if(VisualResource.class.isAssignableFrom(resClass)) {
265 if(DEBUG) Out.prln(resClass.getName() + " is a VR");
266 } else if(Controller.class.isAssignableFrom(resClass)){
267 //type specific stuff for Controllers
268 }
269
270
271 //set the parameterValues of the resource
272 try{
273 FeatureMap parameters = newFeatureMap();
274 //put the defaults
275 parameters.putAll(resData.getParameterList().getInitimeDefaults());
276 //overwrite the defaults with the user provided values
277 parameters.putAll(parameterValues);
278 res.setParameterValues(parameters);
279 }catch(ParameterException pe){
280 throw new ResourceInstantiationException(
281 "Could not set the init parameters for: " +
282 res.getClass().getName() + " :\n" + pe.toString()
283 );
284 }
285
286 //set the name
287 // if we have an explicitly provided name, use that, otherwise generate a
288 // suitable name if the resource doesn't already have one
289 if(resourceName != null && resourceName.trim().length() > 0) {
290 res.setName(resourceName);
291 }
292 else if(res.getName() == null) {
293 //no name provided, and the resource doesn't have a name already (e.g. calculated in init())
294 // -> let's try and find a reasonable one
295 try{
296 //first try to get a filename from the various parameters
297 URL sourceUrl = null;
298 if(res instanceof SimpleDocument){
299 sourceUrl = ((SimpleDocument)res).getSourceUrl();
300 } else if(res instanceof AnnotationSchema){
301 sourceUrl = ((AnnotationSchema)res).getXmlFileUrl();
302 } else if(res.getClass().getName().startsWith("gate.creole.ontology.owlim.")){
303 // get the name for the OWLIM2 ontology LR
304 java.lang.reflect.Method m = resClass.getMethod("getRdfXmlURL");
305 sourceUrl = (java.net.URL)m.invoke(res);
306 if(sourceUrl == null){
307 m = resClass.getMethod("getN3URL");
308 sourceUrl = (java.net.URL)m.invoke(res);
309 }
310 if(sourceUrl == null){
311 m = resClass.getMethod("getNtriplesURL");
312 sourceUrl = (java.net.URL)m.invoke(res);
313 }
314 if(sourceUrl == null){
315 m = resClass.getMethod("getTurtleURL");
316 sourceUrl = (java.net.URL)m.invoke(res);
317 }
318 } else if (res.getClass().getName().startsWith("gate.creole.ontology.impl.")) {
319 java.lang.reflect.Method m = resClass.getMethod("getSourceURL");
320 sourceUrl = (java.net.URL)m.invoke(res);
321 }
322 if(sourceUrl != null){
323 URI sourceURI = sourceUrl.toURI();
324 resourceName = sourceURI.getPath().trim();
325 if(resourceName == null ||
326 resourceName.length() == 0 ||
327 resourceName.equals("/")){
328 //this URI has no path -> use the whole string
329 resourceName = sourceURI.toString();
330 }else{
331 //there is a significant path value -> get the last element
332 int lastSlash = resourceName.lastIndexOf('/');
333 if(lastSlash >=0){
334 String subStr = resourceName.substring(lastSlash + 1);
335 if(subStr.trim().length() > 0) resourceName = subStr;
336 }
337 }
338 }
339 }catch(Exception t){
340 //there were problems while trying to guess a name
341 //we can safely ignore them
342 }finally{
343 //make sure there is a name provided, whatever happened
344 if(resourceName == null || resourceName.trim().length() == 0){
345 resourceName = resData.getName();
346 }
347 }
348 resourceName += "_" + Gate.genSym();
349 res.setName(resourceName);
350 } // else if(res.getName() == null)
351 // if res.getName() != null, leave it as it is
352
353 Map listeners = new HashMap(gate.Gate.getListeners());
354 // set the listeners if any
355 if(listeners != null && !listeners.isEmpty()) {
356 try {
357 if(DEBUG) Out.prln("Setting the listeners for " + res.toString());
358 AbstractResource.setResourceListeners(res, listeners);
359 } catch(Exception e) {
360 if(DEBUG) Out.prln("Failed to set listeners for " + res.toString());
361 throw new
362 ResourceInstantiationException("Parameterisation failure" + e);
363 }
364 }
365
366 // if the features of the resource have not been explicitly set,
367 // set them to the features of the resource data
368 if(res.getFeatures() == null || res.getFeatures().isEmpty()){
369 FeatureMap fm = newFeatureMap();
370 fm.putAll(resData.getFeatures());
371 res.setFeatures(fm);
372 }
373 // add the features specified by the user
374 if(features != null) res.getFeatures().putAll(features);
375
376 // initialise the resource
377 if(DEBUG) Out.prln("Initialising resource " + res.toString());
378 res = res.init();
379
380 // remove the listeners if any
381 if(listeners != null && !listeners.isEmpty()) {
382 try {
383 if(DEBUG) Out.prln("Removing the listeners for " + res.toString());
384 AbstractResource.removeResourceListeners(res, listeners);
385 } catch(Exception e) {
386 if (DEBUG) Out.prln(
387 "Failed to remove the listeners for " + res.toString()
388 );
389 throw new
390 ResourceInstantiationException("Parameterisation failure" + e);
391 }
392 }
393 // record the instantiation on the resource data's stack
394 resData.addInstantiation(res);
395 // fire the event
396 creoleProxy.fireResourceLoaded(
397 new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
398 );
399 return res;
400 } // create(resourceClassName, parameterValues, features, listeners)
401
402 /** Delete an instance of a resource. This involves removing it from
403 * the stack of instantiations maintained by this resource type's
404 * resource data. Deletion does not guarantee that the resource will
405 * become a candidate for garbage collection, just that the GATE framework
406 * is no longer holding references to the resource.
407 *
408 * @param resource the resource to be deleted.
409 */
410 public static void deleteResource(Resource resource) {
411 ResourceData rd =
412 (ResourceData) reg.get(resource.getClass().getName());
413 if(rd!= null && rd.removeInstantiation(resource)) {
414 creoleProxy.fireResourceUnloaded(
415 new CreoleEvent(resource, CreoleEvent.RESOURCE_UNLOADED)
416 );
417 resource.cleanup();
418 }
419 } // deleteResource
420
421 /** Create a new transient Corpus. */
422 public static Corpus newCorpus(String name)
423 throws ResourceInstantiationException
424 {
425 return (Corpus) createResource("gate.corpora.CorpusImpl", newFeatureMap(), newFeatureMap(), name);
426 } // newCorpus
427
428 /** Create a new transient Document from a URL. */
429 public static Document newDocument(URL sourceUrl)
430 throws ResourceInstantiationException
431 {
432 FeatureMap parameterValues = newFeatureMap();
433 parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
434 return
435 (Document) createResource("gate.corpora.DocumentImpl", parameterValues);
436 } // newDocument(URL)
437
438 /** Create a new transient Document from a URL and an encoding. */
439 public static Document newDocument(URL sourceUrl, String encoding)
440 throws ResourceInstantiationException
441 {
442 FeatureMap parameterValues = newFeatureMap();
443 parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
444 parameterValues.put(Document.DOCUMENT_ENCODING_PARAMETER_NAME, encoding);
445 return
446 (Document) createResource("gate.corpora.DocumentImpl", parameterValues);
447 } // newDocument(URL)
448
449 /** Create a new transient textual Document from a string. */
450 public static Document newDocument(String content)
451 throws ResourceInstantiationException
452 {
453 FeatureMap params = newFeatureMap();
454 params.put(Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, content);
455 Document doc =
456 (Document) createResource("gate.corpora.DocumentImpl", params);
457 /*
458 // laziness: should fit this into createResource by adding a new
459 // document parameter, but haven't time right now...
460 doc.setContent(new DocumentContentImpl(content));
461 */
462 // various classes are in the habit of assuming that a document
463 // inevitably has a source URL... so give it a dummy one
464 /* try {
465 doc.setSourceUrl(new URL("http://localhost/"));
466 } catch(MalformedURLException e) {
467 throw new ResourceInstantiationException(
468 "Couldn't create dummy URL in newDocument(String): " + e
469 );
470 }
471 */
472 doc.setSourceUrl(null);
473 return doc;
474 } // newDocument(String)
475
476
477 /**
478 * Utility method to create an immutable annotation set. If the provided
479 * collection of annotations is <code>null</null>, the newly created set will
480 * be empty.
481 * @param document the document this set belongs to.
482 * @param annotations the set of annotations that should be contained in the
483 * returned {@link AnnotationSet}.
484 * @return an {@link AnnotationSet} that throws exceptions on all attempts
485 * to modify it.
486 */
487 public static AnnotationSet createImmutableAnnotationSet(Document document,
488 Collection<Annotation> annotations) {
489 return new ImmutableAnnotationSetImpl(document, annotations){
490 private static final long serialVersionUID = -6703131102439043539L;
491 };
492 }
493
494 /**
495 * <p>
496 * Create a <i>duplicate</i> of the given resource. A duplicate is a
497 * an independent copy of the resource that has the same name and the
498 * same behaviour. It does <i>not necessarily</i> have the same concrete
499 * class as the original, but if the original resource implements any of
500 * the following interfaces then the duplicate can be assumed to
501 * implement the same ones:
502 * </p>
503 * <ul>
504 * <li>{@link ProcessingResource}</li>
505 * <li>{@link LanguageAnalyser}</li>
506 * <li>{@link Controller}</li>
507 * <li>{@link CorpusController}</li>
508 * <li>{@link ConditionalController}</li>
509 * <li>{@link Gazetteer}</li>
510 * <li>{@link LanguageResource}</li>
511 * <li>{@link gate.creole.ontology.Ontology}</li>
512 * <li>{@link Document}</li>
513 * <li>{@link Corpus}</li>
514 * </ul>
515 * <p>
516 * The default duplication algorithm simply calls
517 * {@link #createResource(String, FeatureMap, FeatureMap, String) createResource}
518 * with the type and name of the original resource, and with parameters
519 * and features which are copies of those from the original resource,
520 * but any Resource values in the maps will themselves be duplicated.
521 * A context is passed around all the duplicate calls that stem from the
522 * same call to this method so that if the same resource is referred to
523 * in different places, the same duplicate can be used in the
524 * corresponding places in the duplicated object graph.
525 * </p>
526 * <p>
527 * This default behaviour is sufficient for most resource types (and
528 * is roughly the equivalent of saving the resource's state using the
529 * persistence manager and then reloading it), but individual resource
530 * classes can override it by implementing the {@link CustomDuplication}
531 * interface. This may be necessary for semantic reasons (e.g.
532 * controllers need to recursively duplicate the PRs they contain),
533 * or desirable for performance or memory consumption reasons (e.g. the
534 * behaviour of a DefaultGazetteer can be duplicated by a
535 * SharedDefaultGazetteer that shares the internal data structures).
536 * </p>
537 *
538 * @param res the resource to duplicate
539 * @return an independent duplicate copy of the resource
540 * @throws ResourceInstantiationException if an exception occurs while
541 * constructing the duplicate.
542 */
543 public static Resource duplicate(Resource res)
544 throws ResourceInstantiationException {
545 DuplicationContext ctx = new DuplicationContext();
546 try {
547 return duplicate(res, ctx);
548 }
549 finally {
550 // de-activate the context
551 ctx.active = false;
552 }
553 }
554
555 /**
556 * Create a duplicate of the given resource, using the provided context.
557 * This method is intended for use by resources that implement the
558 * {@link CustomDuplication} interface when they need to duplicate
559 * their child resources. Calls made to this method outside the scope of
560 * such a {@link CustomDuplication#duplicate CustomDuplication.duplicate}
561 * call will fail with a runtime exception.
562 *
563 * @see #duplicate(Resource)
564 * @param res the resource to duplicate
565 * @param ctx the current context as passed to the
566 * {@link CustomDuplication#duplicate} method.
567 * @return the duplicated resource
568 * @throws ResourceInstantiationException if an error occurs while
569 * constructing the duplicate.
570 */
571 public static Resource duplicate(Resource res,
572 DuplicationContext ctx)
573 throws ResourceInstantiationException {
574 checkDuplicationContext(ctx);
575 // check for null
576 if(res == null) {
577 return null;
578 }
579 // check if we've seen this resource before
580 else if(ctx.knownResources.containsKey(res)) {
581 return ctx.knownResources.get(res);
582 }
583 else {
584 // create the duplicate
585 Resource newRes = null;
586 if(res instanceof CustomDuplication) {
587 // use custom duplicate if available
588 newRes = ((CustomDuplication)res).duplicate(ctx);
589 }
590 else {
591 newRes = defaultDuplicate(res, ctx);
592 }
593 // remember this duplicate in the context
594 ctx.knownResources.put(res, newRes);
595 return newRes;
596 }
597 }
598
599 /**
600 * Implementation of the default duplication algorithm described
601 * in the comment for {@link #duplicate(Resource)}. This method is
602 * public for the benefit of resources that implement
603 * {@link CustomDuplication} but only need to do some post-processing
604 * after the default duplication algorithm; they can call this method
605 * to obtain an initial duplicate and then post-process it before
606 * returning. If they need to duplicate child resources they should
607 * call {@link #duplicate(Resource, DuplicationContext)} in the normal
608 * way. Calls to this method made outside the context of a
609 * {@link CustomDuplication#duplicate CustomDuplication.duplicate}
610 * call will fail with a runtime exception.
611 *
612 * @param res the resource to duplicate
613 * @param ctx the current context
614 * @return a duplicate of the given resource, constructed using the
615 * default algorithm. In particular, if <code>res</code>
616 * implements {@link CustomDuplication} its own duplicate method
617 * will <i>not</i> be called.
618 * @throws ResourceInstantiationException if an error occurs
619 * while duplicating the given resource.
620 */
621 public static Resource defaultDuplicate(Resource res,
622 DuplicationContext ctx)
623 throws ResourceInstantiationException {
624 checkDuplicationContext(ctx);
625 String className = res.getClass().getName();
626 String resName = res.getName();
627
628 FeatureMap newResFeatures = duplicate(res.getFeatures(), ctx);
629
630 // get the resource data to extract the parameters
631 ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(className);
632 if(rData == null)
633 throw new ResourceInstantiationException(
634 "Could not find CREOLE data for " + className);
635
636 ParameterList params = rData.getParameterList();
637 // init parameters
638 FeatureMap initParams = Factory.newFeatureMap();
639 for(List<Parameter> parDisjunction : params.getInitimeParameters()) {
640 for(Parameter p : parDisjunction) {
641 initParams.put(p.getName(), res.getParameterValue(p.getName()));
642 }
643 }
644 // duplicate any Resources in the params map
645 initParams = duplicate(initParams, ctx);
646
647 // create the new resource
648 Resource newResource = createResource(className, initParams, newResFeatures, resName);
649 if(newResource instanceof ProcessingResource) {
650 // runtime params
651 FeatureMap runtimeParams = Factory.newFeatureMap();
652 for(List<Parameter> parDisjunction : params.getRuntimeParameters()) {
653 for(Parameter p : parDisjunction) {
654 runtimeParams.put(p.getName(), res.getParameterValue(p.getName()));
655 }
656 }
657 // duplicate any Resources in the params map
658 runtimeParams = duplicate(runtimeParams, ctx);
659 newResource.setParameterValues(runtimeParams);
660 }
661
662 return newResource;
663 }
664
665 /**
666 * Construct a feature map that is a copy of the one provided except
667 * that any {@link Resource} values in the map are replaced by their
668 * duplicates. This method is public for the benefit of resources
669 * that implement {@link CustomDuplication} and will fail if called
670 * outside of a
671 * {@link CustomDuplication#duplicate CustomDuplication.duplicate}
672 * implementation.
673 *
674 * @param fm the feature map to duplicate
675 * @param ctx the current context
676 * @return a duplicate feature map
677 * @throws ResourceInstantiationException if an error occurs while
678 * duplicating any Resource in the feature map.
679 */
680 public static FeatureMap duplicate(FeatureMap fm,
681 DuplicationContext ctx)
682 throws ResourceInstantiationException {
683 checkDuplicationContext(ctx);
684 FeatureMap newFM = Factory.newFeatureMap();
685 for(Map.Entry<Object, Object> entry : fm.entrySet()) {
686 Object value = entry.getValue();
687 if(value instanceof Resource) {
688 value = duplicate((Resource)value, ctx);
689 }
690 newFM.put(entry.getKey(), value);
691 }
692 return newFM;
693 }
694
695 /**
696 * Opaque memo object passed to
697 * {@link CustomDuplication#duplicate CustomDuplication.duplicate}
698 * methods to encapsulate the state of the current duplication run.
699 * If the duplicate method itself needs to duplicate any objects it
700 * should pass this context back to
701 * {@link #duplicate(Resource,DuplicationContext)}.
702 */
703 public static class DuplicationContext {
704 IdentityHashMap<Resource, Resource> knownResources =
705 new IdentityHashMap<Resource, Resource>();
706
707 /**
708 * Whether this duplication context is part of an active duplicate
709 * call.
710 */
711 boolean active = true;
712
713 /**
714 * Overridden to ensure no public constructor.
715 */
716 DuplicationContext() {
717 }
718 }
719
720 /**
721 * Throws an exception if the specified duplication context is
722 * null or not active. This is to ensure that the Factory
723 * helper methods that take a DuplicationContext parameter can
724 * only be called in the context of a
725 * {@link #duplicate(Resource)} call.
726 * @param ctx the context to check.
727 * @throws NullPointerException if the provided context is null.
728 * @throws IllegalStateException if the provided context is not
729 * active.
730 */
731 protected static void checkDuplicationContext(DuplicationContext ctx) {
732 if(ctx == null) {
733 throw new NullPointerException("No DuplicationContext provided");
734 }
735 if(!ctx.active) {
736 throw new IllegalStateException(
737 new Throwable().getStackTrace()[1].getMethodName()
738 + " helper method called outside an active duplicate call");
739 }
740 }
741
742 static Class japeParserClass = ParseCpsl.class;
743 public static Class getJapeParserClass() {
744 return japeParserClass;
745 }
746 public static void setJapeParserClass(Class newClass) {
747 if (! ParseCpsl.class.isAssignableFrom(newClass))
748 throw new IllegalArgumentException("Parser class must inherit from " + ParseCpsl.class);
749 japeParserClass = newClass;
750 }
751
752 public static ParseCpsl newJapeParser(java.io.Reader stream, HashMap existingMacros) {
753 try {
754 Constructor c = japeParserClass.getConstructor
755 (new Class[] {java.io.Reader.class, existingMacros.getClass()});
756 return (ParseCpsl) c.newInstance(new Object[] {stream, existingMacros});
757 } catch (NoSuchMethodException e) { // Shouldn't happen
758 throw new RuntimeException(e);
759 } catch (IllegalArgumentException e) { // Shouldn't happen
760 throw new RuntimeException(e);
761 } catch (InstantiationException e) { // Shouldn't happen
762 throw new RuntimeException(e);
763 } catch (IllegalAccessException e) { // Shouldn't happen
764 throw new RuntimeException(e);
765 } catch (InvocationTargetException e) { // Happens if the constructor throws an exception
766 throw new RuntimeException(e);
767 }
768 }
769
770 public static ParseCpsl newJapeParser(URL japeURL, String encoding) throws IOException {
771 // the stripping stream is buffered, no need to buffer the URL stream.
772 java.io.Reader stream = new BomStrippingInputStreamReader(japeURL.openStream(), encoding);
773
774 ParseCpsl parser = newJapeParser(stream, new HashMap());
775 parser.setBaseURL(japeURL);
776 parser.setEncoding(encoding);
777 return parser;
778 }
779
780 /**
781 * Active ConstraintFactory for creating and initializing Jape <b>Constraint</b>s.
782 */
783 private static ConstraintFactory japeConstraintFactory = new ConstraintFactory();
784 /**
785 * Return the active {@link ConstraintFactory} for creating and initializing Jape
786 * <b>Constraint</b>s.
787 * @return
788 */
789 public static ConstraintFactory getConstraintFactory() {
790 return japeConstraintFactory;
791 }
792
793 /** Create a new FeatureMap. */
794 public static FeatureMap newFeatureMap() {
795 return new SimpleFeatureMapImpl();
796 } // newFeatureMap
797
798 /** Open an existing DataStore. */
799 public static DataStore openDataStore(
800 String dataStoreClassName, String storageUrl
801 ) throws PersistenceException {
802 DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
803 ds.open();
804 if(dsReg.add(ds))
805 creoleProxy.fireDatastoreOpened(
806 new CreoleEvent(ds, CreoleEvent.DATASTORE_OPENED)
807 );
808
809 return ds;
810 } // openDataStore()
811
812 /** Create a new DataStore and open it. <B>NOTE:</B> for some data stores
813 * creation is an system administrator task; in such cases this
814 * method will throw an UnsupportedOperationException.
815 */
816 public static DataStore createDataStore(
817 String dataStoreClassName, String storageUrl
818 ) throws PersistenceException, UnsupportedOperationException {
819 DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
820 ds.create();
821 ds.open();
822 if(dsReg.add(ds))
823 creoleProxy.fireDatastoreCreated(
824 new CreoleEvent(ds, CreoleEvent.DATASTORE_CREATED)
825 );
826
827 return ds;
828 } // createDataStore()
829
830 /** Instantiate a DataStore (not open or created). */
831 protected static DataStore instantiateDataStore(
832 String dataStoreClassName, String storageUrl
833 ) throws PersistenceException {
834 DataStore godfreyTheDataStore = null;
835 try {
836 godfreyTheDataStore =
837 (DataStore) Gate.getClassLoader().
838 loadClass(dataStoreClassName).newInstance();
839 } catch(Exception e) {
840 throw new PersistenceException("Couldn't create DS class: " + e);
841 }
842
843 if(dsReg == null) // static init ran before Gate.init....
844 dsReg = Gate.getDataStoreRegister();
845 godfreyTheDataStore.setStorageUrl(storageUrl.toString());
846
847 return godfreyTheDataStore;
848 } // instantiateDS(dataStoreClassName, storageURL)
849
850 /** Add a listener */
851 public static synchronized void addCreoleListener(CreoleListener l){
852 creoleProxy.addCreoleListener(l);
853 } // addCreoleListener(CreoleListener)
854
855 /** Static initialiser to set up the CreoleProxy event source object */
856 static {
857 creoleProxy = new CreoleProxy();
858 accessControllerPool = new HashMap();
859 } // static initialiser
860
861
862 /**
863 * Creates and opens a new AccessController (if not available in the pool).
864 */
865 public static synchronized AccessController createAccessController(String jdbcURL)
866 throws PersistenceException {
867
868 if (false == accessControllerPool.containsKey(jdbcURL)) {
869 AccessController ac = new AccessControllerImpl(jdbcURL);
870 ac.open();
871 accessControllerPool.put(jdbcURL,ac);
872 }
873
874 return (AccessController)accessControllerPool.get(jdbcURL);
875 } // createAccessController()
876
877 } // abstract Factory
878
879
880 /**
881 * Factory is basically a collection of static methods but events need to
882 * have as source an object and not a class. The CreolProxy class addresses
883 * this issue acting as source for all events fired by the Factory class.
884 */
885 class CreoleProxy {
886
887 public synchronized void removeCreoleListener(CreoleListener l) {
888 if (creoleListeners != null && creoleListeners.contains(l)) {
889 Vector v = (Vector) creoleListeners.clone();
890 v.removeElement(l);
891 creoleListeners = v;
892 }// if
893 }// removeCreoleListener(CreoleListener l)
894
895 public synchronized void addCreoleListener(CreoleListener l) {
896 Vector v =
897 creoleListeners == null ? new Vector(2) : (Vector) creoleListeners.clone();
898 if (!v.contains(l)) {
899 v.addElement(l);
900 creoleListeners = v;
901 }// if
902 }// addCreoleListener(CreoleListener l)
903
904 protected void fireResourceLoaded(CreoleEvent e) {
905 if (creoleListeners != null) {
906 Vector listeners = creoleListeners;
907 int count = listeners.size();
908 for (int i = 0; i < count; i++) {
909 ((CreoleListener) listeners.elementAt(i)).resourceLoaded(e);
910 }// for
911 }// if
912 }// fireResourceLoaded(CreoleEvent e)
913
914 protected void fireResourceUnloaded(CreoleEvent e) {
915 if (creoleListeners != null) {
916 Vector listeners = creoleListeners;
917 int count = listeners.size();
918 for (int i = 0; i < count; i++) {
919 ((CreoleListener) listeners.elementAt(i)).resourceUnloaded(e);
920 }// for
921 }// if
922 }// fireResourceUnloaded(CreoleEvent e)
923
924 protected void fireDatastoreOpened(CreoleEvent e) {
925 if (creoleListeners != null) {
926 Vector listeners = creoleListeners;
927 int count = listeners.size();
928 for (int i = 0; i < count; i++) {
929 ((CreoleListener) listeners.elementAt(i)).datastoreOpened(e);
930 }// for
931 }// if
932 }// fireDatastoreOpened(CreoleEvent e)
933
934 protected void fireDatastoreCreated(CreoleEvent e) {
935 if (creoleListeners != null) {
936 Vector listeners = creoleListeners;
937 int count = listeners.size();
938 for (int i = 0; i < count; i++) {
939 ((CreoleListener) listeners.elementAt(i)).datastoreCreated(e);
940 }// for
941 }// if
942 }// fireDatastoreCreated(CreoleEvent e)
943
944 protected void fireDatastoreClosed(CreoleEvent e) {
945 if (creoleListeners != null) {
946 Vector listeners = creoleListeners;
947 int count = listeners.size();
948 for (int i = 0; i < count; i++) {
949 ((CreoleListener) listeners.elementAt(i)).datastoreClosed(e);
950 }// for
951 }// if
952 }// fireDatastoreClosed(CreoleEvent e)
953
954 private transient Vector creoleListeners;
955 }//class CreoleProxy
|