/*
* 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.protocol.http.pagestore;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.wicket.Page;
import org.apache.wicket.Session;
import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore;
import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore;
import org.apache.wicket.session.pagemap.IPageMapEntry;
import org.apache.wicket.util.collections.IntHashMap;
import org.apache.wicket.util.lang.Objects;
/**
* Abstract page store that implements the serialization logic so that the subclasses can
* concentrate on actual storing of serialized page instances.
*
* @author Matej Knopp
*/
public abstract class AbstractPageStore implements IPageStore
{
/**
* Immutable class that contains a serialized page instance.
*
* @author Matej Knopp
*/
protected static class SerializedPage implements Serializable
{
private static final long serialVersionUID = 1L;
private final int pageId;
private final String pageMapName;
private final int versionNumber;
private final int ajaxVersionNumber;
private byte[] data;
/**
* Construct.
*
* @param pageId
* @param pageMapName
* @param versionNumber
* @param ajaxVersionNumber
* @param data
*/
public SerializedPage(int pageId, String pageMapName, int versionNumber,
int ajaxVersionNumber, byte[] data)
{
this.pageId = pageId;
this.pageMapName = pageMapName;
this.versionNumber = versionNumber;
this.ajaxVersionNumber = ajaxVersionNumber;
this.data = data;
}
/**
* Construct.
*
* @param page
*/
public SerializedPage(Page page)
{
pageId = page.getNumericId();
pageMapName = page.getPageMapName();
versionNumber = page.getCurrentVersionNumber();
ajaxVersionNumber = page.getAjaxVersionNumber();
}
/**
* @return
*/
public int getPageId()
{
return pageId;
}
/**
* @return
*/
public String getPageMapName()
{
return pageMapName;
}
/**
* @return
*/
public int getVersionNumber()
{
return versionNumber;
}
/**
* @return
*/
public int getAjaxVersionNumber()
{
return ajaxVersionNumber;
}
/**
* @return
*/
public byte[] getData()
{
return data;
}
/**
* @param data
*/
public void setData(byte[] data)
{
this.data = data;
}
public int hashCode()
{
return pageId * 1931 + versionNumber * 13 + ajaxVersionNumber * 301 +
(pageMapName != null ? pageMapName.hashCode() : 0);
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj instanceof SerializedPage == false)
return false;
SerializedPage rhs = (SerializedPage)obj;
return pageId == rhs.pageId &&
(pageMapName == rhs.pageMapName || (pageMapName != null && pageMapName.equals(rhs.pageMapName))) &&
versionNumber == rhs.versionNumber && ajaxVersionNumber == rhs.ajaxVersionNumber;
}
};
/**
* Creates a list of {@link SerializedPage} instances obtained from serializing the provided
* page.
* <p>
* One page instance can be serialized to multiple {@link SerializedPage} instances, because
* each referenced page is serialized separately and should also be separately saved On
* deserialization wicket detects a page instance placeholder and loads the appropriate page.
* <p>
* As an example, when there is PageA that has a member variable of type PageB, serializing
* instanceof PageA will result in a list of two {@link SerializedPage} instances, one for PageA
* and another one for the referenced PageB.
*
* @param page
* page to be serialized
* @return list of {@link SerializedPage}s
*/
protected List/* <SerializedPage> */serializePage(Page page)
{
final List result = new ArrayList();
SerializedPage initialPage = new SerializedPage(page);
result.add(initialPage);
PageSerializer serializer = new PageSerializer(initialPage)
{
protected void onPageSerialized(SerializedPage page)
{
result.add(page);
}
};
Page.serializer.set(serializer);
try
{
initialPage.setData(Objects.objectToByteArray(page.getPageMapEntry()));
}
finally
{
Page.serializer.set(null);
}
return result;
}
/**
* Creates a page instance from given byte array. Optionally gets the specified version of the
* page.
*
* @param data
* Serialized page instance data as byte array
* @param versionNumber
* Requested page version or -1 if original version (the one serialized) should be
* kept
* @return page instance
*/
protected Page deserializePage(byte[] data, int versionNumber)
{
boolean set = Page.serializer.get() == null;
Page page = null;
try
{
if (set)
{
Page.serializer.set(new PageSerializer(null));
}
IPageMapEntry entry = (IPageMapEntry)Objects.byteArrayToObject(data);
if (entry != null)
{
page = entry.getPage();
if (versionNumber != -1)
{
page = page.getVersion(versionNumber);
}
}
}
finally
{
if (set)
{
Page.serializer.set(null);
}
}
return page;
}
/**
* Internal class for page serialization and deserialization
*
* @author Matej Knopp
* @author Johan Compagner
*/
private static class PageSerializer implements Page.IPageSerializer
{
private SerializedPage current;
private final List previous = new ArrayList();
private final List completed = new ArrayList();
protected void onPageSerialized(SerializedPage page)
{
}
/**
* Construct.
*
* @param page
*/
public PageSerializer(SerializedPage page)
{
current = page;
}
/**
* @throws IOException
* @see org.apache.wicket.Page.IPageSerializer#serializePage(org.apache.wicket.Page,
* java.io.ObjectOutputStream)
*/
public void serializePage(Page page, ObjectOutputStream stream) throws IOException
{
if (current.getPageId() == page.getNumericId())
{
stream.writeBoolean(false);
stream.defaultWriteObject();
return;
}
SerializedPage spk = new SerializedPage(page);
if (!completed.contains(spk) && !previous.contains(spk))
{
previous.add(current);
current = spk;
byte[] bytes = Objects.objectToByteArray(page.getPageMapEntry());
current.setData(bytes);
onPageSerialized(current);
completed.add(current);
current = (SerializedPage)previous.remove(previous.size() - 1);
}
stream.writeBoolean(true);
stream.writeObject(new PageHolder(page));
}
public Page deserializePage(int id, String pageMapName, Page page, ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
HashMap pageMaps = (HashMap)SecondLevelCacheSessionStore.getUsedPages().get();
if (pageMaps == null)
{
pageMaps = new HashMap();
SecondLevelCacheSessionStore.getUsedPages().set(pageMaps);
}
IntHashMap pages = (IntHashMap)pageMaps.get(pageMapName);
if (pages == null)
{
pages = new IntHashMap();
pageMaps.put(pageMapName, pages);
}
boolean b = stream.readBoolean();
if (b == false)
{
stream.defaultReadObject();
}
else
{
// the object will resolve to a Page (probably PageHolder)
page = (Page)stream.readObject();
}
pages.put(id, page);
return page;
}
}
/**
* Class that resolves to page instance
*/
private static class PageHolder implements Serializable
{
private static final long serialVersionUID = 1L;
private final int pageid;
private final String pagemap;
PageHolder(Page page)
{
pageid = page.getNumericId();
pagemap = page.getPageMapName();
}
protected Object readResolve() throws ObjectStreamException
{
return Session.get().getPage(pagemap, Integer.toString(pageid), -1);
}
}
}