http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java new file mode 100644 index 0000000..eecd9df --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java @@ -0,0 +1,571 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.Charset; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.UserDefinedFileAttributeView; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.apache.wicket.Session; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.pageStore.disk.NestedFolders; +import org.apache.wicket.serialize.ISerializer; +import org.apache.wicket.util.file.Files; +import org.apache.wicket.util.io.IOUtils; +import org.apache.wicket.util.lang.Args; +import org.apache.wicket.util.lang.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A storage of pages in files. + * <p> + * While {@link DiskPageStore} uses a single file per session, this implementation stores each page + * in its own file. This improves on a {@link DiskPageStore disadvantage of DiskPageStore} surfacing + * with alternating Ajax requests from different browser tabs. + */ +public class FilePageStore implements IPersistentPageStore +{ + private static final String ATTRIBUTE_PAGE_TYPE = "user.wicket_page_type"; + + private static final String FILE_SUFFIX = ".data"; + + private static final Logger log = LoggerFactory.getLogger(FilePageStore.class); + + private static final String KEY = "wicket:FilePageStore"; + + /** + * A cache that holds all page stores. + */ + private static final ConcurrentMap<String, FilePageStore> FILE_STORES = new ConcurrentHashMap<>(); + + private final String applicationName; + + private final ISerializer serializer; + + private final Bytes maxSizePerSession; + + private final NestedFolders folders; + + /** + * Create a store that supports {@link SerializedPage}s only. + * + * @param applicationName + * name of application + * @param fileStoreFolder + * folder to store to + * @param maxSizePerSession + * maximum size per session + * + * @see SerializingPageStore + */ + public FilePageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession) + { + this(applicationName, fileStoreFolder, maxSizePerSession, null); + } + + /** + * Create a store to disk. + * + * @param applicationName + * name of application + * @param fileStoreFolder + * folder to store to + * @param maxSizePerSession + * maximum size per session + * @param serializer + * for serialization of pages + */ + public FilePageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession, + ISerializer serializer) + { + this.applicationName = Args.notNull(applicationName, "applicationName"); + this.folders = new NestedFolders(new File(fileStoreFolder, applicationName + "-filestore")); + this.maxSizePerSession = Args.notNull(maxSizePerSession, "maxSizePerSession"); + this.serializer = serializer; // optional + + if (FILE_STORES.containsKey(applicationName)) + { + throw new IllegalStateException( + "Store for application with key '" + applicationName + "' already exists."); + } + FILE_STORES.put(applicationName, this); + } + + /** + * Versioning is supported if a serializer was provided to the constructor. + */ + @Override + public boolean supportsVersioning() + { + return serializer != null; + } + + @Override + public void destroy() + { + log.debug("Destroying..."); + + FILE_STORES.remove(applicationName); + + log.debug("Destroyed."); + } + + private File getPageFile(String sessionId, int id, boolean create) + { + File folder = folders.get(sessionId, create); + + return new File(folder, id + FILE_SUFFIX); + } + + @Override + public boolean canBeAsynchronous(IPageContext context) + { + // session attribute must be added here *before* any asynchronous calls + // when session is no longer available + getSessionAttribute(context, true); + + return true; + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + SessionAttribute attribute = getSessionAttribute(context, false); + if (attribute == null) + { + return null; + } + + byte[] data = readFile(attribute.identifier, id); + if (data == null) { + return null; + } + + IManageablePage page; + if (serializer == null) + { + page = new SerializedPage(id, "unknown", data); + } + else + { + page = (IManageablePage)serializer.deserialize(data); + } + + return page; + } + + private byte[] readFile(String identifier, int id) + { + File file = getPageFile(identifier, id, false); + if (file.exists() == false) + { + return null; + } + + byte[] data = null; + + try + { + FileChannel channel = FileChannel.open(file.toPath()); + try + { + int size = (int)channel.size(); + MappedByteBuffer buf = channel.map(MapMode.READ_ONLY, 0, size); + data = new byte[size]; + buf.get(data); + } + finally + { + IOUtils.closeQuietly(channel); + } + } + catch (IOException ex) + { + log.warn("cannot read page data for session {} page {}", identifier, id, ex); + } + + return data; + } + + @Override + public void removePage(IPageContext context, IManageablePage page) + { + SessionAttribute attribute = getSessionAttribute(context, false); + if (attribute == null) + { + return; + } + + File file = getPageFile(attribute.identifier, page.getPageId(), false); + if (file.exists()) + { + if (!file.delete()) + { + log.warn("cannot remove page data for session {} page {}", attribute.identifier, + page.getPageId()); + } + } + } + + @Override + public void removeAllPages(IPageContext context) + { + SessionAttribute attribute = getSessionAttribute(context, false); + if (attribute == null) + { + return; + } + + removeFolder(attribute.identifier); + } + + private void removeFolder(String identifier) + { + folders.remove(identifier); + } + + /** + * Supports {@link SerializedPage}s too - for this to work the delegating {@link IPageStore} + * must use the same {@link ISerializer} as this one. + */ + @Override + public void addPage(IPageContext context, IManageablePage page) + { + SessionAttribute attribute = getSessionAttribute(context, false); + if (attribute == null) + { + return; + } + + String type; + byte[] data; + if (page instanceof SerializedPage) + { + type = ((SerializedPage)page).getPageType(); + data = ((SerializedPage)page).getData(); + } + else + { + if (serializer == null) + { + throw new WicketRuntimeException("DiskPageStore not configured for serialization"); + } + type = page.getClass().getName(); + data = serializer.serialize(page); + } + + writeFile(attribute.identifier, page.getPageId(), type, data); + + checkMaxSize(attribute.identifier); + } + + private void writeFile(String identifier, int pageId, String pageType, byte[] data) + { + File file = getPageFile(identifier, pageId, true); + try + { + FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + try + { + ByteBuffer buffer = ByteBuffer.wrap(data); + channel.write(buffer); + } + finally + { + IOUtils.closeQuietly(channel); + } + } + catch (IOException ex) + { + log.warn("cannot store page data for session {} page {}", identifier, pageId, ex); + } + + setPageType(file, pageType); + } + + private void checkMaxSize(String identifier) + { + File[] files = folders.get(identifier, true).listFiles(); + Arrays.sort(files, new LastModifiedComparator()); + + long total = 0; + for (int f = 0; f < files.length; f++) + { + File candidate = files[f]; + + total += candidate.length(); + + if (total > maxSizePerSession.bytes()) + { + if (!Files.remove(candidate)) + { + log.warn("cannot remove page data for session {} page {}", identifier, + candidate.getName()); + } + } + } + } + + protected SessionAttribute getSessionAttribute(IPageContext context, boolean create) + { + context.bind(); + + SessionAttribute attribute = context.getSessionAttribute(KEY); + if (attribute == null && create) + { + attribute = new SessionAttribute(applicationName, context.getSessionId()); + context.setSessionAttribute(KEY, attribute); + } + return attribute; + } + + /** + * Attribute held in session. + */ + static class SessionAttribute implements Serializable, HttpSessionBindingListener + { + + private final String applicationName; + + /** + * The identifier of the session, must not be equal to {@link Session#getId()}, e.g. when + * the container changes the id after authorization. + */ + public final String identifier; + + public SessionAttribute(String applicationName, String sessionIdentifier) + { + this.applicationName = Args.notNull(applicationName, "applicationName"); + this.identifier = Args.notNull(sessionIdentifier, "sessionIdentifier"); + } + + + @Override + public void valueBound(HttpSessionBindingEvent event) + { + } + + @Override + public void valueUnbound(HttpSessionBindingEvent event) + { + FilePageStore store = FILE_STORES.get(applicationName); + if (store == null) + { + log.warn( + "Cannot remove data '{}' because disk store for application '{}' is no longer present.", + identifier, applicationName); + } + else + { + store.removeFolder(identifier); + } + } + } + + public class LastModifiedComparator implements Comparator<File> + { + + @Override + public int compare(File f1, File f2) + { + return (int)(f2.lastModified() - f1.lastModified()); + } + + } + + @Override + public String getContextIdentifier(IPageContext context) + { + return getSessionAttribute(context, true).identifier; + } + + @Override + public Set<String> getContextIdentifiers() + { + Set<String> identifiers = new HashSet<>(); + + for (File folder : folders.getAll()) + { + identifiers.add(folder.getName()); + } + + return identifiers; + } + + @Override + public List<IPersistedPage> getPersistentPages(String identifier) + { + List<IPersistedPage> pages = new ArrayList<>(); + + File folder = folders.get(identifier, false); + if (folder.exists()) + { + File[] files = folder.listFiles(); + Arrays.sort(files, new LastModifiedComparator()); + for (File file : files) + { + String name = file.getName(); + if (name.endsWith(FILE_SUFFIX)) + { + int pageId; + try + { + pageId = Integer + .valueOf(name.substring(0, name.length() - FILE_SUFFIX.length())); + } + catch (Exception ex) + { + log.debug("unexpected file {}", file.getAbsolutePath()); + continue; + } + + String pageType = getPageType(file); + + pages.add(new PersistedPage(pageId, pageType, file.length())); + } + } + } + + return pages; + } + + /** + * Get the type of page from the given file. + * <p> + * This is an optional operation that returns <code>null</code> in case of any error. + * + * @param file + * @return pageType + */ + protected String getPageType(File file) + { + String pageType = null; + try + { + UserDefinedFileAttributeView view = java.nio.file.Files + .getFileAttributeView(file.toPath(), UserDefinedFileAttributeView.class); + ByteBuffer buffer = ByteBuffer.allocate(view.size(ATTRIBUTE_PAGE_TYPE)); + view.read(ATTRIBUTE_PAGE_TYPE, buffer); + buffer.flip(); + pageType = Charset.defaultCharset().decode(buffer).toString(); + } + catch (IOException ex) + { + log.debug("cannot get pageType for {}", file); + } + + return pageType; + } + + /** + * Set the type of page on the given file. + * <p> + * This is an optional operation that silently fails in case of an error. + * + * @param file + * @param pageType + */ + protected void setPageType(File file, String pageType) + { + try + { + UserDefinedFileAttributeView view = java.nio.file.Files + .getFileAttributeView(file.toPath(), UserDefinedFileAttributeView.class); + view.write(ATTRIBUTE_PAGE_TYPE, Charset.defaultCharset().encode(pageType)); + } + catch (IOException ex) + { + log.debug("cannot set pageType for {}", file, ex); + } + } + + @Override + public Bytes getTotalSize() + { + long total = 0; + + for (File folder : folders.getAll()) + { + for (File file : folder.listFiles()) + { + String name = file.getName(); + if (name.endsWith(FILE_SUFFIX)) + { + total += file.length(); + } + } + } + + return Bytes.bytes(total); + } + + private static class PersistedPage implements IPersistedPage + { + private int pageId; + + private String pageType; + + private long pageSize; + + public PersistedPage(int pageId, String pageType, long pageSize) + { + this.pageId = pageId; + this.pageType = pageType; + this.pageSize = pageSize; + } + + @Override + public int getPageId() + { + return pageId; + } + + @Override + public Bytes getPageSize() + { + return Bytes.bytes(pageSize); + } + + @Override + public String getPageType() + { + return pageType; + } + + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java new file mode 100644 index 0000000..9343935 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.apache.wicket.MetaDataEntry; +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.Session; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.util.string.Strings; + +/** + * An {@link IPageStore} that groups pages. + * + * @see #getGroup(IManageablePage) + */ +public abstract class GroupingPageStore extends DelegatingPageStore +{ + + private static final String DEFAULT_GROUP = "default"; + + private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>() + { + private static final long serialVersionUID = 1L; + }; + + private int maxGroups; + + /** + * Is a group of a page stable. + */ + private boolean stableGroups = false; + + /** + * @param delegate + * store to delegate to + * @param maxGroups + * maximum groups to keep + */ + public GroupingPageStore(IPageStore delegate, int maxGroups) + { + super(delegate); + + this.maxGroups = maxGroups; + } + + /** + * Indicate that groups are stable, i.e. the group of a page never changes. + */ + public GroupingPageStore withStableGroups() + { + stableGroups = true; + + return this; + } + + /** + * Get the group of a page, default is <code>"default"</code> + * + * @return group of page, must not be empty + */ + protected String getGroup(IManageablePage page) + { + return DEFAULT_GROUP; + } + + private String getGroupInternal(IManageablePage page) + { + String group = getGroup(page); + + if (Strings.isEmpty(group)) + { + throw new WicketRuntimeException("group must not be empy"); + } + + return group; + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + SessionData sessionData = getSessionData(context); + + sessionData.addPage(context, page, getGroupInternal(page), maxGroups, stableGroups, getDelegate()); + } + + @Override + public void removePage(IPageContext context, IManageablePage page) + { + SessionData sessionData = getSessionData(context); + + sessionData.removePage(context, page, getDelegate()); + } + + @Override + public void removeAllPages(IPageContext context) + { + SessionData sessionData = getSessionData(context); + + sessionData.removeAllPages(context, getDelegate()); + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + SessionData sessionData = getSessionData(context); + + return sessionData.getPage(context, id, getDelegate()); + } + + private SessionData getSessionData(IPageContext context) + { + SessionData data = context.getSessionData(KEY); + if (data == null) + { + context.bind(); + + data = context.setSessionData(KEY, new SessionData()); + } + + return data; + } + + /** + * Data kept in the {@link Session}. + */ + static class SessionData implements Serializable + { + private LinkedList<String> groups = new LinkedList<>(); + + private Map<String, MetaDataEntry<?>[]> metaData = new HashMap<>(); + + public synchronized <T> void setMetaData(String group, MetaDataKey<T> key, T value) + { + metaData.put(group, key.set(metaData.get(group), value)); + } + + public synchronized <T> T getMetaData(String group, MetaDataKey<T> key) + { + return key.get(metaData.get(group)); + } + + public synchronized void addPage(IPageContext context, IManageablePage page, String group, int maxGroups, boolean stableGroups, IPageStore delegate) + { + if (stableGroups == false) + { + // group might have changed, so remove page first from all groups + for (String other : groups) + { + delegate.removePage(new GroupContext(context, this, other), page); + } + } + + // add as last + groups.remove(group); + groups.addLast(group); + + // delegate + delegate.addPage(new GroupContext(context, this, group), page); + + while (groups.size() > maxGroups) + { + String first = groups.removeFirst(); + + delegate.removeAllPages(new GroupContext(context, this, first)); + } + } + + public IManageablePage getPage(IPageContext context, int id, IPageStore delegate) + { + for (String group : groups) + { + IManageablePage page = delegate.getPage(new GroupContext(context, this, group), id); + if (page != null) + { + return page; + } + } + return null; + } + + public synchronized void removePage(IPageContext context, IManageablePage page, IPageStore delegate) + { + for (String group : groups) + { + delegate.removePage(new GroupContext(context, this, group), page); + } + } + + public synchronized void removeAllPages(IPageContext context, IPageStore delegate) + { + for (String group : groups) + { + delegate.removeAllPages(new GroupContext(context, this, group)); + } + } + } + + /** + * Context passed to the delegate store to group data and attributes. + */ + static class GroupContext implements IPageContext + { + + private final IPageContext context; + + private final SessionData sessionData; + + private final String group; + + public GroupContext(IPageContext context, SessionData sessionData, String group) + { + this.context = context; + this.sessionData = sessionData; + this.group = group; + } + + @Override + public String getSessionId() + { + return context.getSessionId() + "_" + group; + } + + @Override + public <T extends Serializable> T setSessionData(MetaDataKey<T> key, T value) + { + sessionData.setMetaData(group, key, value); + + return value; + } + + @Override + public <T extends Serializable> T getSessionData(MetaDataKey<T> key) + { + return sessionData.getMetaData(group, key); + } + + @Override + public <T extends Serializable> void setSessionAttribute(String key, T value) + { + context.setSessionAttribute(key + "_" + group, value); + } + + @Override + public <T extends Serializable> T getSessionAttribute(String key) + { + return context.getSessionAttribute(key + "_" + group); + } + + @Override + public <T> void setRequestData(MetaDataKey<T> key, T data) + { + throw new WicketRuntimeException("no request available for group"); + } + + @Override + public <T> T getRequestData(MetaDataKey<T> key) + { + throw new WicketRuntimeException("no request available for group"); + } + + + @Override + public void bind() + { + context.bind(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java deleted file mode 100644 index b59176a..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.wicket.pageStore; - -/** - * Data stores are used to persist (read & write) Wicket page data to a persistent store like e.g. - * files or databases. - * - * @see IPageStore - */ -public interface IDataStore -{ - /** - * Get data associated with the session id and page id. - * - * @param sessionId - * Session ID - * @param id - * Page ID - * @return All the page data persisted - */ - byte[] getData(String sessionId, int id); - - /** - * Remove all persisted data related to the session id and page id - * - * @param sessionId - * Session ID - * @param id - * Page ID - */ - void removeData(String sessionId, int id); - - /** - * Remove all page data for the session id - * - * @param sessionId - * Session ID - */ - void removeData(String sessionId); - - /** - * Store the page data - * - * @param sessionId - * Session ID - * @param id - * Page ID - * @param data - * Page data - */ - void storeData(String sessionId, int id, byte[] data); - - /** - * Properly close the data store and possibly open resource handles - */ - void destroy(); - - /** - * - * @return whether the data store is replicated - */ - boolean isReplicated(); - - /** - * @return whether the implementation can be wrapped in {@link AsynchronousDataStore} - */ - boolean canBeAsynchronous(); -} http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java new file mode 100644 index 0000000..7dff1a5 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.Serializable; + +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.page.IManageablePage; + +/** + * Context of a {@link IManageablePage} when it is store in a {@link IPageStore}, decoupling it from + * request cycle and session. + * + * @author Matej Knopp + * @author svenmeier + */ +public interface IPageContext +{ + /** + * Set data into the current request. + * + * @param key + * key + * @param value + * value + */ + <T> void setRequestData(MetaDataKey<T> key, T value); + + /** + * Get data from the current request. + * + * @param key + * key + * @return value + */ + <T> T getRequestData(MetaDataKey<T> key); + + /** + * Set an attribute in the session. + * + * @param key + * key + * @param value + * value + */ + <T extends Serializable> void setSessionAttribute(String key, T value); + + /** + * Get an attribute from the session. + * + * @param key + * key + * @param value + * value + */ + <T extends Serializable> T getSessionAttribute(String key); + + /** + * Set data into the session - only if it is not set already. + * <p> + * Recommended usage: + * <pre> + * <code> + * SessionData data = context.getSessionData(KEY); + * if (data == null) + * { + * data = context.setSessionData(KEY, new SessionData()); + * } + * </code> + * </pre> + * + * @param key + * key + * @param value + * value + * @return the old value if already present, or the new value + */ + <T extends Serializable> T setSessionData(MetaDataKey<T> key, T value); + + /** + * Get data from the session. + * + * @param key + * key + * @return value + */ + <T extends Serializable> T getSessionData(MetaDataKey<T> key); + + /** + * Bind the current session. This make a temporary session become persistent across requests. + */ + void bind(); + + /** + * Get the identifier of the session. + * + * @return session id + */ + String getSessionId(); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java index ed1b9d3..bf2c798 100644 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java @@ -16,96 +16,88 @@ */ package org.apache.wicket.pageStore; -import java.io.Serializable; - import org.apache.wicket.page.IManageablePage; /** - * {@link IPageStore} role is to mediate the storing and loading of pages done by {@link IDataStore} - * s. {@link IPageStore} may pre-process the pages before passing them to - * {@link IDataStore#storeData(String, int, byte[])} and to post-process them after - * {@link IDataStore#getData(String, int)}. - * - * @see IDataStore + * A store of pages */ public interface IPageStore { /** - * Destroy the store. + * Is versioning of pages supported, i.e. when an altered page instance has been stored in this store, can the previous state of that page + * still be retrieved under its former page id. Most implementations achieve this by keeping a copy of the page instance, e.g. by means of serialization. */ - void destroy(); + boolean supportsVersioning(); /** - * Restores a page from the persistent layer. + * This method is called by {@link AsynchronousPageStore} before any attempt to call + * {@link #addPage(IPageContext, IManageablePage)} asynchronously. + * <p> + * A page store returning <code>true</code> must immediately access all required values from the context, + * since no additional values can be accessed when {@link #addPage(IPageContext, IManageablePage)} is called + * asynchronously afterwards. * - * @param sessionId - * The session of the page that must be removed - * @param pageId - * The id of the page. - * @return The page + * @return whether {@link #addPage(IPageContext, IManageablePage)} may be called asynchronously, + * default is <code>false</code> */ - IManageablePage getPage(String sessionId, int pageId); - + default boolean canBeAsynchronous(IPageContext context) + { + return false; + } + /** - * Removes a page from the persistent layer. + * Stores the page- * - * @param sessionId - * The session of the page that must be removed - * @param pageId - * The id of the page. + * @param context + * the context of the page + * @param id + * the id of the page. */ - void removePage(String sessionId, int pageId); + void addPage(IPageContext context, IManageablePage page); /** - * Stores the page to a persistent layer. The page should be stored under the id and the version - * number. + * Removes a page from storage. * - * @param sessionId - * The session of the page that must be removed - * @param page - * The page to store + * @param context + * the context of the page + * @param id + * the id of the page. */ - void storePage(String sessionId, IManageablePage page); + void removePage(IPageContext context, IManageablePage page); /** - * The page store should cleanup all the pages for that sessionid. + * All pages should be removed from storage for the given context. * - * @param sessionId - * The session of the page that must be removed + * @param context + * the context of the pages */ - void unbind(String sessionId); + void removeAllPages(IPageContext context); /** - * Process the page before the it gets serialized. The page can be either real page instance or - * object returned by {@link #restoreAfterSerialization(Serializable)}. + * Restores a page from storage. * - * @param sessionId - * The session of the page that must be removed - * @param page - * @return The Page itself or a SerializedContainer for that page + * @param context + * the context of the page + * @param id + * the id of the page. + * @return the page */ - Serializable prepareForSerialization(String sessionId, Serializable page); + IManageablePage getPage(IPageContext context, int id); /** - * This method should restore the serialized page to intermediate object that can be converted - * to real page instance using {@link #convertToPage(Object)}. + * Detach from the current context. * - * @param serializable - * @return Page + * @param context + * the context of the pages */ - Object restoreAfterSerialization(Serializable serializable); + default void detach(IPageContext context) + { + } /** - * Converts a page representation to an instance of {@link IManageablePage} - * - * @param page - * some kind of page representation - * @return page - */ - IManageablePage convertToPage(Object page); - - /** - * @return whether the implementation can be wrapped in {@link AsynchronousPageStore} + * Destroy the store. */ - boolean canBeAsynchronous(); + default void destroy() + { + } } http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java new file mode 100644 index 0000000..8a03507 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.Serializable; + +import org.apache.wicket.util.lang.Bytes; + +/** + * Information about a persisted page in an {@link IPersistentPageStore}. + * + * @see IPersistentPageStore#getPersistentPages(String, int) + */ +public interface IPersistedPage extends Serializable +{ + /** + * Id of page. + */ + int getPageId(); + + /** + * Size of page. + */ + Bytes getPageSize(); + + /** + * Type of page. + */ + String getPageType(); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java new file mode 100644 index 0000000..ab0ed24 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.util.List; +import java.util.Set; + +import org.apache.wicket.util.lang.Bytes; + +/** + * A store that can provide information about stored pages. + */ +public interface IPersistentPageStore extends IPageStore +{ + + /** + * Get the identifier for pages stored for the given context. + */ + String getContextIdentifier(IPageContext context); + + /** + * Get the identifiers for all pages stored in all contexts. + */ + Set<String> getContextIdentifiers(); + + /** + * Get information about all persisted pages with the given context identifier. + */ + List<IPersistedPage> getPersistentPages(String contextIdentifier); + + /** + * Get total size of all pages stored in all contexts. + * + * @return + */ + Bytes getTotalSize(); +} http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java new file mode 100644 index 0000000..d9d025a --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.apache.wicket.Application; +import org.apache.wicket.Session; +import org.apache.wicket.core.util.lang.WicketObjects; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.util.lang.Args; +import org.apache.wicket.util.lang.Bytes; + +/** + * A storage of pages in memory. + */ +public class InMemoryPageStore implements IPersistentPageStore +{ + + /** + * A registry of all page instances. + */ + private static final ConcurrentMap<String, InMemoryPageStore> IN_MEMORY_STORES = new ConcurrentHashMap<>(); + + private static final String KEY = "wicket:InMemoryPageStore"; + + private final Map<String, MemoryData> datas = new ConcurrentHashMap<>(); + + private String applicationName; + + private int maxPages; + + /** + * @param applicationName + * {@link Application#getName()} + * @param maxPages + * max pages per session + */ + public InMemoryPageStore(String applicationName, int maxPages) + { + this.applicationName = Args.notNull(applicationName, "applicationName"); + this.maxPages = maxPages; + + IN_MEMORY_STORES.put(applicationName, this); + } + + /** + * Versioning is not supported. + */ + @Override + public boolean supportsVersioning() + { + return false; + } + + /** + * + * + * @return <code>true</code> always + */ + @Override + public boolean canBeAsynchronous(IPageContext context) + { + // session attribute must be added here *before* any asynchronous calls + // when session is no longer available + getSessionAttribute(context, true); + + return true; + } + + protected SessionAttribute getSessionAttribute(IPageContext context, boolean create) + { + SessionAttribute attribute = context.getSessionAttribute(KEY); + if (attribute == null && create) + { + context.bind(); + + attribute = new SessionAttribute(applicationName, context.getSessionId()); + context.setSessionAttribute(KEY, attribute); + } + return attribute; + } + + @Override + public void destroy() + { + datas.clear(); + + IN_MEMORY_STORES.remove(applicationName); + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + MemoryData data = getMemoryData(context, false); + if (data == null) + { + return null; + } + + return data.get(id); + } + + @Override + public void removePage(IPageContext context, final IManageablePage page) + { + MemoryData data = getMemoryData(context, false); + + if (data != null) + { + synchronized (data) + { + data.remove(page); + } + } + } + + @Override + public void removeAllPages(IPageContext context) + { + MemoryData data = getMemoryData(context, false); + + if (data != null) + { + synchronized (data) + { + data.removeAll(); + } + } + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + MemoryData data = getMemoryData(context, true); + + data.add(page, maxPages); + } + + @Override + public String getContextIdentifier(IPageContext context) + { + return context.getSessionId(); + } + + @Override + public Set<String> getContextIdentifiers() + { + return datas.keySet(); + } + + @Override + public List<IPersistedPage> getPersistentPages(String sessionIdentifier) + { + MemoryData data = datas.get(sessionIdentifier); + if (data == null) + { + return new ArrayList<>(); + } + + synchronized (data) + { + return StreamSupport.stream(data.spliterator(), false) + .map(page -> new PersistedPage(page, getSize(page))) + .collect(Collectors.toList()); + } + } + + @Override + public Bytes getTotalSize() + { + int size = 0; + + for (MemoryData data : datas.values()) + { + synchronized (data) + { + for (IManageablePage page : data) + { + size += getSize(page); + } + } + } + + return Bytes.bytes(size); + } + + /** + * Get the size of the given page. + */ + protected long getSize(IManageablePage page) + { + if (page instanceof SerializedPage) { + return ((SerializedPage)page).getData().length; + } else { + return WicketObjects.sizeof(page); + } + } + + private MemoryData getMemoryData(IPageContext context, boolean create) + { + SessionAttribute attribute = getSessionAttribute(context, create); + + if (!create) + { + if (attribute == null) { + return null; + } else { + return datas.get(attribute.identifier); + } + } + + MemoryData data = new MemoryData(); + MemoryData existing = datas.putIfAbsent(attribute.identifier, data); + return existing != null ? existing : data; + } + + private void removeMemoryData(String identifier) + { + datas.remove(identifier); + } + + /** + * Data kept in memory. + */ + static class MemoryData implements Iterable<IManageablePage> + { + private LinkedHashMap<Integer, IManageablePage> pages = new LinkedHashMap<>(); + + @Override + public Iterator<IManageablePage> iterator() + { + return pages.values().iterator(); + } + + public synchronized void add(IManageablePage page, int maxPages) + { + pages.remove(page.getPageId()); + pages.put(page.getPageId(), page); + + Iterator<IManageablePage> iterator = pages.values().iterator(); + int size = pages.size(); + while (size > maxPages) + { + iterator.next(); + + iterator.remove(); + size--; + } + } + + public void remove(IManageablePage page) + { + pages.remove(page.getPageId()); + } + + public void removeAll() + { + pages.clear(); + } + + public IManageablePage get(int id) + { + IManageablePage page = pages.get(id); + + return page; + } + } + + /** + * Attribute held in session. + */ + static class SessionAttribute implements Serializable, HttpSessionBindingListener + { + + private final String applicationName; + + /** + * The identifier of the session, must not be equal to {@link Session#getId()}, e.g. when + * the container changes the id after authorization. + */ + public final String identifier; + + public SessionAttribute(String applicationName, String sessionIdentifier) + { + this.applicationName = Args.notNull(applicationName, "applicationName"); + this.identifier = Args.notNull(sessionIdentifier, "sessionIdentifier"); + } + + + @Override + public void valueBound(HttpSessionBindingEvent event) + { + } + + @Override + public void valueUnbound(HttpSessionBindingEvent event) + { + InMemoryPageStore store = IN_MEMORY_STORES.get(applicationName); + if (store != null) + { + store.removeMemoryData(identifier); + } + } + } + + private static class PersistedPage implements IPersistedPage + { + + private final int id; + + private final String type; + + private final long size; + + public PersistedPage(IManageablePage page, long size) + { + this.id = page.getPageId(); + this.type = page instanceof SerializedPage ? ((SerializedPage)page).getPageType() : page.getClass().getName(); + this.size = size; + } + + @Override + public int getPageId() + { + return id; + } + + @Override + public String getPageType() + { + return type; + } + + @Override + public Bytes getPageSize() + { + return Bytes.bytes(size); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java new file mode 100644 index 0000000..86b68ce --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import org.apache.wicket.DefaultPageManagerProvider; +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.Session; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.serialize.ISerializer; +import org.apache.wicket.util.lang.Args; + +/** + * A store keeping a configurable maximum of pages in the session. + * <p> + * This store is used by {@link DefaultPageManagerProvider} as a cache in front + * of a persistent store. + */ +public class InSessionPageStore extends DelegatingPageStore +{ + + private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>() + { + private static final long serialVersionUID = 1L; + }; + + private final ISerializer serializer; + + private int maxPages; + + /** + * Use this constructor, if sessions are never persisted by the container. + * + * @param delegate + * store to delegate to + * @param maxPages + * maximum pages to keep in session + */ + public InSessionPageStore(IPageStore delegate, int maxPages) + { + this(delegate, maxPages, new ISerializer() + { + @Override + public byte[] serialize(Object object) + { + throw new WicketRuntimeException("InSessionPageStore not configured for serialization"); + } + + @Override + public Object deserialize(byte[] data) + { + throw new WicketRuntimeException("InSessionPageStore not configured for serialization"); + } + }); + } + + /** + * @param delegate + * store to delegate to + * @param maxPages + * maximum pages to keep in session + * @param serializer + * for serialization of pages if session gets persisted + */ + public InSessionPageStore(IPageStore delegate, int maxPages, ISerializer serializer) + { + super(delegate); + + this.serializer = Args.notNull(serializer, "serializer"); + + this.maxPages = maxPages; + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + SessionData data = getSessionData(context, false); + if (data != null) { + IManageablePage page = data.get(id); + if (page != null) + { + return page; + } + } + + return super.getPage(context, id); + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + SessionData data = getSessionData(context, true); + + data.add(context, page, maxPages); + + super.addPage(context, page); + } + + @Override + public void removePage(IPageContext context, IManageablePage page) + { + SessionData data = getSessionData(context, false); + if (data != null) { + data.remove(page); + } + + super.removePage(context, page); + } + + @Override + public void removeAllPages(IPageContext context) + { + SessionData data = getSessionData(context, false); + if (data != null) { + data.removeAll(); + } + + super.removeAllPages(context); + } + + private SessionData getSessionData(IPageContext context, boolean create) + { + SessionData data = context.getSessionData(KEY); + if (data == null && create) + { + context.bind(); + + data = context.setSessionData(KEY, new SessionData()); + } + + if (data != null) { + // data might be deserialized so initialize again + data.init(serializer); + } + + return data; + } + + /** + * Data kept in the {@link Session}, might get serialized along with its containing + * {@link HttpSession}. + */ + static class SessionData implements Serializable + { + + transient ISerializer serializer; + + /** + * Pages, may partly be serialized. + * <p> + * Kept in list instead of map, since life pages might change their id during a request. + */ + private List<IManageablePage> pages = new LinkedList<>(); + + /** + * This method <em>must</em> be called each time it is retrieved from the session: <br/> + * After deserializing from persisted session the serializer is no longer referenced and all + * contained pages are in a serialized state. + */ + public void init(ISerializer serializer) + { + this.serializer = Args.notNull(serializer, "serializer"); + } + + public synchronized void add(IPageContext context, IManageablePage page, int maxPages) + { + // move to end + remove(page); + pages.add(page); + + while (pages.size() > maxPages) + { + pages.remove(0); + } + } + + public synchronized void remove(IManageablePage page) + { + Iterator<IManageablePage> iterator = pages.iterator(); + while (iterator.hasNext()) { + if (iterator.next().getPageId() == page.getPageId()) { + iterator.remove(); + break; + } + } + } + + public synchronized void removeAll() + { + pages.clear(); + } + + public synchronized IManageablePage get(int id) + { + IManageablePage page = null; + + for (int p = 0; p < pages.size(); p++) + { + IManageablePage candidate = pages.get(p); + + if (candidate.getPageId() == id) { + if (candidate instanceof SerializedPage) + { + if (serializer == null) + { + throw new IllegalStateException("SessionData#init() was not called"); + } + candidate = (IManageablePage)serializer.deserialize(((SerializedPage)candidate).getData()); + + pages.set(p, candidate); + } + + page = candidate; + break; + } + } + + return page; + } + + /** + * Serialize pages before writing to output. + */ + private void writeObject(final ObjectOutputStream output) throws IOException + { + // serialize pages if not already + for (int p = 0; p < pages.size(); p++) + { + IManageablePage page = pages.get(p); + + if ((page instanceof SerializedPage) == false) + { + if (serializer == null) + { + throw new IllegalStateException("SessionData#init() was not called"); + } + pages.set(p, new SerializedPage(page.getPageId(), page.getClass().getName(), serializer.serialize(page))); + } + } + + output.defaultWriteObject(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java new file mode 100644 index 0000000..d31313f --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import org.apache.wicket.page.IManageablePage; + +/** + * A non-storage of pages. + */ +public class NoopPageStore implements IPageStore +{ + @Override + public boolean supportsVersioning() + { + return false; + } + + @Override + public boolean canBeAsynchronous(IPageContext context) + { + return true; + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + } + + @Override + public void removePage(IPageContext context, IManageablePage page) + { + } + + @Override + public void removeAllPages(IPageContext context) + { + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + return null; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java deleted file mode 100644 index f194710..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java +++ /dev/null @@ -1,506 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.wicket.pageStore; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import org.apache.wicket.util.collections.IntHashMap; - -/** - * Manages positions and size of serialized pages in the pagemap file. - * <p> - * The pages are stored inside the file in a cyclic way. Newer pages are placed after older ones, - * until the maximum file size is reached. After that, the next page is stored in the beginning of - * the file. - * - * @author Matej Knopp - */ -public class PageWindowManager implements Serializable -{ - private static final long serialVersionUID = 1L; - - /** - * Contains information about a page inside the file. - * - * @author Matej Knopp - */ - private static class PageWindowInternal implements Serializable - { - private static final long serialVersionUID = 1L; - - /** id of page or -1 if the window is empty */ - private int pageId; - - /** offset in the file where the serialized page data begins */ - private int filePartOffset; - - /** size of serialized page data */ - private int filePartSize; - } - - /** list of PageWindowInternal objects */ - private final List<PageWindowInternal> windows = new ArrayList<PageWindowInternal>(); - - /** - * map from page id to list of pagewindow indices (referring to the windows list) - to improve - * searching speed the index must be cleaned when the instances in the windows list change their - * indexes (e.g. items are shifted on page window removal) - */ - private IntHashMap<Integer> idToWindowIndex = null; - - /** - * Inversed index of #idToWindowIndex - */ - private IntHashMap<Integer> windowIndexToPageId = null; - - /** index of last added page */ - private int indexPointer = -1; - - private int totalSize = 0; - - /** - * Maximum page size. After this size is exceeded, the pages will be saved starting at the - * beginning of file. - */ - private final long maxSize; - - /** - * - * @param pageId - * @param windowIndex - */ - private void putWindowIndex(int pageId, int windowIndex) - { - if (idToWindowIndex != null && pageId != -1 && windowIndex != -1) - { - Integer oldPageId = windowIndexToPageId.remove(windowIndex); - if (oldPageId != null) - { - idToWindowIndex.remove(oldPageId); - } - idToWindowIndex.put(pageId, windowIndex); - windowIndexToPageId.put(windowIndex, pageId); - } - } - - /** - * - * @param pageId - */ - private void removeWindowIndex(int pageId) - { - Integer windowIndex = idToWindowIndex.remove(pageId); - if (windowIndex != null) - { - windowIndexToPageId.remove(windowIndex); - } - } - - /** - * - */ - private void rebuildIndices() - { - idToWindowIndex = null; - idToWindowIndex = new IntHashMap<Integer>(); - windowIndexToPageId = null; - windowIndexToPageId = new IntHashMap<Integer>(); - for (int i = 0; i < windows.size(); ++i) - { - PageWindowInternal window = windows.get(i); - putWindowIndex(window.pageId, i); - } - } - - /** - * Returns the index of the given page in the {@link #windows} list. - * - * @param pageId - * @return window index - */ - private int getWindowIndex(int pageId) - { - if (idToWindowIndex == null) - { - rebuildIndices(); - } - - Integer result = idToWindowIndex.get(pageId); - return result != null ? result : -1; - } - - /** - * Increments the {@link #indexPointer}. If the maximum file size has been reached, the - * {@link #indexPointer} is set to 0. - * - * @return new index pointer - */ - private int incrementIndexPointer() - { - if ((maxSize > 0) && (totalSize >= maxSize) && (indexPointer == windows.size() - 1)) - { - indexPointer = 0; - } - else - { - ++indexPointer; - } - return indexPointer; - } - - /** - * Returns the offset in file of the window on given index. The offset is counted by getting the - * previous page offset and adding the previous page size to it. - * - * @param index - * @return window file offset - */ - private int getWindowFileOffset(int index) - { - if (index > 0) - { - PageWindowInternal window = windows.get(index - 1); - return window.filePartOffset + window.filePartSize; - } - return 0; - } - - /** - * Splits the window with given index to two windows. First of those will have size specified by - * the argument, the other one will fill up the rest of the original window. - * - * @param index - * @param size - */ - private void splitWindow(int index, int size) - { - PageWindowInternal window = windows.get(index); - int delta = window.filePartSize - size; - - if (index == windows.size() - 1) - { - // if this is last window - totalSize -= delta; - window.filePartSize = size; - } - else if (window.filePartSize != size) - { - PageWindowInternal newWindow = new PageWindowInternal(); - newWindow.pageId = -1; - window.filePartSize = size; - - windows.add(index + 1, newWindow); - - newWindow.filePartOffset = getWindowFileOffset(index + 1); - newWindow.filePartSize = delta; - } - - idToWindowIndex = null; - windowIndexToPageId = null; - } - - /** - * Merges the window with given index with the next window. The resulting window will have size - * of the two windows summed together. - * - * @param index - */ - private void mergeWindowWithNext(int index) - { - if (index < windows.size() - 1) - { - PageWindowInternal window = windows.get(index); - PageWindowInternal next = windows.get(index + 1); - window.filePartSize += next.filePartSize; - - windows.remove(index + 1); - idToWindowIndex = null; // reset index - windowIndexToPageId = null; - } - } - - /** - * Adjusts the window on given index to the specified size. If the new size is smaller than the - * window size, the window will be split. Otherwise the window will be merged with as many - * subsequent window as necessary. In case the window is last window in the file, the size will - * be adjusted without splitting or merging. - * - * @param index - * @param size - */ - private void adjustWindowSize(int index, int size) - { - PageWindowInternal window = windows.get(index); - - // last window, just adjust size - if (index == windows.size() - 1) - { - int delta = size - window.filePartSize; - totalSize += delta; - window.filePartSize = size; - } - else - { - // merge as many times as necessary - while (window.filePartSize < size && index < windows.size() - 1) - { - mergeWindowWithNext(index); - } - - // done merging - do we have enough room ? - if (window.filePartSize < size) - { - // no, this is the last window - int delta = size - window.filePartSize; - totalSize += delta; - window.filePartSize = size; - } - else - { - // yes, we might want to split the window, so that we don't lose - // space when the created window was too big - splitWindow(index, size); - } - } - - window.pageId = -1; - } - - /** - * Allocates window on given index with to size. If the index is pointing to existing window, - * the window size will be adjusted. Otherwise a new window with appropriated size will be - * created. - * - * @param index - * @param size - * @return page window - */ - private PageWindowInternal allocatePageWindow(int index, int size) - { - final PageWindowInternal window; - - // new window - if (index == windows.size()) - { - // new page window - window = new PageWindowInternal(); - window.filePartOffset = getWindowFileOffset(index); - totalSize += size; - window.filePartSize = size; - windows.add(window); - } - else - { - // get the window - window = windows.get(index); - - // adjust if necessary - if (window.filePartSize != size) - { - adjustWindowSize(index, size); - } - } - - return window; - } - - /** - * Public (read only) version of page window. - * - * @author Matej Knopp - */ - public static class PageWindow - { - private final PageWindowInternal pageWindowInternal; - - /** - * Construct. - * - * @param pageWindowInternal - */ - private PageWindow(PageWindowInternal pageWindowInternal) - { - this.pageWindowInternal = pageWindowInternal; - } - - /** - * @return page Id - */ - public int getPageId() - { - return pageWindowInternal.pageId; - } - - /** - * @return offset in the pagemap file where the serialized page data starts - */ - public int getFilePartOffset() - { - return pageWindowInternal.filePartOffset; - } - - /** - * @return size of the serialized page data - */ - public int getFilePartSize() - { - return pageWindowInternal.filePartSize; - } - } - - /** - * Creates and returns a new page window for given page. - * - * @param pageId - * @param size - * @return page window - */ - public synchronized PageWindow createPageWindow(int pageId, int size) - { - int index = getWindowIndex(pageId); - - // if we found the page window, mark it as invalid - if (index != -1) - { - removeWindowIndex(pageId); - (windows.get(index)).pageId = -1; - } - - // if we are not going to reuse a page window (because it's not on - // indexPointer position or because we didn't find it), increment the - // indexPointer - if (index == -1 || index != indexPointer) - { - index = incrementIndexPointer(); - } - - PageWindowInternal window = allocatePageWindow(index, size); - window.pageId = pageId; - - putWindowIndex(pageId, index); - return new PageWindow(window); - } - - /** - * Returns the page window for given page or null if no window was found. - * - * @param pageId - * @return page window or null - */ - public synchronized PageWindow getPageWindow(int pageId) - { - int index = getWindowIndex(pageId); - if (index != -1) - { - return new PageWindow(windows.get(index)); - } - return null; - } - - /** - * Removes the page window for given page. - * - * @param pageId - */ - public synchronized void removePage(int pageId) - { - int index = getWindowIndex(pageId); - if (index != -1) - { - PageWindowInternal window = windows.get(index); - removeWindowIndex(pageId); - if (index == windows.size() - 1) - { - windows.remove(index); - totalSize -= window.filePartSize; - if (indexPointer == index) - { - --indexPointer; - } - } - else - { - window.pageId = -1; - } - } - } - - /** - * Returns last n saved page windows. - * - * @param count - * @return list of page windows - */ - public synchronized List<PageWindow> getLastPageWindows(int count) - { - List<PageWindow> result = new ArrayList<PageWindow>(); - - // start from current index to 0 - int currentIndex = indexPointer; - - do - { - if (currentIndex == -1) - { - break; - } - - if (currentIndex < windows.size()) - { - PageWindowInternal window = windows.get(currentIndex); - if (window.pageId != -1) - { - result.add(new PageWindow(window)); - } - } - - --currentIndex; - if (currentIndex == -1) - { - // rewind to the last entry and collect all entries until current index - currentIndex = windows.size() - 1; - } - } - while (result.size() < count && currentIndex != indexPointer); - - return result; - } - - /** - * Creates a new PageWindowManager. - * - * @param maxSize - * maximum page size. After this size is exceeded, the pages will be saved starting - * at the beginning of file - */ - public PageWindowManager(long maxSize) - { - this.maxSize = maxSize; - } - - /** - * Returns the size of all saved pages - * - * @return total size - */ - public synchronized int getTotalSize() - { - return totalSize; - } -} http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java deleted file mode 100644 index 4a21f4c..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.wicket.pageStore; - -import java.lang.ref.SoftReference; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentSkipListMap; - -import org.apache.wicket.page.IManageablePage; -import org.apache.wicket.serialize.ISerializer; -import org.apache.wicket.util.lang.Args; - -/** - * A page store that uses a SecondLevelPageCache with the last N used page instances - * per session. - * - * <strong>Note</strong>: the size of the cache depends on the {@code cacheSize} constructor - * parameter multiplied by the number of the active http sessions. - * - * It depends on the application use cases but usually a reasonable value of - * {@code cacheSize} would be just a few pages (2-3). If the application don't expect many - * active http sessions and the work flow involves usage of the browser/application history - * then the {@code cacheSize} value may be increased to a bigger value. - */ -public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePage> -{ - /** - * Constructor. - * - * @param pageSerializer - * the {@link org.apache.wicket.serialize.ISerializer} that will be used to convert pages from/to byte arrays - * @param dataStore - * the {@link org.apache.wicket.pageStore.IDataStore} that actually stores the pages - * @param cacheSize - * the number of pages to cache in memory before passing them to - * {@link org.apache.wicket.pageStore.IDataStore#storeData(String, int, byte[])} - */ - public PerSessionPageStore(final ISerializer pageSerializer, final IDataStore dataStore, - final int cacheSize) - { - super(pageSerializer, dataStore, new PagesCache(cacheSize)); - } - - @Override - public IManageablePage convertToPage(final Object object) - { - if (object == null) - { - return null; - } - else if (object instanceof IManageablePage) - { - return (IManageablePage)object; - } - - String type = object.getClass().getName(); - throw new IllegalArgumentException("Unknown object type: " + type); - } - - /** - * An implementation of SecondLevelPageCache that stores the last used N live page instances - * per http session. - */ - protected static class PagesCache implements SecondLevelPageCache<String, Integer, IManageablePage> - { - /** - * Helper class used to compare the page entries in the cache by their - * access time - */ - private static class PageValue - { - /** - * The id of the cached page - */ - private final int pageId; - - /** - * The last time this page has been used/accessed. - */ - private long accessTime; - - private PageValue(IManageablePage page) - { - this(page.getPageId()); - } - - private PageValue(int pageId) - { - this.pageId = pageId; - touch(); - } - - /** - * Updates the access time with the current time - */ - private void touch() - { - accessTime = System.nanoTime(); - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - PageValue pageValue = (PageValue) o; - - return pageId == pageValue.pageId; - } - - @Override - public int hashCode() - { - return pageId; - } - } - - private static class PageComparator implements Comparator<PageValue> - { - @Override - public int compare(PageValue p1, PageValue p2) - { - return Long.compare(p1.accessTime, p2.accessTime); - } - } - - private final int maxEntriesPerSession; - - private final ConcurrentMap<String, SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>>> cache; - - /** - * Constructor. - * - * @param maxEntriesPerSession - * The number of cache entries per session - */ - public PagesCache(final int maxEntriesPerSession) - { - this.maxEntriesPerSession = maxEntriesPerSession; - cache = new ConcurrentHashMap<>(); - } - - /** - * - * @param sessionId - * The id of the http session - * @param pageId - * The id of the page to remove from the cache - * @return the removed {@link org.apache.wicket.page.IManageablePage} or <code>null</code> - otherwise - */ - @Override - public IManageablePage removePage(final String sessionId, final Integer pageId) - { - IManageablePage result = null; - - if (maxEntriesPerSession > 0) - { - Args.notNull(sessionId, "sessionId"); - Args.notNull(pageId, "pageId"); - - SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId); - if (pagesPerSession != null) - { - ConcurrentMap<PageValue, IManageablePage> pages = pagesPerSession.get(); - if (pages != null) - { - PageValue sample = new PageValue(pageId); - Iterator<Map.Entry<PageValue, IManageablePage>> iterator = pages.entrySet().iterator(); - while (iterator.hasNext()) - { - Map.Entry<PageValue, IManageablePage> entry = iterator.next(); - if (sample.equals(entry.getKey())) - { - result = entry.getValue(); - iterator.remove(); - break; - } - } - } - } - } - - return result; - } - - /** - * Removes all {@link org.apache.wicket.page.IManageablePage}s for the session - * with <code>sessionId</code> from the cache. - * - * @param sessionId - * The id of the expired http session - */ - @Override - public void removePages(String sessionId) - { - Args.notNull(sessionId, "sessionId"); - - if (maxEntriesPerSession > 0) - { - cache.remove(sessionId); - } - } - - /** - * Returns a {@link org.apache.wicket.page.IManageablePage} by looking it up by <code>sessionId</code> and - * <code>pageId</code>. If there is a match then it is <i>touched</i>, i.e. it is moved at - * the top of the cache. - * - * @param sessionId - * The id of the http session - * @param pageId - * The id of the page to find - * @return the found serialized page or <code>null</code> when not found - */ - @Override - public IManageablePage getPage(String sessionId, Integer pageId) - { - IManageablePage result = null; - - if (maxEntriesPerSession > 0) - { - Args.notNull(sessionId, "sessionId"); - Args.notNull(pageId, "pageId"); - - SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId); - if (pagesPerSession != null) - { - ConcurrentSkipListMap<PageValue, IManageablePage> pages = pagesPerSession.get(); - if (pages != null) - { - PageValue sample = new PageValue(pageId); - for (Map.Entry<PageValue, IManageablePage> entry : pages.entrySet()) - { - if (sample.equals(entry.getKey())) - { - // touch the entry - entry.getKey().touch(); - result = entry.getValue(); - break; - } - } - } - } - } - return result; - } - - /** - * Store the serialized page in cache - * - * @param page - * the data to serialize (page id, session id, bytes) - */ - @Override - public void storePage(String sessionId, Integer pageId, IManageablePage page) - { - if (maxEntriesPerSession > 0) - { - Args.notNull(sessionId, "sessionId"); - Args.notNull(pageId, "pageId"); - - SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId); - if (pagesPerSession == null) - { - ConcurrentSkipListMap<PageValue, IManageablePage> pages = new ConcurrentSkipListMap<>(new PageComparator()); - pagesPerSession = new SoftReference<>(pages); - SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> old = cache.putIfAbsent(sessionId, pagesPerSession); - if (old != null) - { - pagesPerSession = old; - } - } - - ConcurrentSkipListMap<PageValue, IManageablePage> pages = pagesPerSession.get(); - if (pages == null) - { - pages = new ConcurrentSkipListMap<>(); - pagesPerSession = new SoftReference<>(pages); - SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> old = cache.putIfAbsent(sessionId, pagesPerSession); - if (old != null) - { - pages = old.get(); - } - } - - if (pages != null) - { - removePage(sessionId, pageId); - - PageValue pv = new PageValue(page); - pages.put(pv, page); - - while (pages.size() > maxEntriesPerSession) - { - pages.pollFirstEntry(); - } - } - } - } - - @Override - public void destroy() - { - cache.clear(); - } - } - - @Override - public boolean canBeAsynchronous() - { - return false; // NOTE: not analyzed neither tested yet, this page store being wrapped by asynchronous one - } -} http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java new file mode 100644 index 0000000..89618bc --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import java.util.LinkedList; + +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.request.cycle.RequestCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Buffers storage of added pages until the end of the request, when they are delegated to the next store in + * the identical order they where added. + */ +public class RequestPageStore extends DelegatingPageStore +{ + + private static final Logger log = LoggerFactory.getLogger(RequestPageStore.class); + + private static final MetaDataKey<RequestData> KEY = new MetaDataKey<>() + { + private static final long serialVersionUID = 1L; + }; + + public RequestPageStore(IPageStore delegate) + { + super(delegate); + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + IManageablePage page = getRequestData(context).get(id); + if (page != null) + { + return page; + } + + return super.getPage(context, id); + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + getRequestData(context).add(page); + } + + @Override + public void removePage(IPageContext context, IManageablePage page) + { + getRequestData(context).remove(page); + + super.removePage(context, page); + } + + @Override + public void removeAllPages(IPageContext context) + { + getRequestData(context).removeAll(); + + super.removeAllPages(context); + } + + @Override + public void detach(IPageContext context) + { + RequestData requestData = getRequestData(context); + for (IManageablePage page : requestData.pages()) + { + boolean isPageStateless; + try + { + isPageStateless = page.isPageStateless(); + } + catch (Exception x) + { + log.warn("An error occurred while checking whether a page is stateless. Assuming it is stateful.", x); + isPageStateless = false; + } + + if (isPageStateless == false) + { + super.addPage(context, page); + } + } + requestData.removeAll(); + + super.detach(context); + } + + private RequestData getRequestData(IPageContext context) + { + RequestData requestData = context.getRequestData(KEY); + if (requestData == null) + { + requestData = new RequestData(); + + context.setRequestData(KEY, requestData); + } + return requestData; + } + + /** + * Data kept in the {@link RequestCycle}. + */ + static class RequestData + { + private LinkedList<IManageablePage> pages = new LinkedList<>(); + + public void add(IManageablePage page) + { + // add as last + pages.remove(page); + pages.addLast(page); + } + + public Iterable<IManageablePage> pages() + { + return pages; + } + + public IManageablePage get(int id) { + for (IManageablePage page : pages) { + if (page.getPageId() == id) { + return page; + } + } + return null; + } + + public void remove(IManageablePage page) + { + pages.remove(page); + } + + public void removeAll() + { + pages.clear(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java deleted file mode 100644 index 3d30857..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.wicket.pageStore; - -/** - * An application scoped cache that holds the last N used pages in the application. - * Acts as a second level cache between the Http Session (first level) and the - * disk (third level cache). - * - * @param <S> - * The type of the session identifier - * @param <PI> - * The type of the page identifier - * @param <P> - * The type of the stored page - */ -public interface SecondLevelPageCache<S, PI, P> -{ - P removePage(S session, PI pageId); - - void removePages(S session); - - P getPage(S session, PI pageId); - - void storePage(S session, PI pageId, P page); - - void destroy(); -} http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java new file mode 100644 index 0000000..5d858b5 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.util.lang.Args; + +/** + * A wrapper around a serialized page. + * <p> + * An {@link IPageStore} might use this representation of a page internally, + * accept it in {@link IPageStore#addPage(IPageContext, IManageablePage)} or delegate it + * to another store. + * <p> + * Stores might pose restrictions on what type of pages they work with, see {@link CryptingPageStore} + * for an example. + * + * @see SerializingPageStore + */ +class SerializedPage implements IManageablePage +{ + + private final int pageId; + + private final String pageType; + + private final byte[] data; + + public SerializedPage(int pageId, String pageType, byte[] data) + { + this.pageId = pageId; + this.pageType = pageType; + this.data = Args.notNull(data, "data"); + } + + @Override + public boolean isPageStateless() + { + return false; + } + + @Override + public int getPageId() + { + return pageId; + } + + public String getPageType() + { + return pageType; + } + + public byte[] getData() + { + return data; + } + + @Override + public void detach() + { + } + + @Override + public boolean setFreezePageId(boolean freeze) + { + return false; + } + + @Override + public String toString() + { + return "[SerializedPage id = " + pageId + "]"; + } +} \ No newline at end of file