You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@manifoldcf.apache.org by kw...@apache.org on 2011/07/18 23:40:20 UTC

svn commit: r1148064 - in /incubator/lcf/branches/CONNECTORS-221: ./ connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/

Author: kwright
Date: Mon Jul 18 21:40:19 2011
New Revision: 1148064

URL: http://svn.apache.org/viewvc?rev=1148064&view=rev
Log:
Add CMIS authority connector

Added:
    incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java   (with props)
    incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java   (with props)
Modified:
    incubator/lcf/branches/CONNECTORS-221/build.xml

Modified: incubator/lcf/branches/CONNECTORS-221/build.xml
URL: http://svn.apache.org/viewvc/incubator/lcf/branches/CONNECTORS-221/build.xml?rev=1148064&r1=1148063&r2=1148064&view=diff
==============================================================================
--- incubator/lcf/branches/CONNECTORS-221/build.xml (original)
+++ incubator/lcf/branches/CONNECTORS-221/build.xml Mon Jul 18 21:40:19 2011
@@ -1434,6 +1434,7 @@
             <fileset dir="connectors/cmis/dist/lib"/>
         </copy>
         <replace file="dist/example/connectors.xml" token="&lt;!-- Add your repository connectors here --&gt;" value="&lt;!-- Add your repository connectors here --&gt;&#0010;  &lt;repositoryconnector name=&quot;CMIS&quot; class=&quot;org.apache.manifoldcf.crawler.connectors.cmis.CmisRepositoryConnector&quot;/&gt;"/>
+        <replace file="dist/example/connectors.xml" token="&lt;!-- Add your authority connectors here --&gt;" value="&lt;!-- Add your authority connectors here --&gt;&#0010;  &lt;authorityconnector name=&quot;CMIS&quot; class=&quot;org.apache.manifoldcf.crawler.connectors.cmis.CmisAuthorityConnector&quot;/&gt;"/>
     </target>
     
     <target name="deliver-documentum-example" depends="deliver-framework-example,calculate-documentum-condition" if="documentum.include">

Added: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java
URL: http://svn.apache.org/viewvc/incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java?rev=1148064&view=auto
==============================================================================
--- incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java (added)
+++ incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java Mon Jul 18 21:40:19 2011
@@ -0,0 +1,799 @@
+/**
+ * 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.manifoldcf.crawler.connectors.cmis;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.chemistry.opencmis.client.api.Session;
+import org.apache.chemistry.opencmis.client.api.SessionFactory;
+import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
+import org.apache.chemistry.opencmis.commons.SessionParameter;
+import org.apache.chemistry.opencmis.commons.enums.BindingType;
+import org.apache.commons.lang.StringUtils;
+import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
+import org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector;
+import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
+import org.apache.manifoldcf.core.interfaces.CacheManagerFactory;
+import org.apache.manifoldcf.core.interfaces.ConfigParams;
+import org.apache.manifoldcf.core.interfaces.ICacheCreateHandle;
+import org.apache.manifoldcf.core.interfaces.ICacheDescription;
+import org.apache.manifoldcf.core.interfaces.ICacheHandle;
+import org.apache.manifoldcf.core.interfaces.ICacheManager;
+import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
+import org.apache.manifoldcf.core.interfaces.IPostParameters;
+import org.apache.manifoldcf.core.interfaces.IThreadContext;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+import org.apache.manifoldcf.core.interfaces.StringSet;
+import org.apache.manifoldcf.crawler.system.Logging;
+import org.apache.manifoldcf.ui.util.Encoder;
+
+/**
+ * 
+ * The CMIS Authority Connector is based only on a regular expression checker, because the CMIS specification 
+ * (and the Apache Chemistry implementation) doesn't have any exposed method to get info about users.
+ * 
+ * For the configuration we can assume that we could have different users for any CMIS repository that 
+ * could have the same endpoint. That's why for this connector is required the Repository ID.
+ * 
+ * @author Piergiorgio Lucidi
+ * 
+ */
+public class CmisAuthorityConnector extends BaseAuthorityConnector {
+
+  public static final String CONFIG_PARAM_USERNAME = "username";
+  public static final String CONFIG_PARAM_PASSWORD = "password";
+  public static final String CONFIG_PARAM_ENDPOINT = "endpoint";
+  public static final String CONFIG_PARAM_REPOSITORY_ID = "repositoryId";
+  
+  /** User name mapping parameter */
+  protected static final String CONFIG_PARAM_USERMAPPING = "usermapping";
+  
+  protected static final String CONFIG_PARAM_USERNAME_REGEXP = "usernameregexp";
+  protected static final String CONFIG_PARAM_USER_TRANSLATION = "usertranslation";
+  
+  private static final String TAB_NAME_REPOSITORY = "Repository";
+  private static final String TAB_NAME_USER_MAPPING = "User Mapping";
+  
+  private static final String DEFAULT_VALUE_USERNAME = "admin";
+  private static final String DEFAULT_VALUE_PASSWORD = "pwd";
+  private static final String DEFAULT_VALUE_ENDPOINT = "http://localhost:8080/cmis/";
+  private static final String DEFAULT_VALUE_REPOSITORY_ID = "uuid";
+
+  
+  /**
+   * CMIS Session handle
+   */
+  Session session = null;
+  
+  protected String username = null;
+  protected String password = null;
+  protected String endpoint = null;
+  protected String repositoryId = null;
+
+  protected SessionFactory factory = SessionFactoryImpl.newInstance();
+  protected Map<String, String> parameters = new HashMap<String, String>();
+
+  protected static final long timeToRelease = 300000L;
+  protected long lastSessionFetch = -1L;
+  
+  /** The cache manager. */
+  protected ICacheManager cacheManager = null;
+  
+  /** Match map for username mapping */
+  protected MatchMap matchMap = null;
+  
+  protected static long responseLifetime = 60000L;
+  protected static int LRUsize = 1000;
+  protected static StringSet emptyStringSet = new StringSet();
+  
+  /** This is the active directory global deny token.  This should be ingested with all documents. */
+  public static final String GLOBAL_DENY_TOKEN = "DEAD_AUTHORITY";
+  
+  /** Unreachable CMIS */
+  private static final AuthorizationResponse unreachableResponse = new AuthorizationResponse(
+    new String[]{GLOBAL_DENY_TOKEN},AuthorizationResponse.RESPONSE_UNREACHABLE);
+  
+  /** User not found */
+  private static final AuthorizationResponse userNotFoundResponse = new AuthorizationResponse(
+    new String[]{GLOBAL_DENY_TOKEN},AuthorizationResponse.RESPONSE_USERNOTFOUND);
+
+  /** Set thread context.
+   */
+   @Override
+   public void setThreadContext(IThreadContext tc)
+     throws ManifoldCFException {
+     super.setThreadContext(tc);
+     cacheManager = CacheManagerFactory.make(tc);
+   }
+   
+   /** Clear thread context.
+   */
+   @Override
+   public void clearThreadContext() {
+     super.clearThreadContext();
+     cacheManager = null;
+   }
+  
+  public CmisAuthorityConnector() {
+    super();
+  }
+  
+  protected class CheckConnectionThread extends Thread {
+    protected Throwable exception = null;
+
+    public CheckConnectionThread() {
+      super();
+      setDaemon(true);
+    }
+
+    public void run() {
+      try {
+        session.getRepositoryInfo();
+      } catch (Throwable e) {
+        this.exception = e;
+      }
+    }
+
+    public Throwable getException() {
+      return exception;
+    }
+
+  }
+  
+  protected class GetSessionThread extends Thread {
+    protected Throwable exception = null;
+
+    public GetSessionThread() {
+      super();
+      setDaemon(true);
+    }
+
+    public void run() {
+      try {
+        // Create a session
+        parameters.clear();
+
+        // user credentials
+        parameters.put(SessionParameter.USER, username);
+        parameters.put(SessionParameter.PASSWORD, password);
+
+        // connection settings
+        parameters.put(SessionParameter.ATOMPUB_URL, endpoint);
+        parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
+
+        // get a session from the repository specified in the
+        // configuration with its own ID
+        parameters.put(SessionParameter.REPOSITORY_ID, repositoryId);
+        session = factory.createSession(parameters);
+        
+      } catch (Throwable e) {
+        this.exception = e;
+      }
+    }
+
+    public Throwable getException() {
+      return exception;
+    }
+  }
+  
+  protected class DestroySessionThread extends Thread {
+    protected Throwable exception = null;
+
+    public DestroySessionThread() {
+      super();
+      setDaemon(true);
+    }
+
+    public void run() {
+      try {
+        session = null;
+      } catch (Throwable e) {
+        this.exception = e;
+      }
+    }
+
+    public Throwable getException() {
+      return exception;
+    }
+
+  }
+  
+  /**
+   * This method create a new CMIS session for a CMIS repository, if the
+   * repositoryId is not provided in the configuration, the connector will
+   * retrieve all the repositories exposed for this endpoint the it will start
+   * to use the first one.
+   */
+  @Override
+  public void connect(ConfigParams configParams) {
+    super.connect(configParams);
+    username = params.getParameter(CONFIG_PARAM_USERNAME);
+    password = params.getParameter(CONFIG_PARAM_PASSWORD);
+    endpoint = params.getParameter(CONFIG_PARAM_ENDPOINT);
+    repositoryId = params.getParameter(CONFIG_PARAM_REPOSITORY_ID);
+    String userNameMapping = params.getParameter(CONFIG_PARAM_USERMAPPING);
+    matchMap = new MatchMap(userNameMapping);
+  }
+  
+  /**
+   * Output the configuration body section. This method is called in the body
+   * section of the authority connector's configuration page. Its purpose is to
+   * present the required form elements for editing. The coder can presume that
+   * the HTML that is output from this configuration will be within appropriate
+   * <html>, <body>, and <form> tags. The name of the form is "editconnection".
+   * 
+   * @param threadContext
+   *          is the local thread context.
+   * @param out
+   *          is the output to which any HTML should be sent.
+   * @param parameters
+   *          are the configuration parameters, as they currently exist, for
+   *          this connection being configured.
+   * @param tabName
+   *          is the current tab name.
+   */
+  @Override
+  public void outputConfigurationBody(IThreadContext threadContext,
+      IHTTPOutput out, ConfigParams parameters, String tabName)
+      throws ManifoldCFException, IOException {
+
+    String username = parameters.getParameter(CONFIG_PARAM_USERNAME);
+    String password = parameters.getParameter(CONFIG_PARAM_PASSWORD);
+    String endpoint = parameters.getParameter(CONFIG_PARAM_ENDPOINT);
+    String repositoryId = parameters.getParameter(CONFIG_PARAM_REPOSITORY_ID);
+    
+    if(StringUtils.isEmpty(username))
+      username = DEFAULT_VALUE_USERNAME;
+    if(StringUtils.isEmpty(password))
+      password = DEFAULT_VALUE_PASSWORD;
+    if(StringUtils.isEmpty(endpoint))
+      endpoint = DEFAULT_VALUE_ENDPOINT;
+    if(StringUtils.isEmpty(repositoryId))
+      repositoryId = DEFAULT_VALUE_REPOSITORY_ID;
+    
+    String userMappingString = parameters.getParameter(CONFIG_PARAM_USERMAPPING);
+    MatchMap localMap;
+    if (StringUtils.isNotEmpty(userMappingString)){
+      localMap = new MatchMap(userMappingString);
+    } else {
+      localMap = new MatchMap();
+      localMap.appendMatchPair("(.*)","$(1)");
+    }
+    String usernameRegexp = localMap.getMatchString(0);
+    String userTranslation = localMap.getReplaceString(0);
+    
+    if (tabName.equals(TAB_NAME_REPOSITORY))
+    {
+    out.print("<table class=\"displaytable\">\n"
+        + "  <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n");
+    out.print(
+        "<tr><td class=\"description\"><nobr>Username:</nobr></td>\n"
+       +"<td class=\"value\"><input type=\"text\" name=\""
+        + CONFIG_PARAM_USERNAME + "\" value=\""+Encoder.attributeEscape(username)+"\"/></td></tr>\n");
+    out.print("<tr><td class=\"description\"><nobr>Password:</nobr></td>" +
+        "<td class=\"value\"><input type=\"password\" name=\""
+        + CONFIG_PARAM_PASSWORD + "\" value=\""+Encoder.attributeEscape(password)+"\"/></td></tr>\n");
+    out.print("<tr><td class=\"description\"><nobr>Endpoint:</nobr></td>" +
+        "<td class=\"value\"><input type=\"text\" name=\""
+        + CONFIG_PARAM_ENDPOINT + "\" value=\""+Encoder.attributeEscape(endpoint)+"\" size=\"50\"/></td></tr>\n");
+    out.print("<tr><td class=\"description\"><nobr>Repository ID:</nobr></td>" +
+        "<td class=\"value\"><input type=\"text\" name=\""
+        + CONFIG_PARAM_REPOSITORY_ID + "\" value=\""+Encoder.attributeEscape(repositoryId)+"\"/></td></tr>\n");
+    out.print("</table>\n");
+    }
+    else
+    {
+      out.print("<input type=\"hidden\" name=\""+CONFIG_PARAM_USERNAME+"\" value=\""+Encoder.attributeEscape(username)+"\"/>\n");
+      out.print("<input type=\"hidden\" name=\""+CONFIG_PARAM_PASSWORD+"\" value=\""+Encoder.attributeEscape(password)+"\"/>\n");
+      out.print("<input type=\"hidden\" name=\""+CONFIG_PARAM_ENDPOINT+"\" value=\""+Encoder.attributeEscape(endpoint)+"\"/>\n");
+      out.print("<input type=\"hidden\" name=\""+CONFIG_PARAM_REPOSITORY_ID+"\" value=\""+Encoder.attributeEscape(repositoryId)+"\"/>\n");
+    }
+    
+    if (tabName.equals(TAB_NAME_USER_MAPPING))
+    {
+      out.print(
+"<table class=\"displaytable\">\n"+
+"  <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
+"  <tr>\n"+
+"    <td class=\"description\"><nobr>User mapping:</nobr></td>\n"+
+"    <td class=\"value\">\n"+
+"      <input type=\"text\" size=\"32\" name=\""+CONFIG_PARAM_USERNAME_REGEXP+"\" value=\""+
+  Encoder.attributeEscape(usernameRegexp)+"\"/> ==&gt; \n"+
+"      <input type=\"text\" size=\"32\" name=\""+CONFIG_PARAM_USER_TRANSLATION+"\" value=\""+
+  Encoder.attributeEscape(userTranslation)+"\"/>\n"+
+"    </td>\n"+
+"  </tr>\n"+
+"</table>\n"
+      );
+    }
+    else
+    {
+      out.print(
+"<input type=\"hidden\" name=\"usernameregexp\" value=\""+
+  Encoder.attributeEscape(usernameRegexp)+"\"/>\n"+
+"<input type=\"hidden\" name=\"usertranslation\" value=\""+
+  Encoder.attributeEscape(userTranslation)+"\"/>\n"
+      );
+    }
+
+  }
+
+  /**
+   * Output the configuration header section. This method is called in the head
+   * section of the connector's configuration page. Its purpose is to add the
+   * required tabs to the list, and to output any javascript methods that might
+   * be needed by the configuration editing HTML.
+   * 
+   * @param threadContext
+   *          is the local thread context.
+   * @param out
+   *          is the output to which any HTML should be sent.
+   * @param parameters
+   *          are the configuration parameters, as they currently exist, for
+   *          this connection being configured.
+   * @param tabsArray
+   *          is an array of tab names. Add to this array any tab names that are
+   *          specific to the connector.
+   */
+  @Override
+  public void outputConfigurationHeader(IThreadContext threadContext,
+      IHTTPOutput out, ConfigParams parameters, List<String> tabsArray)
+      throws ManifoldCFException, IOException {
+    
+    tabsArray.add(TAB_NAME_REPOSITORY);
+    tabsArray.add(TAB_NAME_USER_MAPPING);
+    
+    out.print("<script type=\"text/javascript\">\n" + "<!--\n"
+        + "function checkConfig()\n" + "{\n"
+        + "  if (editconnection.username.value == \"\")\n" + "  {\n"
+        + "    alert(\"The username must be not null\");\n"
+        + "    editconnection.username.focus();\n" + "    return false;\n"
+        + "  }\n" + "  if (editconnection.password.value == \"\")\n" + "  {\n"
+        + "    alert(\"The password must be not null\");\n"
+        + "    editconnection.password.focus();\n" + "    return false;\n"
+        + "  }\n" + "  if (editconnection.endpoint.value == \"\")\n" + "  {\n"
+        + "    alert(\"The endpoint must be not null\");\n"
+        + "    editconnection.endpoint.focus();\n" + "    return false;\n"
+        + "  }\n" + "\n" + "  return true;\n" + "}\n" + " \n"
+        + "function checkConfigForSave()\n" + "{\n"
+        + "  if (editconnection.username.value == \"\")\n" + "  {\n"
+        + "    alert(\"The username must be not null\");\n"
+        + "    editconnection.username.focus();\n" + "    return false;\n"
+        + "  }\n" + "  if (editconnection.password.value == \"\")\n" + "  {\n"
+        + "    alert(\"The password must be not null\");\n"
+        + "    editconnection.password.focus();\n" + "    return false;\n"
+        + "  }\n" + "  if (editconnection.endpoint.value == \"\")\n" + "  {\n"
+        + "    alert(\"The endpoint must be not null\");\n"
+        + "    editconnection.endpoint.focus();\n" + "    return false;\n"
+        + "  }\n" + "  if (editconnection.repositoryId.value == \"\")\n" + "  {\n"
+        + "    alert(\"The repository id must be not null\");\n"
+        + "    editconnection.repositoryId.focus();\n" + "    return false;\n"
+        + "  }\n" + "  return true;\n" + "}\n" + "\n" + "//-->\n"
+        + "</script>\n");
+  }
+
+  /**
+   * Process a configuration post. This method is called at the start of the
+   * connector's configuration page, whenever there is a possibility that form
+   * data for a connection has been posted. Its purpose is to gather form
+   * information and modify the configuration parameters accordingly. The name
+   * of the posted form is "editconnection".
+   * 
+   * @param threadContext
+   *          is the local thread context.
+   * @param variableContext
+   *          is the set of variables available from the post, including binary
+   *          file post information.
+   * @param parameters
+   *          are the configuration parameters, as they currently exist, for
+   *          this connection being configured.
+   * @return null if all is well, or a string error message if there is an error
+   *         that should prevent saving of the connection (and cause a
+   *         redirection to an error page).
+   */
+  @Override
+  public String processConfigurationPost(IThreadContext threadContext,
+      IPostParameters variableContext, ConfigParams parameters)
+      throws ManifoldCFException {
+
+    //Repository
+    String username = variableContext.getParameter(CONFIG_PARAM_USERNAME);
+    if (StringUtils.isNotEmpty(username))
+      parameters.setParameter(CONFIG_PARAM_USERNAME, username);
+
+    String password = variableContext.getParameter(CONFIG_PARAM_PASSWORD);
+    if (StringUtils.isNotEmpty(password))
+      parameters.setParameter(CONFIG_PARAM_PASSWORD, password);
+
+    String endpoint = variableContext.getParameter(CONFIG_PARAM_ENDPOINT);
+    if (StringUtils.isNotEmpty(endpoint) && endpoint.length() > 0)
+      parameters.setParameter(CONFIG_PARAM_ENDPOINT, endpoint);
+
+    String repositoryId = variableContext
+        .getParameter(CONFIG_PARAM_REPOSITORY_ID);
+    if (StringUtils.isNotEmpty(repositoryId))
+      parameters.setParameter(CONFIG_PARAM_REPOSITORY_ID, repositoryId);
+    
+    //User Mapping
+    String usernameRegexp = variableContext.getParameter(CONFIG_PARAM_USERNAME_REGEXP);
+    String userTranslation = variableContext.getParameter(CONFIG_PARAM_USER_TRANSLATION);
+    if (StringUtils.isNotEmpty(usernameRegexp) && StringUtils.isNotEmpty(userTranslation))
+    {
+      MatchMap localMap = new MatchMap();
+      localMap.appendMatchPair(usernameRegexp,userTranslation);
+      parameters.setParameter(CONFIG_PARAM_USERMAPPING,localMap.toString());
+    }
+
+    return null;
+
+  }
+
+  @Override
+  public String check() throws ManifoldCFException {
+    try {
+      checkConnection();
+      return super.check();
+    } catch (ServiceInterruption e) {
+      return "Connection temporarily failed: " + e.getMessage();
+    } catch (ManifoldCFException e) {
+      return "Connection failed: " + e.getMessage();
+    }
+  }
+
+  protected void checkConnection() throws ManifoldCFException,
+      ServiceInterruption {
+    while (true) {
+      boolean noSession = (session == null);
+      getSession();
+      long currentTime;
+      CheckConnectionThread t = new CheckConnectionThread();
+      try {
+        t.start();
+        t.join();
+        Throwable thr = t.getException();
+        if (thr != null) {
+          if (thr instanceof RemoteException)
+            throw (RemoteException) thr;
+          else
+            throw (Error) thr;
+        }
+        return;
+      } catch (InterruptedException e) {
+        t.interrupt();
+        throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
+            ManifoldCFException.INTERRUPTED);
+      } catch (RemoteException e) {
+        Throwable e2 = e.getCause();
+        if (e2 instanceof InterruptedException
+            || e2 instanceof InterruptedIOException)
+          throw new ManifoldCFException(e2.getMessage(), e2,
+              ManifoldCFException.INTERRUPTED);
+        if (noSession) {
+          currentTime = System.currentTimeMillis();
+          throw new ServiceInterruption(
+              "Transient error connecting to filenet service: "
+                  + e.getMessage(), currentTime + 60000L);
+        }
+        session = null;
+        lastSessionFetch = -1L;
+        continue;
+      }
+    }
+  }
+  
+  protected void getSession() throws ManifoldCFException, ServiceInterruption {
+    if (session == null) {
+      // Check for parameter validity
+      if (StringUtils.isEmpty(username))
+        throw new ManifoldCFException("Parameter " + CONFIG_PARAM_USERNAME
+            + " required but not set");
+
+      if (Logging.connectors.isDebugEnabled())
+        Logging.connectors.debug("CMIS: Username = '" + username + "'");
+
+      if (StringUtils.isEmpty(password))
+        throw new ManifoldCFException("Parameter " + CONFIG_PARAM_PASSWORD
+            + " required but not set");
+
+      Logging.connectors.debug("CMIS: Password exists");
+
+      if (StringUtils.isEmpty(endpoint))
+        throw new ManifoldCFException("Parameter " + CONFIG_PARAM_ENDPOINT
+            + " required but not set");
+
+      long currentTime;
+      GetSessionThread t = new GetSessionThread();
+      try {
+        t.start();
+        t.join();
+        Throwable thr = t.getException();
+        if (thr != null) {
+          if (thr instanceof java.net.MalformedURLException)
+            throw (java.net.MalformedURLException) thr;
+          else if (thr instanceof NotBoundException)
+            throw (NotBoundException) thr;
+          else if (thr instanceof RemoteException)
+            throw (RemoteException) thr;
+          else
+            throw (Error) thr;
+        }
+      } catch (InterruptedException e) {
+        t.interrupt();
+        throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
+            ManifoldCFException.INTERRUPTED);
+      } catch (java.net.MalformedURLException e) {
+        throw new ManifoldCFException(e.getMessage(), e);
+      } catch (NotBoundException e) {
+        // Transient problem: Server not available at the moment.
+        Logging.connectors.warn(
+            "CMIS: Server not up at the moment: " + e.getMessage(), e);
+        currentTime = System.currentTimeMillis();
+        throw new ServiceInterruption(e.getMessage(), currentTime + 60000L);
+      } catch (RemoteException e) {
+        Throwable e2 = e.getCause();
+        if (e2 instanceof InterruptedException
+            || e2 instanceof InterruptedIOException)
+          throw new ManifoldCFException(e2.getMessage(), e2,
+              ManifoldCFException.INTERRUPTED);
+        // Treat this as a transient problem
+        Logging.connectors.warn(
+            "CMIS: Transient remote exception creating session: "
+                + e.getMessage(), e);
+        currentTime = System.currentTimeMillis();
+        throw new ServiceInterruption(e.getMessage(), currentTime + 60000L);
+      }
+
+    }
+
+    lastSessionFetch = System.currentTimeMillis();
+  }
+  
+  @Override
+  public void poll() throws ManifoldCFException {
+    if (lastSessionFetch == -1L)
+      return;
+
+    long currentTime = System.currentTimeMillis();
+    if (currentTime >= lastSessionFetch + timeToRelease) {
+      DestroySessionThread t = new DestroySessionThread();
+      try {
+        t.start();
+        t.join();
+        Throwable thr = t.getException();
+        if (thr != null) {
+          if (thr instanceof RemoteException)
+            throw (RemoteException) thr;
+          else
+            throw (Error) thr;
+        }
+        session = null;
+        lastSessionFetch = -1L;
+      } catch (InterruptedException e) {
+        t.interrupt();
+        throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
+            ManifoldCFException.INTERRUPTED);
+      } catch (RemoteException e) {
+        Throwable e2 = e.getCause();
+        if (e2 instanceof InterruptedException
+            || e2 instanceof InterruptedIOException)
+          throw new ManifoldCFException(e2.getMessage(), e2,
+              ManifoldCFException.INTERRUPTED);
+        session = null;
+        lastSessionFetch = -1L;
+        // Treat this as a transient problem
+        Logging.connectors.warn(
+            "CMIS: Transient remote exception closing session: "
+                + e.getMessage(), e);
+      }
+
+    }
+  }
+  
+  /**
+   * View configuration. This method is called in the body section of the
+   * connector's view configuration page. Its purpose is to present the
+   * connection information to the user. The coder can presume that the HTML that
+   * is output from this configuration will be within appropriate <html> and
+   * <body> tags.
+   * 
+   * @param threadContext
+   *          is the local thread context.
+   * @param out
+   *          is the output to which any HTML should be sent.
+   * @param parameters
+   *          are the configuration parameters, as they currently exist, for
+   *          this connection being configured.
+   */
+  @Override
+  public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out,
+      ConfigParams parameters) throws ManifoldCFException, IOException {
+    out.print("<table class=\"displaytable\">\n"
+        + "  <tr>\n"
+        + "    <td class=\"description\" colspan=\"1\"><nobr>Parameters:</nobr></td>\n"
+        + "    <td class=\"value\" colspan=\"3\">\n");
+    Iterator iter = parameters.listParameters();
+    while (iter.hasNext()) {
+      String param = (String) iter.next();
+      String value = parameters.getParameter(param);
+      if (param.length() >= "password".length()
+          && param.substring(param.length() - "password".length())
+              .equalsIgnoreCase("password")) {
+        out.print("      <nobr>"
+            + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)
+            + "=********</nobr><br/>\n");
+      } else {
+        out.print("      <nobr>"
+            + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param) + "="
+            + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(value)
+            + "</nobr><br/>\n");
+      }
+    }
+    out.print("</td>\n" + "  </tr>\n" + "</table>\n");
+  }
+  
+  /** Obtain the default access tokens for a given user name.
+   *@param userName is the user name or identifier.
+   *@return the default response tokens, presuming that the connect method fails.
+   */
+   @Override
+   public AuthorizationResponse getDefaultAuthorizationResponse(String userName)
+   {
+     return unreachableResponse;
+   }
+   
+   /** Uncached version of the getAuthorizationResponse method.
+    *@param userName is the user name or identifier.
+    *@return the response tokens (according to the current authority).
+    * (Should throws an exception only when a condition cannot be properly described within the authorization response object.)
+    */
+    protected AuthorizationResponse getAuthorizationResponseUncached(String userName)
+      throws ManifoldCFException
+    {
+      if (Logging.connectors.isDebugEnabled())
+        Logging.connectors.debug("CMIS: Calculating response access tokens for user '"+userName+"'");
+
+      // Map the user to the final value
+      String verifiedUserName = StringUtils.EMPTY;
+      try {
+        verifiedUserName = matchMap.translate(userName);
+      } catch (Exception e) {
+          return userNotFoundResponse;
+      }
+
+      if (Logging.connectors.isDebugEnabled())
+        Logging.connectors.debug("CMIS: Mapped user name is '"+verifiedUserName+"'");
+      
+      String[] tokens = new String[1];
+      tokens[0] = verifiedUserName;
+      return new AuthorizationResponse(tokens,AuthorizationResponse.RESPONSE_OK);
+
+    }
+  
+  /** Obtain the access tokens for a given user name.
+   *@param userName is the user name or identifier.
+   *@return the response tokens (according to the current authority).
+   * (Should throws an exception only when a condition cannot be properly described within the authorization response object.)
+   */
+   @Override
+   public AuthorizationResponse getAuthorizationResponse(String userName)
+     throws ManifoldCFException
+   {
+     if (Logging.connectors.isDebugEnabled())
+       Logging.connectors.debug("CMIS: Received request for user '"+userName+"'");
+     
+     ICacheDescription objectDescription = new AuthorizationResponseDescription(userName,endpoint,repositoryId,matchMap.toString());
+     
+     // Enter the cache
+     ICacheHandle ch = cacheManager.enterCache(new ICacheDescription[]{objectDescription},null,null);
+     try
+     {
+       ICacheCreateHandle createHandle = cacheManager.enterCreateSection(ch);
+       try
+       {
+         // Lookup the object
+         AuthorizationResponse response = (AuthorizationResponse)cacheManager.lookupObject(createHandle,objectDescription);
+         if (response != null)
+           return response;
+         // Create the object.
+         response = getAuthorizationResponseUncached(userName);
+         // Save it in the cache
+         cacheManager.saveObject(createHandle,objectDescription,response);
+         // And return it...
+         return response;
+       }
+       finally
+       {
+         cacheManager.leaveCreateSection(createHandle);
+       }
+     }
+     finally
+     {
+       cacheManager.leaveCache(ch);
+     }
+   }
+   
+   /** This is the cache object descriptor for cached access tokens from
+    * this connector.
+    */
+    protected static class AuthorizationResponseDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription
+    {
+      /** The user name associated with the access tokens */
+      protected String userName;
+      /** The repository endpoint */
+      protected String endpoint;
+      /** The repository id */
+      protected String repositoryId;
+      /** The user mapping */
+      protected String userMapping;
+      /** The expiration time */
+      protected long expirationTime = -1;
+      
+      /** Constructor. */
+      public AuthorizationResponseDescription(String userName, String endpoint, String repositoryId, String userMapping)
+      {
+        super("CMISAuthority",LRUsize);
+        this.userName = userName;
+        this.endpoint = endpoint;
+        this.repositoryId = repositoryId;
+        this.userMapping = userMapping;
+      }
+
+      /** Return the invalidation keys for this object. */
+      public StringSet getObjectKeys()
+      {
+        return emptyStringSet;
+      }
+
+      /** Get the critical section name, used for synchronizing the creation of the object */
+      public String getCriticalSectionName()
+      {
+        return getClass().getName() + "-" + userName + "-" + endpoint +
+          "-" + repositoryId + "_" + userMapping;
+      }
+
+      /** Return the object expiration interval */
+      public long getObjectExpirationTime(long currentTime)
+      {
+        if (expirationTime == -1)
+          expirationTime = currentTime + responseLifetime;
+        return expirationTime;
+      }
+
+      public int hashCode()
+      {
+        return userName.hashCode() + endpoint.hashCode() + repositoryId.hashCode() + userMapping.hashCode();
+      }
+      
+      public boolean equals(Object o)
+      {
+        if (!(o instanceof AuthorizationResponseDescription))
+          return false;
+        AuthorizationResponseDescription ard = (AuthorizationResponseDescription)o;
+        return ard.userName.equals(userName) && ard.endpoint.equals(endpoint) &&
+          repositoryId.equals(repositoryId) && ard.userMapping.equals(userMapping);
+      }
+      
+    }
+  
+}

Propchange: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/CmisAuthorityConnector.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java
URL: http://svn.apache.org/viewvc/incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java?rev=1148064&view=auto
==============================================================================
--- incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java (added)
+++ incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java Mon Jul 18 21:40:19 2011
@@ -0,0 +1,587 @@
+/* $Id$ */
+
+/**
+* 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.manifoldcf.crawler.connectors.cmis;
+
+import org.apache.manifoldcf.core.interfaces.*;
+import java.util.*;
+import java.util.regex.*;
+
+/** An instance of this class describes a "match map", which describes a translation of an input
+* string using regexp technology.
+* A match map consists of multiple clauses, which are fired in sequence.  Each clause is a regexp
+* search and replace, where the replace string can include references to the groups present in the
+* search regexp.
+* MatchMaps can be converted to strings in two different ways.  The first way is to build a single
+* string of the form "match1=replace1&match2=replace2...".  Strings of this kind must escape & and =
+* characters in the match and replace strings, where found.  The second way is to generate an array
+* of match strings and a corresponding array of replace strings.  This method requires no escaping
+* of the string contents.
+*/
+public class MatchMap
+{
+  public static final String _rcsid = "@(#)$Id$";
+
+  /** This is the set of match regexp strings */
+  protected ArrayList matchStrings;
+  /** This is the set of Pattern objects corresponding to the match regexp strings.
+  * It's null if the patterns have not been built yet. */
+  protected Pattern[] matchPatterns = null;
+  /** This is the set of replace strings */
+  protected ArrayList replaceStrings;
+
+  /** Constructor.  Build an empty matchmap. */
+  public MatchMap()
+  {
+    matchStrings = new ArrayList();
+    replaceStrings = new ArrayList();
+  }
+
+  /** Constructor.  Build a matchmap from a single string. */
+  public MatchMap(String stringForm)
+  {
+    matchStrings = new ArrayList();
+    replaceStrings = new ArrayList();
+    StringBuffer matchString = new StringBuffer();
+    StringBuffer replaceString = new StringBuffer();
+    int i = 0;
+    while (i < stringForm.length())
+    {
+      matchString.setLength(0);
+      replaceString.setLength(0);
+      while (i < stringForm.length())
+      {
+        char x = stringForm.charAt(i);
+        if (x == '&' || x == '=')
+          break;
+        i++;
+        if (x == '\\' && i < stringForm.length())
+          x = stringForm.charAt(i++);
+        matchString.append(x);
+      }
+
+      if (i < stringForm.length())
+      {
+        char x = stringForm.charAt(i);
+        if (x == '=')
+        {
+          i++;
+          // Pick up the second string
+          while (i < stringForm.length())
+          {
+            x = stringForm.charAt(i);
+            if (x == '&')
+              break;
+            i++;
+            if (x == '\\' && i < stringForm.length())
+              x = stringForm.charAt(i++);
+            replaceString.append(x);
+          }
+        }
+      }
+
+      matchStrings.add(matchString.toString());
+      replaceStrings.add(replaceString.toString());
+
+      if (i < stringForm.length())
+      {
+        char x = stringForm.charAt(i);
+        if (x == '&')
+          i++;
+      }
+    }
+  }
+
+  /** Constructor.  Build a matchmap from two arraylists representing match and replace strings */
+  public MatchMap(ArrayList matchStrings, ArrayList replaceStrings)
+  {
+    this.matchStrings = (ArrayList)matchStrings.clone();
+    this.replaceStrings = (ArrayList)replaceStrings.clone();
+  }
+
+  /** Get the number of match/replace strings */
+  public int getMatchCount()
+  {
+    return matchStrings.size();
+  }
+
+  /** Get a specific match string */
+  public String getMatchString(int index)
+  {
+    return (String)matchStrings.get(index);
+  }
+
+  /** Get a specific replace string */
+  public String getReplaceString(int index)
+  {
+    return (String)replaceStrings.get(index);
+  }
+
+  /** Delete a specified match/replace string pair */
+  public void deleteMatchPair(int index)
+  {
+    matchStrings.remove(index);
+    replaceStrings.remove(index);
+    matchPatterns = null;
+  }
+
+  /** Insert a match/replace string pair */
+  public void insertMatchPair(int index, String match, String replace)
+  {
+    matchStrings.add(index,match);
+    replaceStrings.add(index,replace);
+    matchPatterns = null;
+  }
+
+  /** Append a match/replace string pair */
+  public void appendMatchPair(String match, String replace)
+  {
+    matchStrings.add(match);
+    replaceStrings.add(replace);
+    matchPatterns = null;
+  }
+
+  /** Append old-style match/replace pair.
+  * This method translates old-style regexp and group output form to the
+  * current style before adding to the map.
+  */
+  public void appendOldstyleMatchPair(String oldstyleMatch, String oldstyleReplace)
+  {
+    String newStyleMatch = "^" + oldstyleMatch + "$";
+
+    // Need to build a new-style replace string from the old one.  To do that, use the
+    // original parser (which basically will guarantee that we get it right)
+
+    EvaluatorTokenStream et = new EvaluatorTokenStream(oldstyleReplace);
+    StringBuffer newStyleReplace = new StringBuffer();
+
+    while (true)
+    {
+      EvaluatorToken t = et.peek();
+      if (t == null)
+        break;
+      switch (t.getType())
+      {
+      case EvaluatorToken.TYPE_COMMA:
+        et.advance();
+        break;
+      case EvaluatorToken.TYPE_GROUP:
+        et.advance();
+        int groupNumber = t.getGroupNumber();
+        switch (t.getGroupStyle())
+        {
+        case EvaluatorToken.GROUPSTYLE_NONE:
+          newStyleReplace.append("$(").append(Integer.toString(groupNumber)).append(")");
+          break;
+        case EvaluatorToken.GROUPSTYLE_LOWER:
+          newStyleReplace.append("$(").append(Integer.toString(groupNumber)).append("l)");
+          break;
+        case EvaluatorToken.GROUPSTYLE_UPPER:
+          newStyleReplace.append("$(").append(Integer.toString(groupNumber)).append("u)");
+          break;
+        case EvaluatorToken.GROUPSTYLE_MIXED:
+          newStyleReplace.append("$(").append(Integer.toString(groupNumber)).append("m)");
+          break;
+        default:
+          break;
+        }
+        break;
+      case EvaluatorToken.TYPE_TEXT:
+        et.advance();
+        escape(newStyleReplace,t.getTextValue());
+        break;
+      default:
+        break;
+      }
+    }
+
+    appendMatchPair(newStyleMatch,newStyleReplace.toString());
+  }
+
+  /** Escape a string so it is verbatim */
+  protected static void escape(StringBuffer output, String input)
+  {
+    int i = 0;
+    while (i < input.length())
+    {
+      char x = input.charAt(i++);
+      if (x == '$')
+        output.append(x);
+      output.append(x);
+    }
+  }
+
+  /** Convert the matchmap to string form. */
+  public String toString()
+  {
+    int i = 0;
+    StringBuffer rval = new StringBuffer();
+    while (i < matchStrings.size())
+    {
+      String matchString = (String)matchStrings.get(i);
+      String replaceString = (String)replaceStrings.get(i);
+      if (i > 0)
+        rval.append('&');
+      stuff(rval,matchString);
+      rval.append('=');
+      stuff(rval,replaceString);
+      i++;
+    }
+    return rval.toString();
+  }
+
+  /** Stuff characters */
+  protected static void stuff(StringBuffer sb, String value)
+  {
+    int i = 0;
+    while (i < value.length())
+    {
+      char x = value.charAt(i++);
+      if (x == '\\' || x == '&' || x == '=')
+        sb.append('\\');
+      sb.append(x);
+    }
+  }
+
+  /** Perform a translation.
+  */
+  public String translate(String input)
+    throws ManifoldCFException
+  {
+    // Build pattern vector if not already there
+    if (matchPatterns == null)
+    {
+      matchPatterns = new Pattern[matchStrings.size()];
+      int i = 0;
+      while (i < matchPatterns.length)
+      {
+        String regexp = (String)matchStrings.get(i);
+        try
+        {
+          matchPatterns[i] = Pattern.compile(regexp);
+        }
+        catch (java.util.regex.PatternSyntaxException e)
+        {
+          matchPatterns = null;
+          throw new ManifoldCFException("For match expression '"+regexp+"', found pattern syntax error: "+e.getMessage(),e);
+        }
+        i++;
+      }
+    }
+
+    int j = 0;
+    while (j < matchPatterns.length)
+    {
+      Pattern p = matchPatterns[j];
+      // Construct a matcher
+      Matcher m = p.matcher(input);
+      // Grab the output description
+      String outputDescription = (String)replaceStrings.get(j);
+      j++;
+      // Create a copy buffer
+      StringBuffer outputBuffer = new StringBuffer();
+      // Keep track of the index in the original string we have done up to
+      int currentIndex = 0;
+      // Scan the string using find, and for each one found, do a translation
+      while (true)
+      {
+        boolean foundOne = m.find();
+        if (foundOne == false)
+        {
+          // No subsequent match found.
+          // Copy everything from currentIndex until the end of input
+          outputBuffer.append(input.substring(currentIndex));
+          break;
+        }
+
+        // Do a translation.  This involves copying everything in the input
+        // string up until the start of the match, then doing a replace for
+        // the match itself, and finally setting the currentIndex to the end
+        // of the match.
+
+        int matchStart = m.start(0);
+        int matchEnd = m.end(0);
+        if (matchStart == -1)
+        {
+          // The expression was degenerate; treat this as the end.
+          outputBuffer.append(input.substring(currentIndex));
+          break;
+        }
+        outputBuffer.append(input.substring(currentIndex,matchStart));
+
+        // Process translation description!
+        int i = 0;
+        while (i < outputDescription.length())
+        {
+          char x = outputDescription.charAt(i++);
+          if (x == '$' && i < outputDescription.length())
+          {
+            x = outputDescription.charAt(i++);
+            if (x == '(')
+            {
+              // Process evaluation expression
+              StringBuffer numberBuf = new StringBuffer();
+              boolean upper = false;
+              boolean lower = false;
+              boolean mixed = false;
+              while (i < outputDescription.length())
+              {
+                char y = outputDescription.charAt(i++);
+                if (y == ')')
+                  break;
+                else if (y >= '0' && y <= '9')
+                  numberBuf.append(y);
+                else if (y == 'u' || y == 'U')
+                  upper = true;
+                else if (y == 'l' || y == 'L')
+                  lower = true;
+                else if (y == 'm' || y == 'M')
+                  mixed = true;
+              }
+              String number = numberBuf.toString();
+              try
+              {
+                int groupnum = Integer.parseInt(number);
+                String groupValue = m.group(groupnum);
+                if (upper)
+                  outputBuffer.append(groupValue.toUpperCase());
+                else if (lower)
+                  outputBuffer.append(groupValue.toLowerCase());
+                else if (mixed && groupValue.length() > 0)
+                  outputBuffer.append(groupValue.substring(0,1).toUpperCase()).append(groupValue.substring(1).toLowerCase());
+                else
+                  outputBuffer.append(groupValue);
+
+              }
+              catch (NumberFormatException e)
+              {
+                // Silently skip, because it's an illegal group number, so nothing
+                // gets added.
+              }
+
+              // Go back around, so we don't add the $ in
+              continue;
+            }
+          }
+          outputBuffer.append(x);
+        }
+
+        currentIndex = matchEnd;
+      }
+
+      input = outputBuffer.toString();
+    }
+
+    return input;
+  }
+
+
+  // Protected classes
+
+  // These classes are used to process the old token-based replacement strings
+
+  /** Evaluator token.
+  */
+  protected static class EvaluatorToken
+  {
+    public final static int TYPE_GROUP = 0;
+    public final static int TYPE_TEXT = 1;
+    public final static int TYPE_COMMA = 2;
+
+    public final static int GROUPSTYLE_NONE = 0;
+    public final static int GROUPSTYLE_LOWER = 1;
+    public final static int GROUPSTYLE_UPPER = 2;
+    public final static int GROUPSTYLE_MIXED = 3;
+
+    protected int type;
+    protected int groupNumber = -1;
+    protected int groupStyle = GROUPSTYLE_NONE;
+    protected String textValue = null;
+
+    public EvaluatorToken()
+    {
+      type = TYPE_COMMA;
+    }
+
+    public EvaluatorToken(int groupNumber, int groupStyle)
+    {
+      type = TYPE_GROUP;
+      this.groupNumber = groupNumber;
+      this.groupStyle = groupStyle;
+    }
+
+    public EvaluatorToken(String text)
+    {
+      type = TYPE_TEXT;
+      this.textValue = text;
+    }
+
+    public int getType()
+    {
+      return type;
+    }
+
+    public int getGroupNumber()
+    {
+      return groupNumber;
+    }
+
+    public int getGroupStyle()
+    {
+      return groupStyle;
+    }
+
+    public String getTextValue()
+    {
+      return textValue;
+    }
+
+  }
+
+
+  /** Token stream.
+  */
+  protected static class EvaluatorTokenStream
+  {
+    protected String text;
+    protected int pos;
+    protected EvaluatorToken token = null;
+
+    /** Constructor.
+    */
+    public EvaluatorTokenStream(String text)
+    {
+      this.text = text;
+      this.pos = 0;
+    }
+
+    /** Get current token.
+    */
+    public EvaluatorToken peek()
+    {
+      if (token == null)
+      {
+        token = nextToken();
+      }
+      return token;
+    }
+
+    /** Go on to next token.
+    */
+    public void advance()
+    {
+      token = null;
+    }
+
+    protected EvaluatorToken nextToken()
+    {
+      char x;
+      // Fetch the next token
+      while (true)
+      {
+        if (pos == text.length())
+          return null;
+        x = text.charAt(pos);
+        if (x > ' ')
+          break;
+        pos++;
+      }
+
+      StringBuffer sb;
+
+      if (x == '"')
+      {
+        // Parse text
+        pos++;
+        sb = new StringBuffer();
+        while (true)
+        {
+          if (pos == text.length())
+            break;
+          x = text.charAt(pos);
+          pos++;
+          if (x == '"')
+          {
+            break;
+          }
+          if (x == '\\')
+          {
+            if (pos == text.length())
+              break;
+            x = text.charAt(pos++);
+          }
+          sb.append(x);
+        }
+
+        return new EvaluatorToken(sb.toString());
+      }
+
+      if (x == ',')
+      {
+        pos++;
+        return new EvaluatorToken();
+      }
+
+      // Eat number at beginning
+      sb = new StringBuffer();
+      while (true)
+      {
+        if (pos == text.length())
+          break;
+        x = text.charAt(pos);
+        if (x >= '0' && x <= '9')
+        {
+          sb.append(x);
+          pos++;
+          continue;
+        }
+        break;
+      }
+      String numberValue = sb.toString();
+      int groupNumber = 0;
+      if (numberValue.length() > 0)
+        groupNumber = new Integer(numberValue).intValue();
+      // Save the next char position
+      int modifierPos = pos;
+      // Go to the end of the word
+      while (true)
+      {
+        if (pos == text.length())
+          break;
+        x = text.charAt(pos);
+        if (x == ',' || x >= '0' && x <= '9' || x <= ' ' && x >= 0)
+          break;
+        pos++;
+      }
+
+      int style = EvaluatorToken.GROUPSTYLE_NONE;
+      if (modifierPos != pos)
+      {
+        String modifier = text.substring(modifierPos,pos);
+        if (modifier.startsWith("u"))
+          style = EvaluatorToken.GROUPSTYLE_UPPER;
+        else if (modifier.startsWith("l"))
+          style = EvaluatorToken.GROUPSTYLE_LOWER;
+        else if (modifier.startsWith("m"))
+          style = EvaluatorToken.GROUPSTYLE_MIXED;
+      }
+      return new EvaluatorToken(groupNumber,style);
+    }
+  }
+
+}

Propchange: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/branches/CONNECTORS-221/connectors/cmis/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/cmis/MatchMap.java
------------------------------------------------------------------------------
    svn:keywords = Id