View Javadoc
1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.student.common.ui.server.gwt;
17  
18  import java.lang.ref.SoftReference;
19  import java.util.Collections;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  
25  import org.kuali.student.common.ui.client.service.SearchRpcService;
26  import org.kuali.student.r1.common.assembly.transform.IdTranslatorFilter;
27  import org.kuali.student.r2.common.exceptions.MissingParameterException;
28  import org.kuali.student.r2.core.search.dto.*;
29  import org.kuali.student.r2.core.search.service.SearchService;
30  import org.kuali.student.common.util.security.ContextUtils;
31  import org.springframework.beans.factory.InitializingBean;
32  
33  import com.google.gwt.user.server.rpc.RemoteServiceServlet;
34  
35  public class SearchDispatchRpcGwtServlet extends RemoteServiceServlet implements SearchRpcService, InitializingBean {
36  
37      private static final long serialVersionUID = 1L;
38  
39      private IdTranslatorFilter idTranslatorFilter;
40  
41      private SearchService searchDispatcher;
42  
43      protected boolean cachingEnabled = false;
44  	protected int searchCacheMaxSize = 20;
45  	protected int searchCacheMaxAgeSeconds = 90;
46  	protected Map<String,MaxAgeSoftReference<SearchResultInfo>> searchCache;
47  	
48      
49      public SearchDispatchRpcGwtServlet() {
50          super();
51      }
52  
53      /**
54       * Delegates to the service responsible for the given search type key
55       *
56       * @param searchRequest
57       * @return The searchResult from the delegated search or null
58       * @throws MissingParameterException
59       */
60      @Override
61      public SearchResultInfo search(SearchRequestInfo searchRequest) {
62          try
63          {
64              SearchResultInfo searchResult = searchDispatcher.search(searchRequest, ContextUtils.getContextInfo());
65              List<SearchParamInfo> params = searchRequest.getParams();
66              if (params != null && params.size() > 0) {
67                  //Code Changed for JIRA-9075 - SONAR Critical issues - Use get(0) with caution - 5
68                  int firstSearchParamInfo = 0 ;
69                  SearchParamInfo firstParam = params.get(firstSearchParamInfo);
70                  if (firstParam.getKey().equals("lu.queryParam.cluVersionIndId")) {//FIXME can this special case be handled after this call?
71                      doIdTranslation(searchResult);
72                  }
73              }
74              return searchResult;
75          } catch (Exception ex) {
76              // Log exception 
77              ex.printStackTrace();
78              throw new RuntimeException(ex);
79          }
80      }
81  
82      @Override
83      public SearchResultInfo cachingSearch(SearchRequestInfo searchRequest) {
84          try
85          {
86              String cacheKey = searchRequest.toString();
87              if (cachingEnabled) {
88  
89                  //Get From Cache
90                  MaxAgeSoftReference<SearchResultInfo> ref = searchCache.get(cacheKey);
91                  if (ref != null) {
92                      SearchResultInfo cachedSearchResult = ref.get();
93                      if (cachedSearchResult != null) {
94                          return cachedSearchResult;
95                      }
96                  }
97              }
98  
99              //Perform the actual Search
100             SearchResultInfo searchResult = search(searchRequest);
101 
102             if (cachingEnabled) {
103                 //Store to cache
104                 searchCache
105                         .put(cacheKey, new MaxAgeSoftReference<SearchResultInfo>(searchCacheMaxAgeSeconds, searchResult));
106             }
107 
108             return searchResult;
109         } catch (Exception ex) {
110             // Log exception 
111             ex.printStackTrace();
112             throw new RuntimeException(ex);
113         }
114     }
115 
116     private void doIdTranslation(SearchResultInfo searchResult) {
117         for (SearchResultRowInfo searchResultRow : searchResult.getRows()) {
118             for (SearchResultCellInfo searchResultCell : searchResultRow.getCells()) {
119                 String value = searchResultCell.getValue();
120                 if (value != null && value.startsWith("kuali.atp")) {
121                     String newValue = idTranslatorFilter.getTranslationForAtp(value, ContextUtils.getContextInfo());
122                     if (newValue != null) {
123                         searchResultCell.setValue(newValue);
124                     }
125                 }
126             }
127         }
128     }
129 
130 	@Override
131 	public void afterPropertiesSet() throws Exception {
132 		if(cachingEnabled){
133 			searchCache = Collections.synchronizedMap( new MaxSizeMap<String,MaxAgeSoftReference<SearchResultInfo>>( searchCacheMaxSize ) );
134 		}
135 	}
136     
137     public void setSearchDispatcher(SearchService searchDispatcher) {
138         this.searchDispatcher = searchDispatcher;
139     }
140 
141     public void setIdTranslatorFilter(IdTranslatorFilter idTranslatorFilter) {
142         this.idTranslatorFilter = idTranslatorFilter;
143     }
144 
145 	public void setCachingEnabled(boolean cachingEnabled) {
146 		this.cachingEnabled = cachingEnabled;
147 	}
148 
149 	public void setSearchCacheMaxSize(int searchCacheMaxSize) {
150 		this.searchCacheMaxSize = searchCacheMaxSize;
151 	}
152 
153 	public void setSearchCacheMaxAgeSeconds(int searchCacheMaxAgeSeconds) {
154 		this.searchCacheMaxAgeSeconds = searchCacheMaxAgeSeconds;
155 	}
156 	
157 	
158 	//Added as inner classes to avoid huge dependency on rice-impl
159 	/**
160 	 * An extension to SoftReference that stores an expiration time for the 
161 	 * value stored in the SoftReference. If no expiration time is passed in
162 	 * the value will never be cached.  
163 	 */
164 	public class MaxAgeSoftReference<T> extends SoftReference<T> {
165 		
166 		private long expires;
167 
168 		public MaxAgeSoftReference(long expires, T referent) {
169 			super(referent);
170 			this.expires = System.currentTimeMillis() + expires * 1000;
171 		}
172 		
173 		public boolean isValid() {
174 			return System.currentTimeMillis() < expires;
175 		}
176 		
177 		public T get() {			
178 			return isValid() ? super.get() : null;
179 		}		
180 		
181 	}
182 	/**
183 	 * This class acts like an LRU cache, automatically purging contents when it gets above a certain size. 
184 	 * 
185 	 * @author Kuali Rice Team (rice.collab@kuali.org)
186 	 */
187 	public class MaxSizeMap<K,V> extends LinkedHashMap<K,V> {
188 		private static final long serialVersionUID = -5354227348838839919L;
189 
190 		private int maxSize;
191 		
192 		/**
193 		 * @param maxSize
194 		 */
195 		public MaxSizeMap( int maxSize  ) {
196 			this( maxSize, false );
197 		}
198 		/**
199 		 * @param maxSize
200 		 * @param accessOrder Whether to sort in the order accessed rather than the order inserted.
201 		 */
202 		public MaxSizeMap( int maxSize, boolean accessOrder ) {
203 			super( maxSize / 2, 0.75f, accessOrder );
204 			this.maxSize = maxSize;
205 		}
206 		
207 		/**
208 		 * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
209 		 */
210 		@Override
211 		protected boolean removeEldestEntry(Entry<K,V> eldest) {
212 			return size() > maxSize;
213 		}
214 		
215 	}
216 }