View Javadoc
1   /*
2    * Copyright (C) 2003-2007 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (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  package org.exoplatform.services.cms.queries.impl;
18  
19  import org.exoplatform.commons.utils.ISO8601;
20  import org.exoplatform.container.xml.InitParams;
21  import org.exoplatform.container.xml.PortalContainerInfo;
22  import org.exoplatform.services.cache.CacheService;
23  import org.exoplatform.services.cache.ExoCache;
24  import org.exoplatform.services.cms.BasePath;
25  import org.exoplatform.services.cms.impl.DMSConfiguration;
26  import org.exoplatform.services.cms.impl.DMSRepositoryConfiguration;
27  import org.exoplatform.services.cms.queries.QueryService;
28  import org.exoplatform.services.jcr.RepositoryService;
29  import org.exoplatform.services.jcr.access.PermissionType;
30  import org.exoplatform.services.jcr.core.ExtendedNode;
31  import org.exoplatform.services.jcr.core.ManageableRepository;
32  import org.exoplatform.services.jcr.ext.common.SessionProvider;
33  import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
34  import org.exoplatform.services.log.ExoLogger;
35  import org.exoplatform.services.log.Log;
36  import org.exoplatform.services.organization.MembershipHandler;
37  import org.exoplatform.services.organization.OrganizationService;
38  import org.exoplatform.services.security.ConversationState;
39  import org.exoplatform.services.security.IdentityConstants;
40  import org.exoplatform.services.security.Identity;
41  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
42  import org.exoplatform.web.application.RequestContext;
43  import org.picocontainer.Startable;
44  
45  import javax.jcr.*;
46  import javax.jcr.query.Query;
47  import javax.jcr.query.QueryManager;
48  import javax.jcr.query.QueryResult;
49  
50  import java.util.*;
51  
52  public class QueryServiceImpl implements QueryService, Startable{
53    private static final String[] perms = {PermissionType.READ, PermissionType.ADD_NODE,
54      PermissionType.SET_PROPERTY, PermissionType.REMOVE };
55    private String relativePath_;
56    private List<QueryPlugin> queryPlugins_ = new ArrayList<QueryPlugin> ();
57    private RepositoryService repositoryService_;
58    private ExoCache<String, QueryResult> cache_;
59    private PortalContainerInfo containerInfo_;
60    private OrganizationService organizationService_;
61    private String baseUserPath_;
62    private String baseQueriesPath_;
63    private String group_;
64    private DMSConfiguration dmsConfiguration_;
65    private Set<String> configuredQueries_;
66    
67    private NodeHierarchyCreator nodeHierarchyCreator_;
68  
69    private static final Log LOG = ExoLogger.getLogger(QueryServiceImpl.class.getName());
70  
71    /**
72     * Constructor method
73     * @param repositoryService
74     * @param nodeHierarchyCreator
75     * @param params
76     * @param containerInfo
77     * @param cacheService
78     * @param organizationService
79     * @param dmsConfiguration
80     * @throws Exception
81     */
82    public QueryServiceImpl(RepositoryService repositoryService, NodeHierarchyCreator nodeHierarchyCreator,
83        InitParams params, PortalContainerInfo containerInfo, CacheService cacheService,
84        OrganizationService organizationService, DMSConfiguration dmsConfiguration) throws Exception {
85      relativePath_ = params.getValueParam("relativePath").getValue();
86      group_ = params.getValueParam("group").getValue();
87      repositoryService_ = repositoryService;
88      containerInfo_ = containerInfo;
89      cache_ = cacheService.getCacheInstance(CACHE_NAME);
90      organizationService_ = organizationService;
91      nodeHierarchyCreator_ = nodeHierarchyCreator;
92      baseUserPath_ = nodeHierarchyCreator.getJcrPath(BasePath.CMS_USERS_PATH);
93      baseQueriesPath_ = nodeHierarchyCreator.getJcrPath(BasePath.QUERIES_PATH);
94      dmsConfiguration_ = dmsConfiguration;
95    }
96  
97    /**
98     * Implemented method from Startable class
99     * init all ManageDrivePlugin
100    * @see QueryPlugin
101    */
102   public void start() {
103     configuredQueries_ = new HashSet<String>();
104     for(QueryPlugin queryPlugin : queryPlugins_){
105       try{
106         queryPlugin.init(baseQueriesPath_);
107         configuredQueries_.addAll(queryPlugin.getAllConfiguredQueries());
108       }catch (Exception e) {
109         if (LOG.isErrorEnabled()) {
110           LOG.error("Can not start query plugin '" + queryPlugin.getName() + "'", e);
111         }
112       }
113     }
114   }
115 
116   /**
117    * Implemented method from Startable class
118    */
119   public void stop() {
120   }
121   
122   /**
123    * Init query node with current repository
124    */
125   public void init() throws Exception {
126     configuredQueries_ = new HashSet<String>();
127     for(QueryPlugin queryPlugin : queryPlugins_){
128       try{
129         queryPlugin.init(baseQueriesPath_);
130         configuredQueries_.addAll(queryPlugin.getAllConfiguredQueries());
131       }catch (Exception e) {
132         if (LOG.isErrorEnabled()) {
133           LOG.error("Can not init query plugin '" + queryPlugin.getName() + "'", e);
134         }
135       }
136     }
137   }  
138 
139   /**
140    * Add new QueryPlugin to queryPlugins_
141    * @see                   QueryPlugin
142    * @param queryPlugin     QueryPlugin
143    */
144   public void setQueryPlugin(QueryPlugin queryPlugin) {
145     queryPlugins_.add(queryPlugin);
146   }
147 
148   /**
149    * {@inheritDoc}
150    */
151   public String getRelativePath() { return relativePath_; }
152   
153   /**
154    * {@inheritDoc}
155    */
156   public List<Query> getQueries(String userName, SessionProvider provider) throws Exception {
157     List<Query> queries = new ArrayList<Query>();
158     if (userName == null) return queries;
159     Session session = getSession(provider, true);
160     QueryManager manager = session.getWorkspace().getQueryManager();
161     Node usersHome;
162     try {
163       usersHome = (Node)session.getItem(baseUserPath_);
164     } catch (PathNotFoundException e) {
165       usersHome = (Node)getSession(provider, false).getItem(baseUserPath_);
166     }
167     Node userHome = null;
168     try {
169       userHome = nodeHierarchyCreator_.getUserNode(provider, userName);
170     } catch(Exception e) {
171       if (LOG.isWarnEnabled()) {
172         LOG.warn(e.getMessage());
173       }
174     }
175     if (userHome == null) {
176       if(usersHome.hasNode(userName)) {
177         userHome = usersHome.getNode(userName);
178       } else{
179         userHome = usersHome.addNode(userName);
180         if(userHome.canAddMixin("exo:privilegeable")){
181           userHome.addMixin("exo:privilegeable");
182           
183         }
184         ((ExtendedNode)userHome).setPermissions(getPermissions(userName));
185         Node query = null;
186         if(userHome.hasNode(relativePath_)) {
187           query = userHome.getNode(relativePath_);
188         } else {
189           query = getNodeByRelativePath(userHome, relativePath_);
190         }
191         if (query.canAddMixin("exo:privilegeable")){
192           query.addMixin("exo:privilegeable");
193         }
194         ((ExtendedNode)query).setPermissions(getPermissions(userName));
195         usersHome.save();
196       }
197     }
198     Node queriesHome = null;
199     if(userHome.hasNode(relativePath_)) {
200       queriesHome = userHome.getNode(relativePath_);
201     } else {
202       queriesHome = getNodeByRelativePath(userHome, relativePath_);
203     }
204     NodeIterator iter = queriesHome.getNodes();
205     while (iter.hasNext()) {
206       Node node = iter.nextNode();
207       if(node.isNodeType("nt:query")) queries.add(manager.getQuery(node));
208     }
209     return queries;
210   }  
211 
212   /**
213    * Get node by giving the node user and the relative path to its
214    * @param userHome      Node user
215    * @param relativePath  The relative path to its
216    * @return
217    * @throws Exception
218    */
219   private Node getNodeByRelativePath(Node userHome, String relativePath) throws Exception {
220     String[] paths = relativePath.split("/");
221     StringBuffer relPath = null;
222     Node queriesHome = null;
223     for (String path : paths) {
224       if (relPath == null)
225         relPath = new StringBuffer(path);
226       else
227         relPath.append("/").append(path);
228       if (!userHome.hasNode(relPath.toString()))
229         queriesHome = userHome.addNode(relPath.toString());
230     }
231     return queriesHome;
232   }
233 
234   /**
235    * Get all permission of the giving owner
236    * @param owner
237    * @return
238    */
239   private Map<String,String[]> getPermissions(String owner) {
240     Map<String, String[]> permissions = new HashMap<String, String[]>();
241     permissions.put(owner, perms);
242     permissions.put(group_, perms);
243     return permissions;
244   }
245   
246   /**
247    * {@inheritDoc}
248    */
249   public void addQuery(String queryName, String statement, String language, String userName) throws Exception {
250     if (userName == null)
251       return;
252     Session session = getSession();
253     QueryManager manager = session.getWorkspace().getQueryManager();
254     Query query = manager.createQuery(statement, language);
255     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
256     Node userNode = nodeHierarchyCreator_.getUserNode(sessionProvider, userName);
257     if (!userNode.hasNode(getRelativePath())) {
258       getNodeByRelativePath(userNode, relativePath_);
259       session.save();
260     }
261     String absPath = userNode.getPath() + "/" + relativePath_ + "/" + queryName;
262     query.storeAsNode(absPath);
263     session.save();  
264   }
265   
266   /**
267    * {@inheritDoc}
268    */
269   public void removeQuery(String queryPath, String userName) throws Exception {
270     if (userName == null)
271       return;
272     Session session = getSession();
273     
274     Node queryNode = null;
275     try {
276       queryNode = (Node) session.getItem(queryPath);
277     } catch (PathNotFoundException pe) {
278       queryNode = (Node) getSession(WCMCoreUtils.getSystemSessionProvider(), true).getItem(queryPath);
279     }
280     Node queriesHome = queryNode.getParent();
281     queryNode.remove();
282     queriesHome.save();
283     removeFromCache(queryPath);
284   }  
285 
286   /**
287    * {@inheritDoc}
288    */
289   public void addSharedQuery(String queryName,
290                              String statement,
291                              String language,
292                              String[] permissions,
293                              boolean cachedResult) throws Exception {
294     addSharedQuery(queryName,
295                    statement,
296                    language,
297                    permissions,
298                    cachedResult,
299                    WCMCoreUtils.getUserSessionProvider());
300   }
301   
302   public void addSharedQuery(String queryName,
303                              String statement,
304                              String language,
305                              String[] permissions,
306                              boolean cachedResult,
307                              SessionProvider provider) throws Exception {
308     Session session = getSession(provider, true);
309     ValueFactory vt = session.getValueFactory();
310     String queryPath;
311     List<Value> perm = new ArrayList<Value>();
312     for (String permission : permissions) {
313       Value vl = vt.createValue(permission);
314       perm.add(vl);
315     }
316     Value[] vls = perm.toArray(new Value[] {});
317 
318     String queriesPath = baseQueriesPath_;
319     Node queryHome = (Node)session.getItem(baseQueriesPath_);
320     QueryManager queryManager = session.getWorkspace().getQueryManager();
321     queryManager.createQuery(statement, language);
322     if (queryHome.hasNode(queryName)) {
323       Node query = queryHome.getNode(queryName);
324       query.setProperty("jcr:language", language);
325       query.setProperty("jcr:statement", statement);
326       query.setProperty("exo:accessPermissions", vls);
327       query.setProperty("exo:cachedResult", cachedResult);
328       query.save();
329       session.save();
330       queryPath = query.getPath();
331     } else {
332       QueryManager manager = session.getWorkspace().getQueryManager();
333       Query query = manager.createQuery(statement, language);
334       Node newQuery = query.storeAsNode(baseQueriesPath_ + "/" + queryName);
335       newQuery.addMixin("mix:sharedQuery");
336       newQuery.setProperty("exo:accessPermissions", vls);
337       newQuery.setProperty("exo:cachedResult", cachedResult);
338       session.getItem(queriesPath).save();
339       queryPath = queriesPath;
340     }
341     removeFromCache(queryPath);
342   }  
343   
344   /**
345    * {@inheritDoc}
346    */
347   public Node getSharedQuery(String queryName, SessionProvider provider) throws Exception {
348     Session session = getSession(provider, true);
349     try {
350       Node sharedQueryNode = (Node) session.getItem(baseQueriesPath_ + "/" + queryName);
351       return sharedQueryNode;
352     } catch (PathNotFoundException e) {
353       return null;
354     }
355   }  
356 
357   /**
358    * {@inheritDoc}
359    */
360   public List<Node> getSharedQueries(SessionProvider provider) throws Exception {
361     Session session = getSession(provider, true);
362     List<Node> queries = new ArrayList<Node>();
363     Node sharedQueryHome = (Node) session.getItem(baseQueriesPath_);
364     NodeIterator iter = sharedQueryHome.getNodes();
365     while (iter.hasNext()) {
366       Node node = iter.nextNode();
367       if(node.isNodeType("nt:query")) {
368         queries.add(node);
369       }
370     }
371     return queries;
372   }
373   
374   /**
375    * {@inheritDoc}
376    */
377   public List<Node> getSharedQueries(String userId, SessionProvider provider) throws Exception {
378     List<Node> sharedQueries = new ArrayList<Node>();
379     for(Node query : getSharedQueries(provider)) {
380       if (canUseQuery(userId, query)) {
381         sharedQueries.add(query);
382       }
383     }
384     return sharedQueries;
385   }  
386   
387   /**
388    * {@inheritDoc}
389    */
390   public List<Node> getSharedQueries(String queryType,
391                                      String userId,
392                                      SessionProvider provider) throws Exception {
393     List<Node> resultList = new ArrayList<Node>();
394     String language = null;
395     for (Node queryNode: getSharedQueries(provider)) {
396       language = queryNode.getProperty("jcr:language").getString();
397       if (!queryType.equalsIgnoreCase(language)) continue;
398       if (canUseQuery(userId,queryNode)) {
399         resultList.add(queryNode);
400       }
401     }
402     return resultList;
403   }  
404   
405   /**
406    * {@inheritDoc}
407    */
408   public Query getQueryByPath(String queryPath, String userName, SessionProvider provider) throws Exception {
409     List<Query> queries = getQueries(userName, provider);
410     for (Query query : queries) {
411       if (query.getStoredQueryPath().equals(queryPath)) return query;
412     }
413     return null;
414   }  
415   
416   /**
417    * {@inheritDoc}
418    */
419   public void removeSharedQuery(String queryName, SessionProvider provider) throws Exception {
420     Session session = getSession(provider, true);
421     session.getItem(baseQueriesPath_ + "/" + queryName).remove();
422     session.save();
423   }  
424   
425   /**
426    * {@inheritDoc}
427    */
428   public QueryResult execute(String queryPath,
429                              String workspace,
430                              SessionProvider provider,
431                              String userId) throws Exception {
432     Session session = getSession(provider, true);
433     Session querySession = getSession(workspace, provider);
434     Node queryNode = null;
435     try {
436       queryNode = (Node) session.getItem(queryPath);
437     } catch (PathNotFoundException e) {
438       if (LOG.isWarnEnabled()) {
439         LOG.warn("Can not find node by path " + queryPath + " in dms-system workspace");
440       }
441       queryNode = (Node) querySession.getItem(queryPath);
442     }
443     if (queryNode != null && queryNode.hasProperty("exo:cachedResult")
444         && queryNode.getProperty("exo:cachedResult").getBoolean()) {
445       String portalName = containerInfo_.getContainerName();
446       String key = portalName + queryPath;
447       QueryResult result = cache_.get(key);
448       if (result != null) return result;
449       result = execute(querySession, queryNode, userId);
450       cache_.put(key, result);
451       return result;
452     }
453     QueryResult queryResult = execute(querySession, queryNode, userId);
454     return queryResult;
455   }  
456 
457   /**
458    * Execute the query by giving the session, query node and userid
459    * @param session     The Session
460    * @param queryNode   The node of query
461    * @param userId      The userid
462    * @return
463    * @throws Exception
464    */
465   private QueryResult execute(Session session, Node queryNode, String userId) throws Exception {
466     return createQuery(session, queryNode, userId).execute();
467   }
468 
469   /**
470    * This method replaces tokens in the statement by their actual values
471    * Current supported tokens are :
472    * ${UserId}$ corresponds to the current user
473    * ${Date}$   corresponds to the current date
474    * That way, predefined queries can be equipped with dynamic values. This is
475    * useful when querying for documents made by the current user, or documents
476    * in publication state.
477    *
478    * @return the processed String, with replaced tokens
479    */
480   private String computeStatement(String statement, String userId) {
481 
482     // The returned computed statement
483     String ret = statement;
484 
485     // Replace ${UserId}$
486     ret = ret.replace("${UserId}$",userId);
487 
488     // Replace ${Date}$
489     String currentDate = ISO8601.format(new GregorianCalendar());
490     ret = ret.replace("${Date}$",currentDate);
491 
492     return ret;
493   }
494 
495   /**
496    * Remove query from cache by giving the query path
497    * @param queryPath   The path to query
498    * @throws Exception
499    */
500   private void removeFromCache(String queryPath) throws Exception {
501     String portalName = containerInfo_.getContainerName();
502     String key = portalName + queryPath;
503     QueryResult result = cache_.get(key);
504     if (result != null) cache_.remove(key);
505   }
506 
507   /**
508    * Get the session with curent repository
509    * @return
510    * @throws Exception
511    */
512   private Session getSession() throws Exception {
513     ManageableRepository manageableRepository = repositoryService_.getCurrentRepository();
514     SessionProvider sessionProvider = WCMCoreUtils.getSystemSessionProvider();
515     return sessionProvider.getSession(manageableRepository.getConfiguration().getDefaultWorkspaceName(), 
516         manageableRepository);
517 
518   }
519 
520   /**
521    * Get the session by specify the repository, sessionprovider and flag params
522    * @param provider      The SessionProvider
523    * @param flag          The boolean to decide which session will be chosen
524    * @return
525    * @throws Exception
526    */
527   private Session getSession(SessionProvider provider, boolean flag) throws Exception {
528     ManageableRepository manageableRepository = repositoryService_.getCurrentRepository();
529     if (!flag) {
530       String workspace = manageableRepository.getConfiguration().getDefaultWorkspaceName();
531       return provider.getSession(workspace, manageableRepository);
532     }
533     DMSRepositoryConfiguration dmsRepoConfig = dmsConfiguration_.getConfig();
534     return provider.getSession(dmsRepoConfig.getSystemWorkspace(), manageableRepository);
535   }
536 
537   /**
538    * Get the session by specify the repository, workspace and sessionprovider
539    * @param workspace     The workspace name
540    * @param provider      The SessionProvider
541    * @return
542    * @throws Exception
543    */
544   private Session getSession(String workspace, SessionProvider provider) throws Exception {
545     ManageableRepository manageableRepository = repositoryService_.getCurrentRepository();
546     return provider.getSession(workspace,manageableRepository);
547   }
548 
549   /**
550    * Check the given user can use this query
551    * @param userId      The user id
552    * @param queryNode   The node of query
553    * @return
554    * @throws Exception
555    */
556   private boolean canUseQuery(String userId, Node queryNode) throws Exception{
557     Value[] values = queryNode.getProperty("exo:accessPermissions").getValues();
558     for(Value value : values) {
559       String accessPermission = value.getString();
560       if (hasMembership(userId,accessPermission)) {
561         return true;
562       }
563     }
564     return false;
565   }
566 
567   /**
568    * Check the user which has a given membership
569    * @param userId          The user id
570    * @param roleExpression  The expression of membership
571    * @return
572    */
573   private boolean hasMembership(String userId, String roleExpression) {
574     if (userId == null || userId.length() == 0) {
575       return false;
576     }
577     if(roleExpression.equals("*") || roleExpression.equals(IdentityConstants.ANY))
578       return true;
579     ConversationState conversationState = ConversationState.getCurrent();
580     Identity identity = conversationState.getIdentity();
581     String membershipType = roleExpression.substring(0, roleExpression.indexOf(":"));
582     String groupName = roleExpression.substring(roleExpression.indexOf(":") + 1);
583     try {
584       MembershipHandler membershipHandler = organizationService_.getMembershipHandler();
585       if ("*".equals(membershipType)) {
586     	// Determine if there exists at least one membership
587         if (userId.equals(ConversationState.getCurrent().getIdentity().getUserId())) {
588           return identity.isMemberOf(groupName);
589         } else {
590           return !membershipHandler.findMembershipsByUserAndGroup( userId,groupName).isEmpty();
591         }
592       }
593       if (userId.equals(ConversationState.getCurrent().getIdentity().getUserId())) {
594     	  return identity.isMemberOf(groupName, membershipType);
595       } else {
596         // Determine if there exists the membership of specified type
597         return membershipHandler.findMembershipByUserGroupAndType(userId,groupName,membershipType) != null;
598       }
599     }
600     catch(Exception e) {
601       return false;
602     }
603   }
604 
605   /**
606    * {@inheritDoc}
607    */
608   public Query getQuery(String queryPath, String workspace, SessionProvider provider, String userId) throws Exception {
609     Session session = getSession(provider, true);
610     Session querySession = getSession(workspace, provider);
611     Node queryNode = null;
612     try {
613       queryNode = (Node) session.getItem(queryPath);
614     } catch (PathNotFoundException e) {
615       if (LOG.isWarnEnabled()) {
616         LOG.warn("Can not find node by path " + queryPath + " in dms-system workspace");
617       }
618       queryNode = (Node) querySession.getItem(queryPath);
619     }
620     return createQuery(querySession, queryNode, userId);
621   }
622   
623   /**
624    * Creates the Query object by giving the session, query node and userid
625    * @param session     The Session
626    * @param queryNode   The node of query
627    * @param userId      The userid
628    * @return
629    * @throws Exception
630    */
631   private Query createQuery(Session session, Node queryNode, String userId) throws Exception {
632     String statement = this.computeStatement(queryNode.getProperty("jcr:statement").getString(), userId);
633     String language = queryNode.getProperty("jcr:language").getString();
634     Query query = session.getWorkspace().getQueryManager().createQuery(statement,language);
635     return query;
636   }
637 
638   @Override
639   public Set<String> getAllConfiguredQueries() {
640     return configuredQueries_;
641   }
642   
643 }