001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.pageStore; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.function.Supplier; 027import java.util.stream.Collectors; 028import java.util.stream.StreamSupport; 029 030import org.apache.wicket.Application; 031import org.apache.wicket.WicketRuntimeException; 032import org.apache.wicket.core.util.lang.WicketObjects; 033import org.apache.wicket.page.IManageablePage; 034import org.apache.wicket.util.lang.Args; 035import org.apache.wicket.util.lang.Bytes; 036import org.apache.wicket.util.lang.Classes; 037 038/** 039 * A storage of pages in memory. 040 */ 041public class InMemoryPageStore extends AbstractPersistentPageStore implements IPersistentPageStore 042{ 043 044 private final Map<String, IMemoryData> datas; 045 046 private final Supplier<IMemoryData> dataCreator; 047 048 /** 049 * Keep {@code maxPages} for each session. 050 * 051 * @param applicationName 052 * {@link Application#getName()} 053 * @param maxPages 054 * max pages per session 055 */ 056 public InMemoryPageStore(String applicationName, int maxPages) 057 { 058 this(applicationName, () -> new CountLimitedData(maxPages), new ConcurrentHashMap<>()); 059 } 060 061 /** 062 * Keep page up to {@code maxBytes} for each session. 063 * <p> 064 * All pages added to this store <em>must</em> be {@code SerializedPage}s. You can achieve this 065 * by letting a {@link SerializingPageStore} delegate to this store. 066 * 067 * @param applicationName 068 * {@link Application#getName()} 069 * @param maxBytes 070 * maximum bytes to keep in session 071 */ 072 public InMemoryPageStore(String applicationName, Bytes maxBytes) 073 { 074 this(applicationName, () -> new SizeLimitedData(maxBytes), new ConcurrentHashMap<>()); 075 } 076 077 /** 078 * @param applicationName 079 * {@link Application#getName()} 080 * @param dataCreator 081 * creator of new data 082 * @param datas 083 * storage for datas 084 */ 085 protected InMemoryPageStore(String applicationName, Supplier<IMemoryData> dataCreator, 086 Map<String, IMemoryData> datas) 087 { 088 super(applicationName); 089 090 this.dataCreator = dataCreator; 091 092 this.datas = datas; 093 } 094 095 /** 096 * Versioning is not supported. 097 */ 098 @Override 099 public boolean supportsVersioning() 100 { 101 return false; 102 } 103 104 @Override 105 protected IManageablePage getPersistedPage(String sessionIdentifier, int id) 106 { 107 IMemoryData data = getMemoryData(sessionIdentifier, false); 108 if (data != null) 109 { 110 return data.get(id); 111 } 112 113 return null; 114 } 115 116 @Override 117 protected void removePersistedPage(String sessionIdentifier, IManageablePage page) 118 { 119 IMemoryData data = getMemoryData(sessionIdentifier, false); 120 if (data != null) 121 { 122 synchronized (data) 123 { 124 data.remove(page.getPageId()); 125 } 126 } 127 } 128 129 @Override 130 protected void removeAllPersistedPages(String sessionIdentifier) 131 { 132 datas.remove(sessionIdentifier); 133 } 134 135 @Override 136 protected void addPersistedPage(String sessionIdentifier, IManageablePage page) 137 { 138 IMemoryData data = getMemoryData(sessionIdentifier, true); 139 140 data.add(page); 141 } 142 143 @Override 144 public Set<String> getSessionIdentifiers() 145 { 146 return datas.keySet(); 147 } 148 149 @Override 150 public List<IPersistedPage> getPersistedPages(String sessionIdentifier) 151 { 152 IMemoryData data = datas.get(sessionIdentifier); 153 if (data == null) 154 { 155 return new ArrayList<>(); 156 } 157 158 synchronized (data) 159 { 160 return StreamSupport.stream(data.spliterator(), false).map(page -> { 161 String pageType = page instanceof SerializedPage 162 ? ((SerializedPage)page).getPageType() 163 : Classes.name(page.getClass()); 164 165 return new PersistedPage(page.getPageId(), pageType, getSize(page)); 166 }).collect(Collectors.toList()); 167 } 168 } 169 170 @Override 171 public Bytes getTotalSize() 172 { 173 int size = 0; 174 175 for (IMemoryData data : datas.values()) 176 { 177 synchronized (data) 178 { 179 for (IManageablePage page : data) 180 { 181 size += getSize(page); 182 } 183 } 184 } 185 186 return Bytes.bytes(size); 187 } 188 189 /** 190 * Get the size of the given page. 191 */ 192 protected long getSize(IManageablePage page) 193 { 194 if (page instanceof SerializedPage) 195 { 196 return ((SerializedPage)page).getData().length; 197 } 198 else 199 { 200 return WicketObjects.sizeof(page); 201 } 202 } 203 204 private IMemoryData getMemoryData(String sessionIdentifier, boolean create) 205 { 206 if (!create) 207 { 208 return datas.get(sessionIdentifier); 209 } 210 211 IMemoryData data = dataCreator.get(); 212 IMemoryData existing = datas.putIfAbsent(sessionIdentifier, data); 213 return existing != null ? existing : data; 214 } 215 216 /** 217 * Pages kept in memory for a session. 218 */ 219 public interface IMemoryData extends Iterable<IManageablePage> 220 { 221 /** 222 * Remove a page. 223 * 224 * @param pageId 225 * @return 226 */ 227 IManageablePage remove(int pageId); 228 229 /** 230 * Add a page. 231 * 232 * @param page 233 */ 234 void add(IManageablePage page); 235 236 /** 237 * Get a page. 238 * 239 * @param id 240 * @return 241 */ 242 IManageablePage get(int id); 243 } 244 245 /** 246 * List based implementation. 247 */ 248 protected static class MemoryData implements IMemoryData 249 { 250 /** 251 * Kept in list instead of map, since non-serialized pages might change their id during a 252 * request. 253 */ 254 List<IManageablePage> pages = new LinkedList<>(); 255 256 @Override 257 public Iterator<IManageablePage> iterator() 258 { 259 return pages.iterator(); 260 } 261 262 @Override 263 public synchronized void add(IManageablePage page) 264 { 265 remove(page.getPageId()); 266 267 pages.add(page); 268 } 269 270 @Override 271 public synchronized IManageablePage remove(int pageId) 272 { 273 Iterator<IManageablePage> iterator = pages.iterator(); 274 while (iterator.hasNext()) 275 { 276 IManageablePage page = iterator.next(); 277 278 if (page.getPageId() == pageId) 279 { 280 iterator.remove(); 281 return page; 282 } 283 } 284 return null; 285 } 286 287 @Override 288 public synchronized IManageablePage get(int pageId) 289 { 290 for (final IManageablePage page : pages) 291 { 292 if (page.getPageId() == pageId) 293 { 294 return page; 295 } 296 } 297 298 return null; 299 } 300 301 protected void removeOldest() 302 { 303 IManageablePage page = pages.iterator().next(); 304 305 remove(page.getPageId()); 306 } 307 } 308 309 /** 310 * Limit pages by count. 311 */ 312 protected static class CountLimitedData extends MemoryData 313 { 314 private final int maxPages; 315 316 public CountLimitedData(int maxPages) 317 { 318 this.maxPages = Args.withinRange(1, Integer.MAX_VALUE, maxPages, "maxPages"); 319 } 320 321 @Override 322 public synchronized void add(IManageablePage page) 323 { 324 super.add(page); 325 326 while (pages.size() > maxPages) 327 { 328 removeOldest(); 329 } 330 } 331 } 332 333 /** 334 * Limit pages by size. 335 */ 336 protected static class SizeLimitedData extends MemoryData 337 { 338 private final Bytes maxBytes; 339 340 private long size; 341 342 public SizeLimitedData(Bytes maxBytes) 343 { 344 Args.notNull(maxBytes, "maxBytes"); 345 346 this.maxBytes = Args.withinRange(Bytes.bytes(1), Bytes.MAX, maxBytes, "maxBytes"); 347 } 348 349 @Override 350 public synchronized void add(IManageablePage page) 351 { 352 if (page instanceof SerializedPage == false) 353 { 354 throw new WicketRuntimeException( 355 "InMemoryPageStore limited by size works with serialized pages only"); 356 } 357 358 super.add(page); 359 360 size += ((SerializedPage)page).getData().length; 361 362 while (size > maxBytes.bytes()) 363 { 364 removeOldest(); 365 } 366 } 367 368 @Override 369 public synchronized IManageablePage remove(int pageId) 370 { 371 SerializedPage page = (SerializedPage)super.remove(pageId); 372 if (page != null) 373 { 374 size -= page.getData().length; 375 } 376 return page; 377 } 378 } 379}