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.r1.common.search.dto.SearchParam;
29  import org.kuali.student.r1.common.search.dto.SearchRequest;
30  import org.kuali.student.r1.common.search.dto.SearchResult;
31  import org.kuali.student.r1.common.search.dto.SearchResultCell;
32  import org.kuali.student.r1.common.search.dto.SearchResultRow;
33  import org.kuali.student.r1.common.search.service.SearchDispatcher;
34  import org.springframework.beans.factory.InitializingBean;
35  
36  import com.google.gwt.user.server.rpc.RemoteServiceServlet;
37  
38  public class SearchDispatchRpcGwtServlet extends RemoteServiceServlet implements SearchRpcService, InitializingBean {
39  
40      private static final long serialVersionUID = 1L;
41  
42      private IdTranslatorFilter idTranslatorFilter;
43  
44      private SearchDispatcher searchDispatcher;
45  
46      protected boolean cachingEnabled = false;
47  	protected int searchCacheMaxSize = 20;
48  	protected int searchCacheMaxAgeSeconds = 90;
49  	protected Map<String,MaxAgeSoftReference<SearchResult>> searchCache;
50  	
51      
52      public SearchDispatchRpcGwtServlet() {
53          super();
54      }
55  
56      /**
57       * Delegates to the service responsible for the given search type key
58       *
59       * @param searchRequest
60       * @return The searchResult from the delegated search or null
61       * @throws MissingParameterException
62       */
63      @Override
64      public SearchResult search(SearchRequest searchRequest) {
65          try
66          {
67              SearchResult searchResult = searchDispatcher.dispatchSearch(searchRequest);
68              List<SearchParam> params = searchRequest.getParams();
69              if (params != null && params.size() > 0) {
70                  SearchParam firstParam = params.get(0);
71                  if (firstParam.getKey().equals("lu.queryParam.cluVersionIndId")) {//FIXME can this special case be handled after this call?
72                      doIdTranslation(searchResult);
73                  }
74              }
75              return searchResult;
76          } catch (Exception ex) {
77              // Log exception 
78              ex.printStackTrace();
79              throw new RuntimeException(ex);
80          }
81      }
82  
83      @Override
84      public SearchResult cachingSearch(SearchRequest searchRequest) {
85          try
86          {
87              String cacheKey = searchRequest.toString();
88              if (cachingEnabled) {
89  
90                  //Get From Cache
91                  MaxAgeSoftReference<SearchResult> ref = searchCache.get(cacheKey);
92                  if (ref != null) {
93                      SearchResult cachedSearchResult = ref.get();
94                      if (cachedSearchResult != null) {
95                          return cachedSearchResult;
96                      }
97                  }
98              }
99  
100             //Perform the actual Search
101             SearchResult searchResult = search(searchRequest);
102 
103             if (cachingEnabled) {
104                 //Store to cache
105                 searchCache
106                         .put(cacheKey, new MaxAgeSoftReference<SearchResult>(searchCacheMaxAgeSeconds, searchResult));
107             }
108 
109             return searchResult;
110         } catch (Exception ex) {
111             // Log exception 
112             ex.printStackTrace();
113             throw new RuntimeException(ex);
114         }
115     }
116 
117     private void doIdTranslation(SearchResult searchResult) {
118         for (SearchResultRow searchResultRow : searchResult.getRows()) {
119             for (SearchResultCell searchResultCell : searchResultRow.getCells()) {
120                 String value = searchResultCell.getValue();
121                 if (value != null && value.startsWith("kuali.atp")) {
122                     String newValue = idTranslatorFilter.getTranslationForAtp(value);
123                     if (newValue != null) {
124                         searchResultCell.setValue(newValue);
125                     }
126                 }
127             }
128         }
129     }
130 
131 	@Override
132 	public void afterPropertiesSet() throws Exception {
133 		if(cachingEnabled){
134 			searchCache = Collections.synchronizedMap( new MaxSizeMap<String,MaxAgeSoftReference<SearchResult>>( searchCacheMaxSize ) );
135 		}
136 	}
137     
138     public void setSearchDispatcher(SearchDispatcher searchDispatcher) {
139         this.searchDispatcher = searchDispatcher;
140     }
141 
142     public void setIdTranslatorFilter(IdTranslatorFilter idTranslatorFilter) {
143         this.idTranslatorFilter = idTranslatorFilter;
144     }
145 
146 	public void setCachingEnabled(boolean cachingEnabled) {
147 		this.cachingEnabled = cachingEnabled;
148 	}
149 
150 	public void setSearchCacheMaxSize(int searchCacheMaxSize) {
151 		this.searchCacheMaxSize = searchCacheMaxSize;
152 	}
153 
154 	public void setSearchCacheMaxAgeSeconds(int searchCacheMaxAgeSeconds) {
155 		this.searchCacheMaxAgeSeconds = searchCacheMaxAgeSeconds;
156 	}
157 	
158 	
159 	//Added as inner classes to avoid huge dependency on rice-impl
160 	/**
161 	 * An extension to SoftReference that stores an expiration time for the 
162 	 * value stored in the SoftReference. If no expiration time is passed in
163 	 * the value will never be cached.  
164 	 */
165 	public class MaxAgeSoftReference<T> extends SoftReference<T> {
166 		
167 		private long expires;
168 
169 		public MaxAgeSoftReference(long expires, T referent) {
170 			super(referent);
171 			this.expires = System.currentTimeMillis() + expires * 1000;
172 		}
173 		
174 		public boolean isValid() {
175 			return System.currentTimeMillis() < expires;
176 		}
177 		
178 		public T get() {			
179 			return isValid() ? super.get() : null;
180 		}		
181 		
182 	}
183 	/**
184 	 * This class acts like an LRU cache, automatically purging contents when it gets above a certain size. 
185 	 * 
186 	 * @author Kuali Rice Team (rice.collab@kuali.org)
187 	 */
188 	public class MaxSizeMap<K,V> extends LinkedHashMap<K,V> {
189 		private static final long serialVersionUID = -5354227348838839919L;
190 
191 		private int maxSize;
192 		
193 		/**
194 		 * @param maxSize
195 		 */
196 		public MaxSizeMap( int maxSize  ) {
197 			this( maxSize, false );
198 		}
199 		/**
200 		 * @param maxSize
201 		 * @param accessOrder Whether to sort in the order accessed rather than the order inserted.
202 		 */
203 		public MaxSizeMap( int maxSize, boolean accessOrder ) {
204 			super( maxSize / 2, 0.75f, accessOrder );
205 			this.maxSize = maxSize;
206 		}
207 		
208 		/**
209 		 * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
210 		 */
211 		@Override
212 		protected boolean removeEldestEntry(Entry<K,V> eldest) {
213 			return size() > maxSize;
214 		}
215 		
216 	}
217 }