View Javadoc

1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.web.cache;
17  
18  import org.kuali.rice.core.api.cache.CacheManagerRegistry;
19  import org.kuali.rice.core.api.util.tree.Node;
20  import org.kuali.rice.core.api.util.tree.Tree;
21  import org.kuali.rice.core.impl.services.CoreImplServiceLocator;
22  import org.kuali.rice.kim.api.KimConstants;
23  import org.kuali.rice.kim.api.identity.Person;
24  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
25  import org.kuali.rice.krad.util.GlobalVariables;
26  import org.kuali.rice.krad.util.KRADConstants;
27  import org.kuali.rice.krad.web.controller.UifControllerBase;
28  import org.kuali.rice.krad.web.form.UifFormBase;
29  import org.springframework.cache.CacheManager;
30  import org.springframework.stereotype.Controller;
31  import org.springframework.validation.BindingResult;
32  import org.springframework.web.bind.annotation.ModelAttribute;
33  import org.springframework.web.bind.annotation.RequestMapping;
34  import org.springframework.web.bind.annotation.RequestMethod;
35  import org.springframework.web.servlet.ModelAndView;
36  
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  import java.lang.reflect.InvocationTargetException;
40  import java.util.ArrayList;
41  import java.util.Collections;
42  import java.util.Comparator;
43  import java.util.List;
44  
45  @Controller
46  @RequestMapping(value = "/core/admin/cache")
47  public class CacheAdminController extends UifControllerBase {
48  
49      private CacheManagerRegistry registry;
50  
51      public synchronized CacheManagerRegistry getRegistry() {
52          if (registry == null) {
53              registry = CoreImplServiceLocator.getCacheManagerRegistry();
54          }
55          return registry;
56      }
57  
58      /**
59       * @see org.kuali.rice.krad.web.controller.UifControllerBase#createInitialForm(javax.servlet.http.HttpServletRequest)
60       */
61      @Override
62      protected CacheAdminForm createInitialForm(HttpServletRequest request) {
63          return new CacheAdminForm();
64      }
65  
66      @Override
67  	@RequestMapping(params = "methodToCall=start")
68  	public ModelAndView start(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
69  			HttpServletRequest request, HttpServletResponse response) {
70  
71          final Tree<String, String> cacheTree = new Tree<String,String>();
72  
73          final Node<String,String> root = new Node<String,String>("Root", "Root");
74          final List<CacheManager> cms = new ArrayList<CacheManager>(getRegistry().getCacheManagers());
75          Collections.sort(cms, new ByName());
76  
77          for (final CacheManager cm : cms) {
78              final String name = getRegistry().getCacheManagerName(cm);
79              final Node<String, String> cmNode = new Node<String, String>(name, name);
80              final List<String> names = new ArrayList<String>(cm.getCacheNames());
81              Collections.sort(names, String.CASE_INSENSITIVE_ORDER);
82  
83              for (final String cn : names) {
84                  String cacheSize = getCacheSize(name, cn);
85                  final Node<String, String> cNode = new Node<String, String>(cn, cn + (cacheSize != null ? " - " + cacheSize : ""));
86                  //no way to get a keySet from the cache w/o calling the nativeCache
87                  //method which is a bad idea b/c it will tie the rice codebase to
88                  //a caching implementation
89                  cmNode.addChild(cNode);
90              }
91  
92              root.addChild(cmNode);
93          }
94  
95          cacheTree.setRootElement(root);
96          ((CacheAdminForm) form).setCacheTree(cacheTree);
97  
98          return super.start(form, result, request, response);
99      }
100 
101 	@RequestMapping(params = "methodToCall=flush", method = RequestMethod.POST)
102 	public ModelAndView flush(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
103 			HttpServletRequest request, HttpServletResponse response) {
104         Person user = GlobalVariables.getUserSession().getPerson();
105         boolean isAuthorized = KimApiServiceLocator.getPermissionService().isAuthorized(
106 						user.getPrincipalId(),
107 						KRADConstants.KUALI_RICE_SYSTEM_NAMESPACE,
108 						KRADConstants.USE_CACHE_ADMINISTRATION_SCREEN,
109 						Collections.singletonMap(KimConstants.AttributeConstants.PRINCIPAL_ID, user.getPrincipalId()));
110         if(isAuthorized){
111 
112             //FIXME: Could optimize this such that specific cache flushes don't execute if a complete CacheManager
113             //flush was requested
114             for (String name : ((CacheAdminForm) form).getFlush()) {
115                 //path == cacheManager index, cache index
116                 final List<Integer> path = path(removePrefix(name));
117                 final Tree<String, String> tree = ((CacheAdminForm) form).getCacheTree();
118                 final Integer cmIdx = path.get(0);
119                 final Node<String, String> cmNode = tree.getRootElement().getChildren().get(cmIdx);
120                 final String cmName = cmNode.getData();
121                 final CacheManager cm = getRegistry().getCacheManager(cmName);
122 
123                 if (path.size() == 1) {
124                     flushAllCaches(cm);
125                     GlobalVariables.getMessageMap().putInfoForSectionId("mainGroup_div","flush.all.cachemanager", cmName);
126                 } else {
127                     final Integer cIdx = path.get(1);
128                     final Node<String, String> cNode = cmNode.getChildren().get(cIdx);
129                     final String cName = cNode.getData();
130                     flushSpecificCache(cm, cName);
131                     GlobalVariables.getMessageMap().putInfoForSectionId("mainGroup_div",
132                             "flush.single.cachemanager", cName, cmName);
133                 }
134             }
135         }else{
136            GlobalVariables.getMessageMap().putError("flush","error.authorization.general",user.getPrincipalName(),"flush","cachemanager");
137         }
138         return super.start(form, result, request, response);
139     }
140 
141     private static void flushSpecificCache(CacheManager cm, String cache) {
142         for (String s : cm.getCacheNames()) {
143             if (cache.equals(s)) {
144                  cm.getCache(s).clear();
145                  return;
146             }
147         }
148     }
149 
150     private static void flushAllCaches(CacheManager cm) {
151         for (String s : cm.getCacheNames()) {
152             cm.getCache(s).clear();
153         }
154     }
155 
156     // given: 35_node_2_parent_node_0_parent_root will remove "35_"
157     private static String removePrefix(String s) {
158         final StringBuilder sbn = new StringBuilder(s);
159         sbn.delete(0, sbn.indexOf("_") + 1);
160         return sbn.toString();
161     }
162 
163     // given: node_2_parent_node_0_parent_root will return {0, 2}
164     private static List<Integer> path(String s) {
165         final String[] path = s.split("_parent_");
166         final List<Integer> pathIdx = new ArrayList<Integer>();
167         //ignore length - 2 to ignore root
168         for (int i = path.length - 2; i >= 0; i--) {
169             pathIdx.add(Integer.valueOf(path[i].substring(5)));
170         }
171         return Collections.unmodifiableList(pathIdx);
172     }
173 
174     /**
175      * If the cache manager implementation is ehcache, it will return the current size of the cache. Did this in such
176      * a way that it will fallback gracefully just in case the cache implementation being used is not ehcache.
177      */
178     private String getCacheSize(String cacheManagerName, String cacheName) {
179         Object nativeCache = getRegistry().getCacheManager(cacheManagerName).getCache(cacheName).getNativeCache();
180         try {
181             Class<?> ehcache = Class.forName("net.sf.ehcache.Cache");
182             if (ehcache.isInstance(nativeCache)) {
183                 Object intSize = ehcache.getDeclaredMethod("getSize").invoke(nativeCache);
184                 if (intSize != null) {
185                     return intSize.toString();
186                 }
187             }
188         } catch (ClassNotFoundException e) {
189             // ignore
190         } catch (NoSuchMethodException e) {
191             // ignore
192         } catch (InvocationTargetException e) {
193             // ignore
194         } catch (IllegalAccessException e) {
195             // ignore
196         }
197         return null;
198     }
199 
200     private final class ByName implements Comparator<CacheManager> {
201 
202         @Override
203         public int compare(CacheManager o1, CacheManager o2) {
204             return String.CASE_INSENSITIVE_ORDER.compare(getRegistry().getCacheManagerName(o1),
205                     getRegistry().getCacheManagerName(o2));
206         }
207     }
208 
209 }