[Yanel-dev] User management search implementation

Cedric Staub cedric.staub at wyona.com
Thu Oct 21 16:18:55 CEST 2010


Hello everyone

In short: I built a search feature for the user management page.

The current implementation of Yanel's user management resource does not
support searching for users, which can be a bit annoying if you have a
realm with a lot of users. Since Yarep already has an interface that 
allows to search for nodes, I built a search feature for the user 
management page on top of that.

There is one limitation to this approach: The search feature is subject 
to the limitations of the underlying implementation, e.g. if your users
repository does not support searching it won't work. I suggest the 
VirtualFileSystemRepository implementation, which can be configured to 
use a Lucene search index.

Attached are a series of patches for review. If anyone has suggestions
or ideas for improvements just reply to this email ;-). I plan to
improve the code and do some more testing and then file a bug report
later in order to get it commited.

Have a nice day
Cedric
-------------- next part --------------
Index: src/impl/java/org/wyona/security/impl/yarep/YarepUserManager.java
===================================================================
--- src/impl/java/org/wyona/security/impl/yarep/YarepUserManager.java	(revision 54048)
+++ src/impl/java/org/wyona/security/impl/yarep/YarepUserManager.java	(working copy)
@@ -346,8 +346,7 @@
      * @see org.wyona.security.core.api.UserManager#getUsers(String)
      */
     public java.util.Iterator<User> getUsers(String query) throws AccessManagementException {
-        log.error("TODO: Not implemented yet!");
-        return null;
+        return new YarepUsersIterator(identityManager, identitiesRepository, cacheEnabled, resolveGroupsAtCreation, query);
     }
 
     /**
Index: src/impl/java/org/wyona/security/impl/yarep/YarepUsersIterator.java
===================================================================
--- src/impl/java/org/wyona/security/impl/yarep/YarepUsersIterator.java	(revision 54048)
+++ src/impl/java/org/wyona/security/impl/yarep/YarepUsersIterator.java	(working copy)
@@ -15,6 +15,7 @@
 
 import org.wyona.yarep.core.Node;
 import org.wyona.yarep.core.Repository;
+import org.wyona.yarep.core.search.Searcher;
 
 /**
  * Yarep users iterator.
@@ -72,6 +73,42 @@
     }
 
     /**
+     * Constructor with search query.
+     */
+    public YarepUsersIterator(IdentityManager identityManager, 
+                              Repository identitiesRepository, 
+                              boolean cacheEnabled, 
+                              boolean resolveGroupsAtCreation,
+                              String query) 
+                              throws AccessManagementException {
+        // Call parent
+        super(identityManager, identitiesRepository, cacheEnabled, resolveGroupsAtCreation);
+        log.info("Load users from repository '" + identitiesRepository.getConfigFile() + "'");
+
+        // TODO: This is inefficient! If Yarep were able to
+        // return an iterator instead of an array, this could
+        // be done in a more effective manner, but until Yarep
+        // gains that capability we're currently stuck like this.
+        try {
+            Searcher s = identitiesRepository.getSearcher();
+            Node[] userNodes = s.search(query);
+
+            userNodeList = new LinkedList<Node>();
+            for(Node n : userNodes) {
+                if(n.isResource()) {
+                    userNodeList.add(n);
+                }
+            }
+
+            listIterator = userNodeList.listIterator();
+        } catch(Exception e) {
+            String errorMsg = "Could not read users from repository: " + e.getMessage();
+            log.error(errorMsg, e);
+            throw new AccessManagementException(errorMsg, e);
+        }
+    }
+
+    /**
      * @see java.util.Iterator#hasNext()
      */
     @Override
-------------- next part --------------
Index: src/resources/user-mgmt/htdocs/list-users.jelly
===================================================================
--- src/resources/user-mgmt/htdocs/list-users.jelly	(revision 54048)
+++ src/resources/user-mgmt/htdocs/list-users.jelly	(working copy)
@@ -7,36 +7,86 @@
         Showing users ${resource.getLowerBound()} through ${resource.getUpperBound()}
         out of a total of ${resource.getTotalUsers()}.
       </p>
-      <!-- Table of users -->
-      <table border="1">
+      <!-- Jump to certain page, search for user -->
+      <table>
+        <j:if test="${resource.getTotalPages() &gt; 1}">
+          <tr>
+            <td style="border:none;">
+              <label for="pageinput">Jump to page:</label>
+            </td>
+            <td style="border:none;">
+              <form method="GET">
+                <input style="display:inline;" type="text" size="10" name="page" id="pageinput"/>
+                <input style="display:inline;" type="submit" value="Go"/>
+              </form>
+            </td>
+          </tr>
+        </j:if>
         <tr>
-          <th>ID</th>
-          <th>Name</th>
-          <th>Email</th>
-          <th colspan="2">Actions</th>
+          <td style="border:none;">
+            <label for="queryinput">Search for users:</label>
+          </td>
+          <td style="border:none;">
+            <form method="GET">
+              <input style="display:inline;" type="text" size="10" name="query" id="queryinput"/>
+              <input style="display:inline;" type="submit" value="Go"/>
+            </form>
+          </td>
         </tr>
-        <j:forEach var="user" items="${resource.getUsers()}">
+      </table>
+      <!-- Table of users -->
+      <j:choose>
+      <j:when test="${resource.getTotalUsers() &lt; 1}">
+        <p>
+          <strong>No users found.</strong>
+        </p>
+      </j:when>
+      <j:otherwise>
+        <br/>
+        <table border="1">
           <tr>
-            <td>${user.getID()}</td>
-            <td>${user.getName()}</td>
-            <td>${user.getEmail()}</td>
-            <td><a href="update-user-admin.html?userID=${user.getID()}">Update/Edit</a></td>
-            <td><a href="delete-user.html?userID=${user.getID()}">Delete</a></td>
+            <th>ID</th>
+            <th>Name</th>
+            <th>Email</th>
+            <th colspan="2">Actions</th>
           </tr>
-        </j:forEach>
-      </table>
-      <!-- Pagination -->
-      <p>
-        <j:if test="${resource.getCurrentPage() &gt; 1}">
-          <a href="?page=${resource.getCurrentPage()-1}">? Prev</a>
-          &#160;
-        </j:if>
-        Page ${resource.getCurrentPage()}/${resource.getTotalPages()}
-        <j:if test="${resource.hasNext()}">
-          &#160;
-          <a href="?page=${resource.getCurrentPage()+1}">Next ?</a>
-        </j:if>
-      </p>
+          <j:forEach var="user" items="${resource.getUsers()}">
+            <j:choose>
+            <j:when test="${user != null}">
+              <tr>
+                <td>${user.getID()}</td>
+                <td>${user.getName()}</td>
+                <td>${user.getEmail()}</td>
+                <td><a href="update-user-admin.html?userID=${user.getID()}">Update/Edit</a></td>
+                <td><a href="delete-user.html?userID=${user.getID()}">Delete</a></td>
+              </tr>
+            </j:when>
+            <j:otherwise>
+              <tr>
+                <td>???</td>
+                <td>Invalid user</td>
+                <td>(none)</td>
+                <td>Update/Edit</td>
+                <td>Delete</td>
+              </tr>
+            </j:otherwise>
+            </j:choose>
+          </j:forEach>
+        </table>
+        <!-- Pagination -->
+        <p>
+          <j:if test="${resource.getCurrentPage() &gt; 1}">
+            <a href="?page=${resource.getCurrentPage()-1}">? Prev</a>
+            &#160;
+          </j:if>
+          Page ${resource.getCurrentPage()}/${resource.getTotalPages()}
+          <j:if test="${resource.hasNext()}">
+            &#160;
+            <a href="?page=${resource.getCurrentPage()+1}">Next ?</a>
+          </j:if>
+        </p>
+      </j:otherwise>
+      </j:choose>
       <!-- Links -->
       <a href="create-user.html">Create new user</a>
     </body>
Index: src/resources/user-mgmt/src/java/org/wyona/yanel/impl/resources/ListUsersResource.java
===================================================================
--- src/resources/user-mgmt/src/java/org/wyona/yanel/impl/resources/ListUsersResource.java	(revision 54048)
+++ src/resources/user-mgmt/src/java/org/wyona/yanel/impl/resources/ListUsersResource.java	(working copy)
@@ -22,6 +22,8 @@
 import org.wyona.yanel.impl.resources.usecase.UsecaseException;
 import org.wyona.yanel.impl.resources.usecase.UsecaseResource;
 
+import org.apache.log4j.Logger;
+
 import java.lang.System;
 import java.lang.Integer;
 import java.lang.Boolean;
@@ -37,6 +39,7 @@
     // Constants
     private static final int DEFAULT_ITEMS_PER_PAGE = 10;
     //private static final int DEFAULT_ITEMS_PER_PAGE = 100;
+    private static final Logger log =  Logger.getLogger(ListUsersResource.class);
 
     // Variables
     private int currentPage = 1;
@@ -82,11 +85,26 @@
             // All users matching search term,
             // or all users overall if search term is empty
             Iterator<User> allUsers;
-            allUsers = userManager.getAllUsers();
+            String query = getParameterAsString("query");
 
+            if(query != null && !"".equals(query)) { 
+                try {
+                    // TODO: What if getUsers() returns garbage?
+                    allUsers = userManager.getUsers(query);
+                } catch(Exception e) {
+                    log.warn(e, e);
+                    lowerBound = 0;
+                    upperBound = 0;
+                    totalUsers = 0;
+                    return;
+                }
+            } else {
+                allUsers = userManager.getAllUsers();
+            }
+
             // Boundaries...
             lowerBound = (currentPage-1)*itemsPerPage;
-            upperBound = lowerBound+itemsPerPage-1; // TODO: On the very last page this doesn't have to be correct
+            upperBound = lowerBound+itemsPerPage-1;
             totalUsers = userManager.getUserCount();
             totalPages = totalUsers/itemsPerPage;
             if(totalUsers%itemsPerPage != 0) totalPages++;
-------------- next part --------------
Index: src/realms/from-scratch-realm-template/tika-config.xml
===================================================================
--- src/realms/from-scratch-realm-template/tika-config.xml	(revision 0)
+++ src/realms/from-scratch-realm-template/tika-config.xml	(revision 0)
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- The original version is located within the Tika jar file at org/apache/tika/tika-config.xml -->
+<properties>
+
+    <mimeTypeRepository resource="/org/apache/tika/mime/tika-mimetypes.xml" magic="false"/>
+
+    <parsers>
+
+        <parser name="text-xml" class="org.apache.tika.parser.xml.XMLParser">
+                <mime>application/xml</mime>
+                <mime>application/xhtml+xml</mime>
+        </parser>
+
+        <parser name="parse-msword" class="org.apache.tika.parser.microsoft.WordParser">
+                <mime>application/msword</mime>
+        </parser>
+
+        <parser name="parse-msexcel" class="org.apache.tika.parser.microsoft.ExcelParser">
+                <mime>application/vnd.ms-excel</mime>
+        </parser>
+
+        <parser name="parse-mspowerpoint" class="org.apache.tika.parser.microsoft.PowerPointParser">
+                <mime>application/vnd.ms-powerpoint</mime>
+        </parser>
+
+        <parser name="parse-html" class="org.apache.tika.parser.html.HtmlParser">
+                <mime>text/html</mime>
+                <mime>application/x-asp</mime>
+        </parser>
+
+        <parser mame="parse-rtf" class="org.apache.tika.parser.rtf.RTFParser">
+                <mime>application/rtf</mime>
+        </parser>
+
+        <parser name="parse-pdf" class="org.apache.tika.parser.pdf.PDFParser">
+                <mime>application/pdf</mime>
+        </parser>
+
+        <parser name="parse-txt" class="org.apache.tika.parser.txt.TXTParser">
+                <mime>text/plain</mime>
+        </parser>
+
+        <parser name="parse-openoffice" class="org.apache.tika.parser.opendocument.OpenOfficeParser">            
+                <mime>application/vnd.sun.xml.writer</mime>
+                <mime>application/vnd.oasis.opendocument.text</mime>
+                <mime>application/vnd.oasis.opendocument.graphics</mime>
+                <mime>application/vnd.oasis.opendocument.presentation</mime>
+                <mime>application/vnd.oasis.opendocument.spreadsheet</mime>
+                <mime>application/vnd.oasis.opendocument.chart</mime>
+                <mime>application/vnd.oasis.opendocument.image</mime>
+                <mime>application/vnd.oasis.opendocument.formula</mime>
+                <mime>application/vnd.oasis.opendocument.text-master</mime>
+                <mime>application/vnd.oasis.opendocument.text-web</mime>
+                <mime>application/vnd.oasis.opendocument.text-template</mime>
+                <mime>application/vnd.oasis.opendocument.graphics-template</mime>
+                <mime>application/vnd.oasis.opendocument.presentation-template</mime>
+                <mime>application/vnd.oasis.opendocument.spreadsheet-template</mime>
+                <mime>application/vnd.oasis.opendocument.chart-template</mime>
+                <mime>application/vnd.oasis.opendocument.image-template</mime>
+                <mime>application/vnd.oasis.opendocument.formula-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.text</mime>
+                <mime>application/x-vnd.oasis.opendocument.graphics</mime>
+                <mime>application/x-vnd.oasis.opendocument.presentation</mime>
+                <mime>application/x-vnd.oasis.opendocument.spreadsheet</mime>
+                <mime>application/x-vnd.oasis.opendocument.chart</mime>
+                <mime>application/x-vnd.oasis.opendocument.image</mime>
+                <mime>application/x-vnd.oasis.opendocument.formula</mime>
+                <mime>application/x-vnd.oasis.opendocument.text-master</mime>
+                <mime>application/x-vnd.oasis.opendocument.text-web</mime>
+                <mime>application/x-vnd.oasis.opendocument.text-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.graphics-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.presentation-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.spreadsheet-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.chart-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.image-template</mime>
+                <mime>application/x-vnd.oasis.opendocument.formula-template</mime>
+        </parser>
+
+    </parsers>
+
+</properties>
Index: src/realms/from-scratch-realm-template/config/ac-identities-repository.xml
===================================================================
--- src/realms/from-scratch-realm-template/config/ac-identities-repository.xml	(revision 54048)
+++ src/realms/from-scratch-realm-template/config/ac-identities-repository.xml	(working copy)
@@ -1,11 +1,37 @@
 <?xml version="1.0"?>
 
-<repository class="org.wyona.yarep.impl.DefaultRepository">
+<repository class="org.wyona.yarep.impl.repo.vfs.VirtualFileSystemRepository">
   <name>Yanel from Scratch Access Control Identities</name>
 
-  <paths class="org.wyona.yarep.impl.VFileSystemMapImpl" src="../ac-identities"/>
+  <content src="../ac-identities">
+    <!-- Ignore patterns are optional -->
+    <ignore pattern=".*\.svn"/>
+    <ignore pattern=".*\.yarep"/>
+    <ignore pattern=".*\.sh"/>
+    <ignore pattern="search-index"/>
+  </content>
 
-  <storage class="org.wyona.yarep.core.impl.vfs.VFileSystemStorage">
-    <content src="../ac-identities"/>
-  </storage>
+  <!-- Search index config -->
+  <s:search-index 
+      xmlns:s="http://www.wyona.org/yarep/search/2.0" 
+      indexer-class="org.wyona.yarep.impl.search.lucene.LuceneIndexer" 
+      searcher-class="org.wyona.yarep.impl.search.lucene.LuceneSearcher">
+    <index-location file="../ac-identities/search-index"/>
+    <repo-auto-index-fulltext boolean="true"/>
+    <repo-auto-index-properties boolean="true"/>
+    <lucene>
+      <local-tika-config file="tika-config.xml"/>
+      <fulltext-analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
+      <property-analyzer class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>
+      <write-lock-timeout ms="3000"/>
+    </lucene>
+  </s:search-index>
+  
+  <!--
+  <splitpath depth="1" length="2" escape="+">
+    <include path="/users/"/>
+    <include path="/groups/"/>
+  </splitpath>
+  -->
+
 </repository>


More information about the Yanel-development mailing list