1    /*
2     * Copyright 2008 :torweg free software group
3     * 
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.util.search;
19   
20   import java.io.IOException;
21   import java.util.BitSet;
22   import java.util.HashSet;
23   import java.util.Set;
24   
25   import org.apache.lucene.index.IndexReader;
26   import org.apache.lucene.index.Term;
27   import org.apache.lucene.index.TermDocs;
28   import org.apache.lucene.index.TermEnum;
29   import org.apache.lucene.search.CachingWrapperFilter;
30   import org.apache.lucene.search.DocIdSet;
31   import org.apache.lucene.search.Filter;
32   import org.apache.lucene.util.DocIdBitSet;
33   import org.hibernate.Session;
34   import org.hibernate.Transaction;
35   import org.hibernate.search.annotations.Factory;
36   import org.hibernate.search.annotations.Key;
37   import org.hibernate.search.filter.FilterKey;
38   import org.hibernate.search.filter.StandardFilterKey;
39   import org.torweg.pulse.accesscontrol.Role;
40   import org.torweg.pulse.accesscontrol.User;
41   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
42   import org.torweg.pulse.service.PulseException;
43   
44   /**
45    * a {@code Filter} removing {@code SitemapNode}s requiring
46    * {@code Role}s which are not provided to the {@code Filter}.
47    * 
48    * @see org.torweg.pulse.site.map.SitemapNode
49    * @author unknown, Thomas Weber
50    * @version $Revision: 1415 $
51    */
52   public class SitemapNodeRoleFilterFactory {
53   
54       /**
55        * the roles to check against.
56        */
57       private final Set<Role> roles = new HashSet<Role>();
58   
59       /**
60        * injects the roles.
61        * 
62        * @param r
63        *            the roles
64        */
65       public final void setRoles(final Set<Role> r) {
66           this.roles.clear();
67           this.roles.addAll(r);
68       }
69   
70       /**
71        * returns the {@code FilterKey}.
72        * 
73        * @return the filter key
74        */
75       @Key
76       public final FilterKey getKey() {
77           StandardFilterKey key = new StandardFilterKey();
78           key.addParameter(this.roles);
79           return key;
80       }
81   
82       /**
83        * default constructor.
84        */
85       public SitemapNodeRoleFilterFactory() {
86           super();
87       }
88   
89       /**
90        * factory method to create a cacheable version of
91        * {@code SitemapNodeUniqueContentFilter}.
92        * 
93        * @see Factory
94        * @return the cachable filter
95        */
96       @Factory
97       public final Filter getFilter() {
98           Filter filter = new SitemapNodeRoleFilter(this.roles);
99           return new CachingWrapperFilter(filter);
100      }
101  
102      /**
103       * The filter checking whether the filter's roles are part of the document
104       * roles.
105       * 
106       * @author Thomas Weber
107       * @version $Revision: 1415 $
108       */
109      public static class SitemapNodeRoleFilter extends Filter {
110  
111          /**
112           * serialVersionUID.
113           */
114          private static final long serialVersionUID = -4168019388047484467L;
115  
116          /**
117           * the set of role ids to check against.
118           */
119          private final Set<Long> roleIdSet = new HashSet<Long>();
120  
121          /**
122           * creates a new filter for the given set of roles.
123           * 
124           * @param roles
125           *            the roles
126           */
127          public SitemapNodeRoleFilter(final Set<Role> roles) {
128              super();
129              for (Role r : roles) {
130                  this.roleIdSet.add(r.getId());
131              }
132              Session s = Lifecycle.getHibernateDataSource().createNewSession();
133              Transaction tx = s.beginTransaction();
134              try {
135                  this.roleIdSet.add(User.getEverybodyRole(s).getId());
136                  tx.commit();
137              } catch (Exception e) {
138                  tx.rollback();
139                  throw new PulseException("Error: " + e.getLocalizedMessage(), e);
140              } finally {
141                  s.close();
142              }
143          }
144  
145          /**
146           * does the actual filtering.
147           * <p>
148           * One bit set (<tt>A</tt>) collects all documents with roles set.
149           * Another bit set (<tt>B</tt>) collects all documents with a role whose
150           * id is contained in the filter's ids.
151           * </p>
152           * <p>
153           * The result is computed as follows:<br>
154           * {@code not(A) or B}<br>
155           * So <tt>not(A)</tt> represents all documents without a role. Thus the
156           * results are either documents without a role or with a role which is
157           * in the filter's set of roles.
158           * </p>
159           * 
160           * @param reader
161           *            the index reader
162           * @return a bit set with the results to be included
163           * @throws IOException
164           *             on errors accessing the index
165           * @see org.apache.lucene.search.Filter#getDocIdSet(org.apache.lucene.index.IndexReader)
166           */
167          @Override
168          public final DocIdSet getDocIdSet(final IndexReader reader)
169                  throws IOException {
170              // collect all documents with roles
171              BitSet roleMask = new BitSet(reader.maxDoc());
172              // collect all documents with a role in roleIdSet
173              BitSet docsWithValidRoles = new BitSet(reader.maxDoc());
174  
175              Term rolesTerm = new Term("roles.id", "");
176              // get all terms for "roles.id"
177              TermEnum termEnumeration = reader.terms(rolesTerm);
178              if (termEnumeration != null) {
179                  // check per term
180                  Term currentTerm = termEnumeration.term();
181                  while ((currentTerm != null)
182                          && (currentTerm.field().equals(rolesTerm.field()))) {
183  
184                      boolean contained = this.roleIdSet.contains(Long
185                              .parseLong(currentTerm.text()));
186  
187                      // get all docs with the current term
188                      TermDocs td = reader.termDocs(currentTerm);
189  
190                      while (td.next()) {
191                          // td.doc() has at least one role
192                          roleMask.set(td.doc());
193                          if (contained) {
194                              // td.doc() has at least one role from
195                              // this.roleIdSet
196                              docsWithValidRoles.set(td.doc());
197                          }
198                      }
199                      if (!termEnumeration.next()) {
200                          break;
201                      }
202  
203                      // continue with the next term
204                      currentTerm = termEnumeration.term();
205                  }
206              }
207  
208              // inverting gives us all documents without a role!
209              roleMask.flip(0, reader.maxDoc());
210              // now valid document have either no role or a valid role
211              roleMask.or(docsWithValidRoles);
212  
213              return new DocIdBitSet(roleMask);
214          }
215      }
216  }
217