[502065] Enhance user file localization for FileUserManager

* UserManager gets the users in a file on server. Previously, the
  file path could be absolute or relative to the application.
* Now the file path can also be relative to the config folder
  (where cdo-server.xml is) by specifying "@config/...".
* File validity checks are removed to support file creation during
  addUser().
* With -Dnet4j.security.FileUserManager.fallBackToConfigFolder=true
  a relative path is resolved in both the application's current folder
  and the config folder (in this order).

Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=502065
Change-Id: I63b559c93cb67ccfc656018fe0136511e7434c03
Signed-off-by: Laurent Fasani <laurent.fasani@obeo.fr>
Signed-off-by: Eike Stepper <stepper@esc-net.de>
diff --git a/plugins/org.eclipse.emf.cdo.examples.installer/examples/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml b/plugins/org.eclipse.emf.cdo.examples.installer/examples/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
index 4ee0904..4f9f109 100644
--- a/plugins/org.eclipse.emf.cdo.examples.installer/examples/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
+++ b/plugins/org.eclipse.emf.cdo.examples.installer/examples/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
@@ -31,7 +31,7 @@
     -->
 
     <!-- Example http://bugs.eclipse.org/302775
-			<authenticator type="file" description="_database/repo1.users"/>
+			<authenticator type="file" description="@config/repo1.users"/>
     -->
 
     <!-- Example http://bugs.eclipse.org/345431
diff --git a/plugins/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml b/plugins/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
index 4ee0904..4f9f109 100644
--- a/plugins/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
+++ b/plugins/org.eclipse.emf.cdo.examples.master/config/cdo-server.xml
@@ -31,7 +31,7 @@
     -->
 
     <!-- Example http://bugs.eclipse.org/302775
-			<authenticator type="file" description="_database/repo1.users"/>
+			<authenticator type="file" description="@config/repo1.users"/>
     -->
 
     <!-- Example http://bugs.eclipse.org/345431
diff --git a/plugins/org.eclipse.emf.cdo.server.product/config/cdo-server.xml b/plugins/org.eclipse.emf.cdo.server.product/config/cdo-server.xml
index 02168ad..28ff34c 100644
--- a/plugins/org.eclipse.emf.cdo.server.product/config/cdo-server.xml
+++ b/plugins/org.eclipse.emf.cdo.server.product/config/cdo-server.xml
@@ -31,7 +31,7 @@
     -->
 
     <!-- Example http://bugs.eclipse.org/302775
-			<authenticator type="file" description="_database/repo1.users"/>
+			<authenticator type="file" description="@config/repo1.users"/>
     -->
 
     <!-- Example http://bugs.eclipse.org/345431
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileAuthenticatorFactory.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileAuthenticatorFactory.java
index 86dc181..3dbfe68 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileAuthenticatorFactory.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileAuthenticatorFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012 Eike Stepper (Berlin, Germany) and others.
+ * Copyright (c) 2012-2016 Eike Stepper (Berlin, Germany) and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
index a57c627..d082c89 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/FileUserManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008-2012 Eike Stepper (Berlin, Germany) and others.
+ * Copyright (c) 2008-2012, 2016 Eike Stepper (Berlin, Germany) and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -10,12 +10,13 @@
  */
 package org.eclipse.net4j.util.security;
 
+import org.eclipse.net4j.util.StringUtil;
 import org.eclipse.net4j.util.io.IORuntimeException;
 import org.eclipse.net4j.util.io.IOUtil;
+import org.eclipse.net4j.util.om.OMPlatform;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,8 +30,14 @@
  */
 public class FileUserManager extends UserManager
 {
+  private static final boolean FALL_BACK_TO_CONFIG_FOLDER = OMPlatform.INSTANCE.isProperty("net4j.security.FileUserManager.fallBackToConfigFolder");
+
+  private static final String CONFIG_FOLDER_PREFIX = "@config/";
+
   protected String fileName;
 
+  private File file;
+
   public FileUserManager()
   {
   }
@@ -40,55 +47,102 @@
     return fileName;
   }
 
+  /**
+   * Sets the name of the file to be used by this user manager.
+   * <p>
+   * The {@link #getFile() file} is resolved in the following order:
+   * <ol>
+   * <li> If it starts with the path segment &quot;@config&quot; the subsequent path segments are interpreted as relative to the {@link OMPlatform#getConfigFolder() config folder}.
+   * <li> If it is relative it is interpreted as relative to the application's current directory.
+   * <li> Otherwise it is interpreted as absolute.
+   * </ol>
+   * The resolved file is not required to exist when this user manager is activated. In this case it will be created when {@link #addUser(String, char[]) addUser()}
+   * or {@link #removeUser(String) removeUser()} are called.
+   * <p>
+   * With &quot;-Dnet4j.security.FileUserManager.fallBackToConfigFolder=true&quot; a relative path is resolved in both the application's current folder
+   * and the config folder (in this order).
+   */
   public void setFileName(String fileName)
   {
+    checkInactive();
     this.fileName = fileName;
   }
 
+  /**
+   * @since 3.7
+   */
+  public final File getFile()
+  {
+    return file;
+  }
+
+  /**
+   * @since 3.7
+   */
+  protected File resolveFile(String fileName) throws Exception
+  {
+    if (StringUtil.isEmpty(fileName))
+    {
+      return null;
+    }
+
+    if (fileName.replace('\\', '/').startsWith(CONFIG_FOLDER_PREFIX))
+    {
+      return OMPlatform.INSTANCE.getConfigFile(fileName.substring(CONFIG_FOLDER_PREFIX.length()));
+    }
+
+    File file = new File(fileName);
+
+    if (FALL_BACK_TO_CONFIG_FOLDER && !file.isFile())
+    {
+      File configFile = OMPlatform.INSTANCE.getConfigFile(fileName);
+      if (configFile != null && configFile.isFile())
+      {
+        return configFile;
+      }
+    }
+
+    return file;
+  }
+
   @Override
   protected void doBeforeActivate() throws Exception
   {
     super.doBeforeActivate();
-    if (fileName == null)
-    {
-      throw new IllegalStateException("fileName == null"); //$NON-NLS-1$
-    }
 
-    File file = new File(fileName);
-    if (file.exists())
+    file = resolveFile(fileName);
+    if (file != null)
     {
-      if (!file.isFile())
-      {
-        throw new IllegalStateException("Not a file: " + fileName); //$NON-NLS-1$
-      }
+      file = file.getCanonicalFile();
     }
-    else
-    {
-      throw new FileNotFoundException("User manager file not found: " + fileName);
-    }
+  }
+
+  @Override
+  protected void doDeactivate() throws Exception
+  {
+    super.doDeactivate();
+    file = null;
   }
 
   @Override
   protected void load(Map<String, char[]> users) throws IORuntimeException
   {
-    File file = new File(fileName);
-    if (!file.exists())
+    if (file != null && file.isFile())
     {
-      return;
-    }
+      FileInputStream stream = IOUtil.openInputStream(file);
 
-    FileInputStream stream = IOUtil.openInputStream(new File(fileName));
-    try
-    {
-      load(users, stream);
-    }
-    catch (IOException ex)
-    {
-      throw new IORuntimeException(ex);
-    }
-    finally
-    {
-      IOUtil.closeSilent(stream);
+      try
+      {
+        load(users, stream);
+      }
+      catch (IOException ex)
+      {
+        throw new IORuntimeException(ex);
+      }
+      finally
+      {
+        IOUtil.closeSilent(stream);
+      }
     }
   }
 
@@ -96,6 +150,7 @@
   {
     Properties properties = new Properties();
     properties.load(stream);
+
     for (Entry<Object, Object> entry : properties.entrySet())
     {
       String userID = (String)entry.getKey();
@@ -107,30 +162,30 @@
   @Override
   protected void save(Map<String, char[]> users) throws IORuntimeException
   {
-    File file = new File(fileName);
-    if (!file.exists())
+    if (file != null)
     {
-      return;
-    }
+      file.getParentFile().mkdirs();
+      FileOutputStream stream = IOUtil.openOutputStream(file);
 
-    FileOutputStream stream = IOUtil.openOutputStream(new File(fileName));
-    try
-    {
-      save(users, stream);
-    }
-    catch (IOException ex)
-    {
-      throw new IORuntimeException(ex);
-    }
-    finally
-    {
-      IOUtil.closeSilent(stream);
+      try
+      {
+        save(users, stream);
+      }
+      catch (IOException ex)
+      {
+        throw new IORuntimeException(ex);
+      }
+      finally
+      {
+        IOUtil.closeSilent(stream);
+      }
     }
   }
 
   protected void save(Map<String, char[]> users, FileOutputStream stream) throws IOException
   {
     Properties properties = new Properties();
+
     for (Entry<String, char[]> entry : users.entrySet())
     {
       properties.put(entry.getKey(), new String(entry.getValue()));
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
index d77d7bb..8478399 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/security/UserManager.java
@@ -47,7 +47,7 @@
   /**
    * @since 3.3
    */
-  public char[] getPassword(String userID)
+  public synchronized char[] getPassword(String userID)
   {
     return users.get(userID);
   }