001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.protocol.http.pagestore;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.FileOutputStream;
022: import java.io.FilenameFilter;
023: import java.io.IOException;
024: import java.nio.ByteBuffer;
025: import java.util.Arrays;
026: import java.util.Comparator;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import org.apache.wicket.Application;
031: import org.apache.wicket.Page;
032: import org.apache.wicket.protocol.http.WebApplication;
033: import org.slf4j.Logger;
034: import org.slf4j.LoggerFactory;
035:
036: /**
037: * Very simple page store that uses separate file for each serialized page
038: * instance. Also this store doesn't use any worker threads.
039: * <p>
040: * This store is for demonstration purposes only and will perfom badly in
041: * production.
042: *
043: * @author Matej Knopp
044: */
045: public class SimpleSynchronousFilePageStore extends AbstractPageStore {
046: private final File defaultWorkDir;
047: private final String appName;
048:
049: /**
050: * Construct.
051: *
052: * @param workDir
053: */
054: public SimpleSynchronousFilePageStore(File workDir) {
055: defaultWorkDir = workDir;
056: defaultWorkDir.mkdirs();
057:
058: appName = Application.get().getApplicationKey();
059: }
060:
061: /**
062: * Construct.
063: */
064: public SimpleSynchronousFilePageStore() {
065: this ((File) ((WebApplication) Application.get())
066: .getServletContext().getAttribute(
067: "javax.servlet.context.tempdir"));
068: }
069:
070: private String getFileName(String pageMapName, int pageId) {
071: return appName + "-pm-" + pageMapName + "-p-" + pageId;
072: }
073:
074: // sort file by modification time
075: private void sortFiles(File[] files) {
076: Arrays.sort(files, new Comparator() {
077: public int compare(Object arg0, Object arg1) {
078: File f1 = (File) arg0;
079: File f2 = (File) arg1;
080:
081: return f1.lastModified() < f2.lastModified() ? -1 : (f1
082: .lastModified() == f2.lastModified() ? 0 : 1);
083: }
084: });
085: }
086:
087: /**
088: * Returns the file for specified page. The specification might be
089: * incomplete (-1 set as versionNumber or ajaxVersionNumber). In that case,
090: * it search for the correct file. The search traverses the session folder
091: * which is inefficient and should not be used in production. It's good to
092: * manage/cache this information in page store implementations.
093: *
094: * @param sessionDir
095: * @param pageMapName
096: * @param pageId
097: * @param versionNumber
098: * @param ajaxVersionNumber
099: * @return
100: */
101: private File getPageFile(File sessionDir, String pageMapName,
102: int pageId, int versionNumber, int ajaxVersionNumber) {
103: final String fileNamePrefix = getFileName(pageMapName, pageId);
104: if (versionNumber != -1 && ajaxVersionNumber != -1) {
105: return new File(sessionDir, fileNamePrefix + "-v-"
106: + versionNumber + "-a-" + ajaxVersionNumber);
107: } else if (versionNumber == -1) {
108: // if versionNumber is -1, we need the last touched (saved) file
109: File[] files = sessionDir.listFiles(new FilenameFilter() {
110: public boolean accept(File dir, String name) {
111: return name.startsWith(fileNamePrefix);
112: }
113: });
114: if (files == null || files.length == 0) {
115: return null;
116: }
117: sortFiles(files);
118: return files[files.length - 1];
119: } else {
120: // if versionNumber is specified and ajaxVersionNumber is -1, we
121: // need page
122: // with the highest ajax number
123: final String prefixWithVersion = fileNamePrefix + "-v-"
124: + versionNumber;
125: // if versionNumber is -1, we need the last touched (saved) file
126: File[] files = sessionDir.listFiles(new FilenameFilter() {
127: public boolean accept(File dir, String name) {
128: return name.startsWith(prefixWithVersion);
129: }
130: });
131: if (files == null || files.length == 0) {
132: return null;
133: }
134: // from the list of files, we need to pick one with highest ajax
135: // version
136: // (the last integer in file name)
137: int lastAjaxVersion = -1;
138: int indexWithBiggestAjaxVersion = -1;
139: for (int i = 0; i < files.length; ++i) {
140: File file = files[i];
141: String ajaxVersionString = file.getName().substring(
142: file.getName().lastIndexOf('-') + 1);
143: int ajaxVersion = Integer.parseInt(ajaxVersionString);
144: if (lastAjaxVersion < ajaxVersion) {
145: lastAjaxVersion = ajaxVersion;
146: indexWithBiggestAjaxVersion = i;
147: }
148: }
149:
150: return files[indexWithBiggestAjaxVersion];
151: }
152: }
153:
154: public void destroy() {
155: }
156:
157: protected byte[] loadPageData(File workDir, String sessionId,
158: String pageMapName, int pageId, int versionNumber,
159: int ajaxVersionNumber) {
160: File sessionDir = new File(workDir, sessionId);
161: byte[] pageData = null;
162:
163: if (sessionDir.exists()) {
164: File pageFile = getPageFile(sessionDir, pageMapName,
165: pageId, versionNumber, ajaxVersionNumber);
166: if (pageFile.exists()) {
167: long t1 = System.currentTimeMillis();
168: FileInputStream fis = null;
169: try {
170: fis = new FileInputStream(pageFile);
171: int length = (int) pageFile.length();
172: ByteBuffer bb = ByteBuffer.allocate(length);
173: fis.getChannel().read(bb);
174: if (bb.hasArray()) {
175: pageData = bb.array();
176: } else {
177: pageData = new byte[length];
178: bb.get(pageData);
179: }
180: } catch (Exception e) {
181: log.debug("Error loading page " + pageId + ","
182: + versionNumber + " for the sessionid "
183: + sessionId + " from disk", e);
184: } finally {
185: try {
186: if (fis != null) {
187: fis.close();
188: }
189: } catch (IOException ex) {
190: // ignore
191: }
192: }
193: }
194: }
195: return pageData;
196: }
197:
198: public Page getPage(String sessionId, String pageMapName,
199: int pageId, int versionNumber, int ajaxVersionNumber) {
200: byte data[] = loadPageData(defaultWorkDir, sessionId,
201: pageMapName, pageId, versionNumber, ajaxVersionNumber);
202: if (data != null) {
203: return deserializePage(data, versionNumber);
204: } else {
205: return null;
206: }
207: }
208:
209: public void pageAccessed(String sessionId, Page page) {
210: }
211:
212: private void removeFiles(String sessionId, String pageMap, int id) {
213: File sessionDir = new File(defaultWorkDir, sessionId);
214: if (sessionDir.exists()) {
215: final String filepart;
216: if (id != -1) {
217: filepart = appName + "-pm-" + pageMap + "-p-" + id;
218: } else {
219: filepart = appName + "-pm-" + pageMap;
220: }
221: File[] listFiles = sessionDir
222: .listFiles(new FilenameFilter() {
223: public boolean accept(File dir, String name) {
224: return name.startsWith(filepart);
225: }
226: });
227: for (int i = 0; i < listFiles.length; i++) {
228: listFiles[i].delete();
229: }
230: }
231:
232: }
233:
234: protected long savePageData(String sessionId, SerializedPage page) {
235: File sessionDir = new File(defaultWorkDir, sessionId);
236: sessionDir.mkdirs();
237:
238: File pageFile = getPageFile(sessionDir, page.getPageMapName(),
239: page.getPageId(), page.getVersionNumber(), page
240: .getAjaxVersionNumber());
241: FileOutputStream fos = null;
242: int length = 0;
243: try {
244: fos = new FileOutputStream(pageFile);
245: ByteBuffer bb = ByteBuffer.wrap(page.getData());
246: fos.getChannel().write(bb);
247: length = page.getData().length;
248: } catch (Exception e) {
249: log
250: .error("Error saving page "
251: + pageFile.getAbsolutePath());
252: } finally {
253: try {
254: if (fos != null) {
255: fos.close();
256: }
257: } catch (IOException ex) {
258: // ignore
259: }
260: }
261: return length;
262: }
263:
264: public void removePage(String sessionId, String pageMapName,
265: int pageId) {
266: removeFiles(sessionId, pageMapName, pageId);
267: }
268:
269: public void storePage(String sessionId, Page page) {
270: List/* SerializedPage */serialized = serializePage(page);
271:
272: for (Iterator i = serialized.iterator(); i.hasNext();) {
273: SerializedPage serializedPage = (SerializedPage) i.next();
274: savePageData(sessionId, serializedPage);
275: }
276: }
277:
278: private void removeSession(String sessionId) {
279: File sessionDir = new File(defaultWorkDir, sessionId);
280: if (sessionDir.exists()) {
281: File[] files = sessionDir.listFiles();
282: if (files != null) {
283: for (int i = 0; i < files.length; i++) {
284: files[i].delete();
285: }
286: }
287: if (!sessionDir.delete()) {
288: sessionDir.deleteOnExit();
289: }
290: }
291: }
292:
293: public void unbind(String sessionId) {
294: removeSession(sessionId);
295: }
296:
297: public boolean containsPage(String sessionId, String pageMapName,
298: int pageId, int pageVersion) {
299: File sessionDir = new File(defaultWorkDir, sessionId);
300: File pageFile = getPageFile(sessionDir, pageMapName, pageId,
301: pageVersion, -1);
302: return pageFile.exists();
303: }
304:
305: private static final Logger log = LoggerFactory
306: .getLogger(SimpleSynchronousFilePageStore.class);
307: }
|