/**
   * Resolves all spaces represented by the given query uri
   *
   * @param queryUri Data Spaces URI to query for; must be URI without space part being fully
   *     defined, i.e. not pointing to any concrete data space; result spaces for that queries must
   *     be suitable for user path.
   * @param ownerActiveObjectId Id of active object requesting this files, that will become owner of
   *     returned {@link DataSpacesFileObject} instances. May be <code>null</code>, which
   *     corresponds to anonymous (unimportant) owner.
   * @return
   * @throws FileSystemException
   */
  public Map<DataSpacesURI, DataSpacesFileObject> resolveSpaces(
      final DataSpacesURI queryUri, final String ownerActiveObjectId) throws FileSystemException {

    final Map<DataSpacesURI, DataSpacesFileObject> result =
        new HashMap<DataSpacesURI, DataSpacesFileObject>();
    if (logger.isDebugEnabled())
      logger.debug("[VFSMountManager] Spaces access request: " + queryUri);

    final Set<SpaceInstanceInfo> spaces = directory.lookupMany(queryUri);
    if (spaces != null) {
      for (final SpaceInstanceInfo space : spaces) {
        final DataSpacesURI spaceUri = space.getMountingPoint();
        if (!spaceUri.isSuitableForUserPath()) {
          logger.error(
              "[VFSMountManager] Resolved space is not suitable for user path: " + spaceUri);
          throw new IllegalArgumentException(
              "Resolved space is not suitable for user path: " + spaceUri);
        }
        try {
          ensureVirtualSpaceIsMounted(spaceUri, space);
        } catch (SpaceNotFoundException e) {
          ProActiveLogger.logImpossibleException(logger, e);
          throw new RuntimeException(e);
        }
        result.put(spaceUri, doResolveFile(spaceUri, ownerActiveObjectId, null));
      }
    }
    return result;
  }
  /**
   * Ensures that the provided virtual space is mounted (at least one VFS file system is mounted)
   *
   * @param spaceURI uri of the virtual space
   * @param info dataspace info
   * @throws SpaceNotFoundException
   * @throws FileSystemException
   */
  private void ensureVirtualSpaceIsMounted(final DataSpacesURI spaceURI, SpaceInstanceInfo info)
      throws SpaceNotFoundException, FileSystemException {

    final boolean mounted;
    DataSpacesURI spacePart = spaceURI.getSpacePartOnly();

    try {
      readLock.lock();
      mounted = mountedSpaces.containsKey(spacePart);
    } finally {
      readLock.unlock();
    }

    if (!mounted) {
      if (info == null) {
        info = directory.lookupOne(spaceURI);
      }
      if (info == null) {
        logger.warn(
            "[VFSMountManager] Could not find data space in spaces directory: " + spacePart);
        throw new SpaceNotFoundException(
            "Requested data space is not registered in spaces directory.");
      }

      try {
        readLock.lock();
        if (mountedSpaces.containsKey(spacePart) && (mountedSpaces.get(spacePart).size() > 0))
          return;
      } finally {
        readLock.unlock();
      }
      mountFirstAvailableFileSystem(info);
    }
  }
  /**
   * Unmount all file systems for the given dataspace
   *
   * @param spaceUri dataspace uri
   */
  private void unmountAllFileSystems(final DataSpacesURI spaceUri) {

    DataSpacesURI spacePart = spaceUri.getSpacePartOnly();

    final ConcurrentHashMap<String, FileObject> spaceRoots = mountedSpaces.remove(spacePart);

    VFSMountManagerHelper.closeFileSystems(spaceRoots.keySet());
  }
  /**
   * resolves the given virtual uri into a DataSpaceFileObject. The VFS root is forced to the
   * spaceRootFOUri parameter. Mount the space if necessary
   *
   * @param queryUri Data Spaces URI to get access to
   * @param ownerActiveObjectId Id of active object requesting this file, that will become owner of
   *     returned {@link DataSpacesFileObject} instance. May be <code>null</code>, which corresponds
   *     to anonymous (unimportant) owner.
   * @param spaceRootFOUri forces the use of the provided VFS space root (in case there are several
   *     roots)
   * @return
   * @throws FileSystemException
   * @throws SpaceNotFoundException
   */
  public DataSpacesFileObject resolveFile(
      final DataSpacesURI queryUri, final String ownerActiveObjectId, String spaceRootFOUri)
      throws FileSystemException, SpaceNotFoundException {
    if (logger.isDebugEnabled()) logger.debug("[VFSMountManager] File access request: " + queryUri);

    if (!queryUri.isSuitableForUserPath()) {
      logger.error(
          "[VFSMountManager] Requested URI " + queryUri + " is not suitable for user path");
      throw new IllegalArgumentException(
          "Requested URI " + queryUri + " is not suitable for user path");
    }

    final DataSpacesURI spaceURI = queryUri.getSpacePartOnly();

    ensureVirtualSpaceIsMounted(spaceURI, null);

    return doResolveFile(queryUri, ownerActiveObjectId, spaceRootFOUri);
  }
  /**
   * Makes sure that the provided file system is mounted for the given dataspace (identified by its
   * root url)
   *
   * @param mountingPoint dataspace uri
   * @param spaceRootFOUri file system root
   * @return true if the file system is mounted
   * @throws FileSystemException
   */
  private boolean ensureFileSystemIsMounted(
      final DataSpacesURI mountingPoint, final String spaceRootFOUri) throws FileSystemException {
    ConcurrentHashMap<String, FileObject> fileSystems = null;
    DataSpacesURI spacePart = mountingPoint.getSpacePartOnly();
    try {
      readLock.lock();
      fileSystems = mountedSpaces.get(spacePart);
      // already mounted
      if (fileSystems.get(spaceRootFOUri) != null) {
        return true;
      }
    } finally {
      readLock.unlock();
    }
    logger.debug("[VFSMountManager] Request mounting VFS root = " + spaceRootFOUri);
    FileObject mountedRoot;
    try {
      mountedRoot = VFSMountManagerHelper.mount(spaceRootFOUri);

      // the fs is accessible
      try {
        writeLock.lock();
        fileSystems.put(spaceRootFOUri, mountedRoot);
        mountedSpaces.put(spacePart, fileSystems);
      } finally {
        writeLock.unlock();
      }
      if (logger.isDebugEnabled())
        logger.debug(
            String.format(
                "[VFSMountManager] Mounted space: %s (access URL: %s)", spacePart, spaceRootFOUri));
      return true;

    } catch (org.apache.commons.vfs.FileSystemException x) {
      String err =
          String.format(
              "[VFSMountManager] Could not access URL %s to mount %s", spaceRootFOUri, spacePart);
      logger.info(err);
      removeSpaceRootUri(spacePart, spaceRootFOUri);
      throw new FileSystemException(err, x);
    }
  }
  @Test
  public void test()
      throws ApplicationAlreadyRegisteredException, WrongApplicationIdException,
          SpaceAlreadyRegisteredException, IllegalArgumentException {

    Set<SpaceInstanceInfo> spaces = new HashSet<SpaceInstanceInfo>();
    Set<Long> appsRegistered;

    spaces.add(spaceInstanceInput1);
    spaces.add(spaceInstanceInput2);
    spaces.add(spaceInstanceOutput1);
    spaces.add(spaceInstanceOutput2);

    assertFalse(stub.isApplicationIdRegistered(MAIN_APPID));
    // TEST REGISTER APP
    logger.info("Test register application");
    stub.registerApplication(MAIN_APPID, spaces);

    // check if everything has been registered
    logger.info("Checking that application has been registered");
    assertTrue(stub.isApplicationIdRegistered(MAIN_APPID));
    appsRegistered = stub.getRegisteredApplications();
    appsRegistered.contains(MAIN_APPID);

    // TEST LOOKUP FIRST
    logger.info("Test Lookup First");
    assertIsSpaceRegistered(spaceInstanceInput1);
    assertIsSpaceRegistered(spaceInstanceInput2);
    assertIsSpaceRegistered(spaceInstanceOutput1);
    assertIsSpaceRegistered(spaceInstanceOutput2);

    // TEST LOOKUP ALL
    logger.info("Test Lookup All");
    final DataSpacesURI query = DataSpacesURI.createURI(MAIN_APPID);
    final Set<SpaceInstanceInfo> actual = stub.lookupMany(query);
    assertEquals(spaces, actual);

    // TEST UNREGISTER
    logger.info("Test unregister");
    assertTrue(stub.unregister(spaceInstanceInput1.getMountingPoint()));
    assertTrue(stub.unregister(spaceInstanceOutput1.getMountingPoint()));

    // TEST LOOKUP FIRST WITH NULL ANSWER
    logger.info("Test looup first with null answer");
    assertIsSpaceUnregistered(spaceInstanceInput1);
    assertIsSpaceUnregistered(spaceInstanceOutput1);

    // TEST REGISTER
    logger.info("Test register");
    stub.register(spaceInstanceInput1);
    stub.register(spaceInstanceOutput1);

    // TEST EXCEPTION WHEN SPACE ALREADY REGISTERED
    logger.info("Test Exception when space already registered");
    try {
      stub.register(spaceInstanceInput1);
      fail("Exception expected");
    } catch (SpaceAlreadyRegisteredException e) {
    } catch (Exception e) {
      fail("Expected exception of different type");
    }

    // TEST EXCEPTION WHEN APP NOT REGISTERED
    logger.info("Test Exception when app not registered");
    try {
      stub.register(spaceInstanceInput1b);
      fail("Exception expected");
    } catch (WrongApplicationIdException e) {
    } catch (Exception e) {
      fail("Expected exception of different type");
    }

    // TEST EXCEPTION WHEN APP ALREADY REGISTERED
    logger.info("Test Exception when app already registered");
    try {
      stub.registerApplication(MAIN_APPID, null);
      fail("Exception expected");
    } catch (ApplicationAlreadyRegisteredException e) {
    } catch (Exception e) {
      fail("Expected exception of different type");
    }

    // TEST UNREGISTER APP
    logger.info("Test unregister");
    stub.unregisterApplication(MAIN_APPID);
  }
  /**
   * Internal method for resolving a file, will mount the file system if it is not mounted yet
   *
   * @param uri virtual uri of the file
   * @param ownerActiveObjectId Id of active object requesting this file
   * @param spaceRootFOUri root file system to use
   * @return
   * @throws FileSystemException
   */
  private DataSpacesFileObject doResolveFile(
      final DataSpacesURI uri, final String ownerActiveObjectId, String spaceRootFOUri)
      throws FileSystemException {

    DataSpacesURI spacePart = uri.getSpacePartOnly();

    if (spaceRootFOUri != null) {
      ensureFileSystemIsMounted(spacePart, spaceRootFOUri);
    } else {
      try {
        readLock.lock();
        LinkedHashSet<String> los = accessibleFileObjectUris.get(spacePart);
        spaceRootFOUri = los.iterator().next();
      } finally {
        readLock.unlock();
      }
      ensureFileSystemIsMounted(spacePart, spaceRootFOUri);
    }

    final String relativeToSpace = uri.getRelativeToSpace();
    try {
      readLock.lock();

      if (!mountedSpaces.containsKey(spacePart)) {
        throw new FileSystemException("Could not access file that should exist (be mounted)");
      }

      final ConcurrentHashMap<String, FileObject> spaceRoots = mountedSpaces.get(spacePart);
      FileObject spaceRoot = spaceRoots.get(spaceRootFOUri);
      FileName dataSpaceVFSFileName = null;

      final FileObject file;
      // the dataspace "File name" (it is actually a File Path) is computed using the Virtual Space
      // root
      if (dataSpaceVFSFileName == null) {
        dataSpaceVFSFileName = spaceRoot.getName();
      }
      try {
        if (relativeToSpace == null) file = spaceRoot;
        else file = spaceRoot.resolveFile(relativeToSpace);
        final DataSpacesLimitingFileObject limitingFile =
            new DataSpacesLimitingFileObject(
                file, spacePart, spaceRoot.getName(), ownerActiveObjectId);
        return new VFSFileObjectAdapter(
            limitingFile,
            spacePart,
            dataSpaceVFSFileName,
            new ArrayList<String>(accessibleFileObjectUris.get(spacePart)),
            spaceRootFOUri,
            this,
            ownerActiveObjectId);
      } catch (org.apache.commons.vfs.FileSystemException x) {
        logger.error("[VFSMountManager] Could not access file within a space: " + uri);

        throw new FileSystemException(x);
      } catch (FileSystemException e) {
        ProActiveLogger.logImpossibleException(logger, e);
        throw new ProActiveRuntimeException(e);
      }

    } finally {
      readLock.unlock();
    }
  }
  /**
   * Mounts the first available VFS file system on the given dataspace
   *
   * @param spaceInfo space information
   * @throws FileSystemException if no file system could be mounted
   */
  private void mountFirstAvailableFileSystem(final SpaceInstanceInfo spaceInfo)
      throws FileSystemException {

    final DataSpacesURI mountingPoint = spaceInfo.getMountingPoint();

    try {
      writeLock.lock();
      if (!mountedSpaces.containsKey(mountingPoint)) {
        mountedSpaces.put(mountingPoint, new ConcurrentHashMap<String, FileObject>());
      }
      ConcurrentHashMap<String, FileObject> fileSystems = mountedSpaces.get(mountingPoint);

      if (spaceInfo.getUrls().size() == 0) {
        throw new IllegalStateException("Empty Space configuration");
      }

      DataSpacesURI spacePart = mountingPoint.getSpacePartOnly();
      ArrayList<String> urls = new ArrayList<String>(spaceInfo.getUrls());
      if (urls.size() == 1) {
        urls.add(
            0, Utils.getLocalAccessURL(urls.get(0), spaceInfo.getPath(), spaceInfo.getHostname()));
      }

      logger.debug("[VFSMountManager] Request mounting VFS root list : " + urls);

      try {
        VFSMountManagerHelper.mountAny(urls, fileSystems);

        if (!accessibleFileObjectUris.containsKey(mountingPoint)) {
          LinkedHashSet<String> srl = new LinkedHashSet<String>();
          accessibleFileObjectUris.put(mountingPoint, srl);
        }

        LinkedHashSet<String> srl = accessibleFileObjectUris.get(mountingPoint);

        for (String uri : urls) {
          if (fileSystems.containsKey(uri)) {
            srl.add(uri);
          }
        }
        if (srl.isEmpty()) {
          throw new IllegalStateException(
              "Invalid empty size list when trying to mount "
                  + urls
                  + " mounted map content is "
                  + fileSystems);
        }
        accessibleFileObjectUris.put(mountingPoint, srl);

        if (logger.isDebugEnabled())
          logger.debug(
              String.format(
                  "[VFSMountManager] Mounted space: %s (access URL: %s)", spacePart, srl));

        mountedSpaces.put(mountingPoint, fileSystems);

      } catch (org.apache.commons.vfs.FileSystemException e) {
        mountedSpaces.remove(mountingPoint);
        throw new FileSystemException(
            "An error occurred while trying to mount " + spaceInfo.getName(), e);
      }
    } finally {
      writeLock.unlock();
    }
  }