/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.resource.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.uima.UIMAFramework;
import org.apache.uima.UIMA_IllegalStateException;
import org.apache.uima.analysis_engine.impl.AnalysisEngineImplBase;
import org.apache.uima.internal.util.UIMAClassLoader;
import org.apache.uima.resource.CasManager;
import org.apache.uima.resource.DataResource;
import org.apache.uima.resource.ExternalResourceDependency;
import org.apache.uima.resource.ExternalResourceDescription;
import org.apache.uima.resource.FileResourceSpecifier;
import org.apache.uima.resource.ParameterizedDataResource;
import org.apache.uima.resource.RelativePathResolver;
import org.apache.uima.resource.Resource;
import org.apache.uima.resource.ResourceAccessException;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.ResourceManager;
import org.apache.uima.resource.SharedResourceObject;
import org.apache.uima.resource.metadata.ExternalResourceBinding;
import org.apache.uima.resource.metadata.ResourceManagerConfiguration;
import org.apache.uima.util.Level;
import org.apache.uima.util.XMLizable;

/**
 * Reference implementation of {@link org.apache.uima.resource.ResourceManager}.
 * 
 * 
 */
public class ResourceManager_impl implements ResourceManager {
  /**
   * resource bundle for log messages
   */
  protected static final String LOG_RESOURCE_BUNDLE = "org.apache.uima.impl.log_messages";

  /**
   * Object used for resolving relative paths. This is built by parsing the data path.
   */
  private final RelativePathResolver mRelativePathResolver;

  /**
   * Map from qualified key names (declared in resource dependency XML) to Resource objects.
   */
  protected Map<String, Object> mResourceMap = Collections.synchronizedMap(new HashMap<String, Object>());

  /**
   * Internal map from resource names (declared in resource declaration XML) to ResourceRegistration
   * objects. Used during initialization only.
   */
  protected Map<String, ResourceRegistration> mInternalResourceRegistrationMap = Collections.synchronizedMap(new HashMap<String, ResourceRegistration>());

  /**
   * Map from String keys to Class objects. For ParameterizedResources only, stores the
   * implementation class corresponding to each resource name.
   */
  protected Map<String, Class<?>> mParameterizedResourceImplClassMap = Collections.synchronizedMap(new HashMap<String, Class<?>>());

  /**
   * Internal map from resource names (declared in resource declaration XML) to Class objects. Used
   * internally during resource initialization.
   */
  protected Map<String, Class<?>> mInternalParameterizedResourceImplClassMap = Collections
          .synchronizedMap(new HashMap<String, Class<?>>());

  /**
   * Map from ArrayList(0:String,1:DataResource) keys to Resource objects. For
   * ParameterizedResources only, stores the DataResources that have already been encountered, and
   * the Resources that have been instantiated therefrom.
   */
  protected Map<List<Object>, Object> mParameterizedResourceInstanceMap = Collections.synchronizedMap(new HashMap<List<Object>, Object>());

  /**
   * UIMA extension ClassLoader. ClassLoader is created if an extension classpath is specified at
   * the ResourceManager
   */
  private UIMAClassLoader uimaCL = null;

  /** CasManager - manages creation and pooling of CASes. */
  // gets and sets of this are synchronized
  protected CasManager mCasManager = null;

  /**
   * Cache of imported descriptors, so that parsed objects can be reused if the
   * same URL is imported more than once.
   * 
   * Use Case where synchronization is needed:
   *   running multiple instances on multiple threads, sharing a common resource manager,
   *   the initialization that merges typePriorities happens lazily, when using Cas Multipliers,
   *   and occurs when the first getCas call happens on a thread.  Although these calls
   *   are synchronized among themselves, any other use of this map that might occur
   *   simultaneously is not.
   */
  private Map<String,XMLizable> importCache = Collections.synchronizedMap(new HashMap<String,XMLizable>());
  
  /**
   * Creates a new <code>ResourceManager_impl</code>.
   */
  public ResourceManager_impl() {
    mRelativePathResolver = new RelativePathResolver_impl();
  }

 /**
  * Support reusing UIMA Class Loader instances to speed up
  * things including the Component Description Editor when
  * obtaining info from CustomResourceSpecifiers
  * https://issues.apache.org/jira/browse/UIMA-1722
  * @param uimaCL
  * @param resolveResource
  */
 public synchronized void setExtensionClassPath(UIMAClassLoader uimaCL, boolean resolveResource) {
   this.uimaCL = uimaCL;
   
   if (resolveResource) {
     // set UIMA extension ClassLoader also to resolve resources
     getRelativePathResolver().setPathResolverClassLoader(uimaCL);
   }
 }

 /**

  /**
   * @see org.apache.uima.resource.ResourceManager#setExtensionClassPath(java.lang.String, boolean)
   */
  public synchronized void setExtensionClassPath(String classpath, boolean resolveResource)
          throws MalformedURLException {
    // create UIMA extension ClassLoader with the given classpath
    uimaCL = new UIMAClassLoader(classpath, this.getClass().getClassLoader());

    if (resolveResource) {
      // set UIMA extension ClassLoader also to resolve resources
      getRelativePathResolver().setPathResolverClassLoader(uimaCL);
    }
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#setExtensionClassPath(ClassLoader,java.lang.String,
   *      boolean)
   */
  public synchronized void setExtensionClassPath(ClassLoader parent, String classpath, boolean resolveResource)
          throws MalformedURLException {
    // create UIMA extension ClassLoader with the given classpath
    uimaCL = new UIMAClassLoader(classpath, parent);

    if (resolveResource) {
      // set UIMA extension ClassLoader also to resolve resources
      getRelativePathResolver().setPathResolverClassLoader(uimaCL);
    }
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#getExtensionClassLoader()
   */
  public synchronized ClassLoader getExtensionClassLoader() {
    return uimaCL;
  }

  /**
   * Creates a new <code>ResourceManager_impl</code> with a custom ClassLoader to use for locating
   * resources.
   */
  public ResourceManager_impl(ClassLoader aClassLoader) {
    mRelativePathResolver = new RelativePathResolver_impl(aClassLoader);
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#getDataPath()
   */
  public String getDataPath() {
    return getRelativePathResolver().getDataPath();
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#setDataPath(String)
   */
  public void setDataPath(String aPath) throws MalformedURLException {
    getRelativePathResolver().setDataPath(aPath);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#resolveRelativePath(java.lang.String)
   */
  public URL resolveRelativePath(String aRelativePath) throws MalformedURLException {
    URL relativeUrl;
    try {
      relativeUrl = new URL(aRelativePath);
    } catch (MalformedURLException e) {
      relativeUrl = new URL("file", "", aRelativePath);
    }
    return getRelativePathResolver().resolveRelativePath(relativeUrl);
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#getResource(String)
   */
  public Object getResource(String aName) throws ResourceAccessException {
    Object r = mResourceMap.get(aName);
    // if this is a ParameterizedDataResource, it is an error
    if (r instanceof ParameterizedDataResource) {
      throw new ResourceAccessException(ResourceAccessException.PARAMETERS_REQUIRED,
              new Object[] { aName });
    }
    return r;

  }

  /**
   * @see org.apache.uima.resource.ResourceManager#getResource(java.lang.String, java.lang.String[])
   */
  public Object getResource(String aName, String[] aParams) throws ResourceAccessException {
    Object r = mResourceMap.get(aName);

    // if no resource found, return null
    if (r == null) {
      return null;
    }
    // if not a ParameterizedDataResource, it is an error
    if (!(r instanceof ParameterizedDataResource)) {
      throw new ResourceAccessException(ResourceAccessException.PARAMETERS_NOT_ALLOWED,
              new Object[] { aName });
    }
    ParameterizedDataResource pdr = (ParameterizedDataResource) r;

    // get a particular DataResource instance for the specified parameters
    DataResource dr;
    try {
      dr = pdr.getDataResource(aParams);
    } catch (ResourceInitializationException e) {
      throw new ResourceAccessException(e);
    }

    // see if we've already encountered this DataResource under this resource name
    ArrayList<Object> nameAndResource = new ArrayList<Object>();
    nameAndResource.add(aName);
    nameAndResource.add(dr);
    Object resourceInstance = mParameterizedResourceInstanceMap.get(nameAndResource);
    if (resourceInstance != null) {
      return resourceInstance;
    }

    // We haven't encountered this before. See if we need to instantiate a
    // SharedResourceObject
    Class<?> sharedResourceObjectClass = mParameterizedResourceImplClassMap.get(aName);
    if (sharedResourceObjectClass != null) {
      try {
        SharedResourceObject sro = (SharedResourceObject) sharedResourceObjectClass.newInstance();
        sro.load(dr);
        mParameterizedResourceInstanceMap.put(nameAndResource, sro);
        return sro;
      } catch (InstantiationException e) {
        throw new ResourceAccessException(e);
      } catch (IllegalAccessException e) {
        throw new ResourceAccessException(e);
      } catch (ResourceInitializationException e) {
        throw new ResourceAccessException(e);
      }
    } else
    // no impl. class - just return the DataResource
    {
      mParameterizedResourceInstanceMap.put(nameAndResource, dr);
      return dr;
    }
  }

  /**
   * @see org.apache.uima.resource.ResourceManager#getResourceClass(java.lang.String)
   */
  @SuppressWarnings("unchecked")
  public Class<? extends Resource> getResourceClass(String aName) {
    Object r = mResourceMap.get(aName);
    if (r == null) // no such resource
    {
      return null;
    }

    // if this is a ParameterizedDataResource, look up its class
    if (r instanceof ParameterizedDataResource) {
      Class<? extends Resource> customResourceClass = (Class<? extends Resource>) mParameterizedResourceImplClassMap.get(aName);
      if (customResourceClass == null) {
        // return the default class
        return DataResource_impl.class;
      }
      return customResourceClass;
    } else {
      // return r's Class
      return (Class<? extends Resource>) r.getClass();
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#getResourceAsStream(java.lang.String,
   *      java.lang.String[])
   */
  public InputStream getResourceAsStream(String aKey, String[] aParams)
          throws ResourceAccessException {
    try {
      // see if this resource is registered in the ResourceManager
      Object r = getResource(aKey, aParams);
      // if so, and if it is a DataResource, use its InputStream
      if (r != null && r instanceof DataResource) {
        return ((DataResource) r).getInputStream();
      } else {
        return null;
      }
    } catch (IOException e) {
      throw new ResourceAccessException(e);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#getResourceAsStream(java.lang.String)
   */
  public InputStream getResourceAsStream(String aKey) throws ResourceAccessException {
    try {
      // see if this resource is registered in the ResourceManager
      Object r = getResource(aKey);
      // if so, and if it is a DataResource, use its InputStream
      if (r != null && r instanceof DataResource) {
        return ((DataResource) r).getInputStream();
      } else {
        return null;
      }
    } catch (IOException e) {
      throw new ResourceAccessException(e);
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#getResourceURL(java.lang.String,
   *      java.lang.String[])
   */
  public URL getResourceURL(String aKey, String[] aParams) throws ResourceAccessException {
    // try
    // {
    // see if this resource is registered in the ResourceManager
    Object r = getResource(aKey, aParams);
    // if so, and if it is a DataResource, use its URL
    if (r != null && r instanceof DataResource) {
      return ((DataResource) r).getUrl();
    } else {
      return null;
      // //fall back on Relative Path Resolver (searches data path then ClassLoader)
      // URL relativeUrl;
      // try
      // {
      // relativeUrl = new URL(aKey);
      // }
      // catch(MalformedURLException e)
      // {
      // relativeUrl = new URL("file","",aKey);
      // }
      // return getRelativePathResolver().resolveRelativePath(relativeUrl);
    }
    // }
    // catch(MalformedURLException e)
    // {
    // throw new ResourceAccessException(e);
    // }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#getResourceURL(java.lang.String)
   */
  public URL getResourceURL(String aKey) throws ResourceAccessException {
    // try
    // {
    // see if this resource is registered in the ResourceManager
    Object r = getResource(aKey);
    // if so, and if it is a DataResource, use its URL
    if (r != null && r instanceof DataResource) {
      return ((DataResource) r).getUrl();
    } else {
      return null;
      // //fall back on Relative Path Resolver (searches data path then ClassLoader)
      // // String keyNoContext = stripContext(aKey); Stripping to last / doesn't work - also strips
      // part of path!!!
      // URL relativeUrl;
      // try
      // {
      // relativeUrl = new URL(aKey);//keyNoContext);
      // }
      // catch(MalformedURLException e)
      // {
      // relativeUrl = new URL("file","",aKey);//keyNoContext);
      // }
      // return getRelativePathResolver().resolveRelativePath(relativeUrl);
    }
    // }
    // catch(MalformedURLException e)
    // {
    // throw new ResourceAccessException(e);
    // }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#initializeExternalResources(org.apache.uima.resource.metadata.ResourceManagerConfiguration,
   *      java.lang.String, java.util.Map)
   */
  public void initializeExternalResources(ResourceManagerConfiguration aConfiguration,
          String aQualifiedContextName, Map<String, Object> aAdditionalParams)
          throws ResourceInitializationException {
    // register resources
    ExternalResourceDescription[] resources = aConfiguration.getExternalResources();
    for (int i = 0; i < resources.length; i++) {
      String name = resources[i].getName();

      // check for existing resource registration under this name
      ResourceRegistration registration = (ResourceRegistration) mInternalResourceRegistrationMap
              .get(name);
      if (registration == null) {
        registerResource(name, resources[i], aQualifiedContextName, aAdditionalParams);
      } else {
        // log a message if the resource definitions are not identical
        if (!registration.description.equals(resources[i])) {
          // if the resource was overridden in an enclosing aggregate, use an INFO level message.
          // if not (e.g. sibling annotators declare the same resource name), it's a WARNING.
          if (aQualifiedContextName.startsWith(registration.definingContext)) {
            UIMAFramework.getLogger().logrb(Level.CONFIG, ResourceManager_impl.class.getName(),
                    "initializeExternalResources", LOG_RESOURCE_BUNDLE,
                    "UIMA_overridden_resource__CONFIG",
                    new Object[] { name, aQualifiedContextName, registration.definingContext});
          } else {
            UIMAFramework.getLogger().logrb(Level.WARNING, ResourceManager_impl.class.getName(),
                    "initializeExternalResources", LOG_RESOURCE_BUNDLE,
                    "UIMA_duplicate_resource_name__WARNING",
                    new Object[] { name, registration.definingContext, aQualifiedContextName});
          }
        }
      }
    }
    // apply bindings
    ExternalResourceBinding[] bindings = aConfiguration.getExternalResourceBindings();
    for (int i = 0; i < bindings.length; i++) {
      ResourceRegistration registration = (ResourceRegistration) mInternalResourceRegistrationMap
              .get(bindings[i].getResourceName());
      if (registration == null) {
        throw new ResourceInitializationException(
                ResourceInitializationException.UNKNOWN_RESOURCE_NAME, new Object[] {
                    bindings[i].getResourceName(), bindings[i].getSourceUrlString() });
      }
      mResourceMap.put(aQualifiedContextName + bindings[i].getKey(), registration.resource);
      // record the link from key to resource class (for parameterized resources only)
      mParameterizedResourceImplClassMap.put(aQualifiedContextName + bindings[i].getKey(),
              mInternalParameterizedResourceImplClassMap.get(bindings[i].getResourceName()));

    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#resolveAndValidateResourceDependencies(org.apache.uima.resource.ExternalResourceDependency[],
   *      java.lang.String)
   */
  public void resolveAndValidateResourceDependencies(ExternalResourceDependency[] aDependencies,
          String aQualifiedContextName) throws ResourceInitializationException {
    for (int i = 0; i < aDependencies.length; i++) {
      // get resource
      String qname = aQualifiedContextName + aDependencies[i].getKey();
      Object resource = mResourceMap.get(qname);
      if (resource == null) {
        // no resource found
        // try to look up in classpath/datapath
        URL relativeUrl;
        try {
          relativeUrl = new URL("file", "", aDependencies[i].getKey());
        } catch (MalformedURLException e) {
          throw new ResourceInitializationException(e);
        }
        URL absUrl = getRelativePathResolver().resolveRelativePath(relativeUrl);
        if (absUrl != null) {
          // found - create a DataResource object and store it in the mResourceMap
          FileResourceSpecifier spec = new FileResourceSpecifier_impl();
          spec.setFileUrl(absUrl.toString());
          resource = UIMAFramework.produceResource(spec, null);
          mResourceMap.put(qname, resource);
        }
      }
      if (resource == null) // still no resource found - throw exception if required
      {
        if (!aDependencies[i].isOptional()) {
          throw new ResourceInitializationException(
                  ResourceInitializationException.RESOURCE_DEPENDENCY_NOT_SATISFIED, new Object[] {
                      aDependencies[i].getKey(), aDependencies[i].getSourceUrlString() });
        }
      } else {
        // make sure resource exists and implements the correct interface
        try {
          if (aDependencies[i].getInterfaceName() != null
                  && aDependencies[i].getInterfaceName().length() > 0) {
            // get UIMA extension ClassLoader if available
            ClassLoader cl = getExtensionClassLoader();
            Class<?> theInterface = null;

            if (cl != null) {
              // use UIMA extension ClassLoader to load the class
              theInterface = cl.loadClass(aDependencies[i].getInterfaceName());
            } else {
              // use application ClassLoader to load the class
              theInterface = Class.forName(aDependencies[i].getInterfaceName());
            }

            Class<? extends Resource> resourceClass = getResourceClass(qname);
            if (!theInterface.isAssignableFrom(resourceClass)) {
              throw new ResourceInitializationException(
                      ResourceInitializationException.RESOURCE_DOES_NOT_IMPLEMENT_INTERFACE,
                      new Object[] { qname, aDependencies[i].getInterfaceName(),
                          aDependencies[i].getSourceUrlString() });
            }
          }
        } catch (ClassNotFoundException e) {
          throw new ResourceInitializationException(
                  ResourceInitializationException.CLASS_NOT_FOUND, new Object[] {
                      aDependencies[i].getInterfaceName(), aDependencies[i].getSourceUrlString() });
        }
      }
    }
  }

  /**
   * Instantiates a resource and inserts it in the internal resource map.
   */
  private void registerResource(String aName, ExternalResourceDescription aResourceDescription,
          String aDefiningContext, Map<String, Object> aResourceInitParams) throws ResourceInitializationException {
    // add the relative path resolver to the resource init. params
    Map<String, Object> initParams = (aResourceInitParams == null) ? new HashMap<String, Object>() : new HashMap<String, Object>(
            aResourceInitParams);
    initParams.put(DataResource.PARAM_RELATIVE_PATH_RESOLVER, getRelativePathResolver());
    
    // determine if verification mode is on.  If so, we don't want to load the resource data
    boolean verificationMode = initParams.containsKey(AnalysisEngineImplBase.PARAM_VERIFICATION_MODE);
    
    // create the initial resource using the resource factory
    Object r = UIMAFramework.produceResource(aResourceDescription.getResourceSpecifier(),
            initParams);

    // load implementation class (if any) and ensure that it implements
    // SharedResourceObject
    String implementationName = aResourceDescription.getImplementationName();
    Class<?> implClass = null;
    if (implementationName != null && implementationName.length() > 0) {
      try {
        // get UIMA extension ClassLoader if available
        ClassLoader cl = getExtensionClassLoader();

        if (cl != null) {
          // use UIMA extension ClassLoader to load the class
          implClass = cl.loadClass(implementationName);
        } else {
          // use application ClassLoader to load the class
          implClass = Class.forName(implementationName);
        }
      } catch (ClassNotFoundException e) {
        throw new ResourceInitializationException(ResourceInitializationException.CLASS_NOT_FOUND,
                new Object[] { implementationName, aResourceDescription.getSourceUrlString() }, e);
      }

      if (!SharedResourceObject.class.isAssignableFrom(implClass)) {
        throw new ResourceInitializationException(
                ResourceInitializationException.NOT_A_SHARED_RESOURCE_OBJECT, new Object[] {
                    implementationName, aResourceDescription.getSourceUrlString() });
      }
    }

    // is this a DataResource?
    if (r instanceof DataResource) {
      // instantiate and load the resource object if there is one
      if (implClass != null) {
        try {
          SharedResourceObject sro = (SharedResourceObject) implClass.newInstance();
          if (!verificationMode) {
            sro.load((DataResource) r);
          }
          r = sro;
        } catch (InstantiationException e) {
          throw new ResourceInitializationException(
                  ResourceInitializationException.COULD_NOT_INSTANTIATE, new Object[] {
                      implClass.getName(), aResourceDescription.getSourceUrlString() }, e);
        } catch (IllegalAccessException e) {
          throw new ResourceInitializationException(
                  ResourceInitializationException.COULD_NOT_INSTANTIATE, new Object[] {
                      implClass.getName(), aResourceDescription.getSourceUrlString() }, e);
        }
      }
    }
    // is it a ParameterizedDataResource?
    else if (r instanceof ParameterizedDataResource) {
      // we can't load the SharedResourceObject now, but we need to remember
      // which class it is for later when we get a request with parameters
      mInternalParameterizedResourceImplClassMap.put(aName, implClass);
    } else
    // it is some other type of Resource
    {
      // it is an error to specify an implementation class in this case
      if (implClass != null) {
        throw new ResourceInitializationException(
                ResourceInitializationException.NOT_A_DATA_RESOURCE, new Object[] {
                    implClass.getName(), aName, r.getClass().getName(),
                    aResourceDescription.getSourceUrlString() });
      }
    }

    // put resource in internal map for later retrieval
    ResourceRegistration registration = new ResourceRegistration(r, aResourceDescription,
            aDefiningContext);
    mInternalResourceRegistrationMap.put(aName, registration);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.resource.ResourceManager#getCasManager()
   */
  public CasManager getCasManager() {
    synchronized(this) {
      if (mCasManager == null) {
        mCasManager = new CasManager_impl(this);
      }
      return mCasManager;
    }
  }
  
  

  /* (non-Javadoc)
   * @see org.apache.uima.resource.ResourceManager#setCasManager(org.apache.uima.resource.CasManager)
   */
  public void setCasManager(CasManager aCasManager) {
    synchronized(this) {
      if (mCasManager == null) {
        mCasManager = aCasManager;
      }
      else {
        throw new UIMA_IllegalStateException(
                UIMA_IllegalStateException.CANNOT_SET_CAS_MANAGER, new Object[0]);
      }
    }
  }
 
  // This method overridden by subclass for pear wrapper
  protected RelativePathResolver getRelativePathResolver() {
    return mRelativePathResolver;
  }

  static protected class ResourceRegistration { // make protected https://issues.apache.org/jira/browse/UIMA-2102
    Object resource;

    ExternalResourceDescription description;

    String definingContext;

    public ResourceRegistration(Object resource, ExternalResourceDescription description,
            String definingContext) {
      this.resource = resource;
      this.description = description;
      this.definingContext = definingContext;
    }
  }

  public Map<String, XMLizable> getImportCache() {
    return importCache;
  }
}
