001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.providers;
021:
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.io.IOException;
025: import java.io.FileNotFoundException;
026: import java.io.File;
027: import java.io.FilenameFilter;
028: import java.io.FileOutputStream;
029: import java.io.FileInputStream;
030:
031: import java.util.Collection;
032: import java.util.Properties;
033: import java.util.ArrayList;
034: import java.util.Iterator;
035: import java.util.Date;
036: import java.util.List;
037: import java.util.Collections;
038: import java.util.regex.Matcher;
039: import java.util.regex.Pattern;
040:
041: import org.apache.log4j.Logger;
042:
043: import com.ecyrd.jspwiki.*;
044: import com.ecyrd.jspwiki.attachment.Attachment;
045:
046: /**
047: * Provides basic, versioning attachments.
048: *
049: * <PRE>
050: * Structure is as follows:
051: * attachment_dir/
052: * ThisPage/
053: * attachment.doc/
054: * attachment.properties
055: * 1.doc
056: * 2.doc
057: * 3.doc
058: * picture.png/
059: * attachment.properties
060: * 1.png
061: * 2.png
062: * ThatPage/
063: * picture.png/
064: * attachment.properties
065: * 1.png
066: *
067: * </PRE>
068: *
069: * The names of the directories will be URLencoded.
070: * <p>
071: * "attachment.properties" consists of the following items:
072: * <UL>
073: * <LI>1.author = author name for version 1 (etc)
074: * </UL>
075: */
076: public class BasicAttachmentProvider implements WikiAttachmentProvider {
077: private WikiEngine m_engine;
078: private String m_storageDir;
079: public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";
080:
081: /*
082: * Disable client cache for files with patterns
083: * since 2.5.96
084: */
085: private Pattern m_disableCache = null;
086: public static final String PROP_DISABLECACHE = "jspwiki.basicAttachmentProvider.disableCache";
087:
088: public static final String PROPERTY_FILE = "attachment.properties";
089:
090: public static final String DIR_EXTENSION = "-att";
091: public static final String ATTDIR_EXTENSION = "-dir";
092:
093: static final Logger log = Logger
094: .getLogger(BasicAttachmentProvider.class);
095:
096: public void initialize(WikiEngine engine, Properties properties)
097: throws NoRequiredPropertyException, IOException {
098: m_engine = engine;
099: m_storageDir = WikiEngine.getRequiredProperty(properties,
100: PROP_STORAGEDIR);
101:
102: String patternString = engine.getWikiProperties().getProperty(
103: PROP_DISABLECACHE);
104: if (patternString != null) {
105: m_disableCache = Pattern.compile(patternString);
106: }
107:
108: //
109: // Check if the directory exists - if it doesn't, create it.
110: //
111: File f = new File(m_storageDir);
112:
113: if (!f.exists()) {
114: f.mkdirs();
115: }
116:
117: //
118: // Some sanity checks
119: //
120: if (!f.exists())
121: throw new IOException(
122: "Could not find or create attachment storage directory '"
123: + m_storageDir + "'");
124:
125: if (!f.canWrite())
126: throw new IOException(
127: "Cannot write to the attachment storage directory '"
128: + m_storageDir + "'");
129:
130: if (!f.isDirectory())
131: throw new IOException(
132: "Your attachment storage points to a file, not a directory: '"
133: + m_storageDir + "'");
134: }
135:
136: /**
137: * Finds storage dir, and if it exists, makes sure that it is valid.
138: *
139: * @param wikipage Page to which this attachment is attached.
140: */
141: private File findPageDir(String wikipage) throws ProviderException {
142: wikipage = mangleName(wikipage);
143:
144: File f = new File(m_storageDir, wikipage + DIR_EXTENSION);
145:
146: if (f.exists() && !f.isDirectory()) {
147: throw new ProviderException("Storage dir '"
148: + f.getAbsolutePath() + "' is not a directory!");
149: }
150:
151: return f;
152: }
153:
154: private static String mangleName(String wikiname) {
155: String res = TextUtil.urlEncodeUTF8(wikiname);
156:
157: return res;
158: }
159:
160: private static String unmangleName(String filename) {
161: return TextUtil.urlDecodeUTF8(filename);
162: }
163:
164: /**
165: * Finds the dir in which the attachment lives.
166: */
167: private File findAttachmentDir(Attachment att)
168: throws ProviderException {
169: File f = new File(findPageDir(att.getParentName()),
170: mangleName(att.getFileName() + ATTDIR_EXTENSION));
171:
172: //
173: // Migration code for earlier versions of JSPWiki.
174: // Originally, we used plain filename. Then we realized we need
175: // to urlencode it. Then we realized that we have to use a
176: // postfix to make sure illegal file names are never formed.
177: //
178: if (!f.exists()) {
179: File oldf = new File(findPageDir(att.getParentName()),
180: mangleName(att.getFileName()));
181: if (oldf.exists()) {
182: f = oldf;
183: } else {
184: oldf = new File(findPageDir(att.getParentName()), att
185: .getFileName());
186:
187: if (oldf.exists()) {
188: f = oldf;
189: }
190: }
191: }
192:
193: return f;
194: }
195:
196: /**
197: * Goes through the repository and decides which version is
198: * the newest one in that directory.
199: *
200: * @return Latest version number in the repository, or 0, if
201: * there is no page in the repository.
202: */
203: private int findLatestVersion(Attachment att)
204: throws ProviderException {
205: // File pageDir = findPageDir( att.getName() );
206: File attDir = findAttachmentDir(att);
207:
208: // log.debug("Finding pages in "+attDir.getAbsolutePath());
209: String[] pages = attDir.list(new AttachmentVersionFilter());
210:
211: if (pages == null) {
212: return 0; // No such thing found.
213: }
214:
215: int version = 0;
216:
217: for (int i = 0; i < pages.length; i++) {
218: // log.debug("Checking: "+pages[i]);
219: int cutpoint = pages[i].indexOf('.');
220: String pageNum = (cutpoint > 0) ? pages[i].substring(0,
221: cutpoint) : pages[i];
222:
223: try {
224: int res = Integer.parseInt(pageNum);
225:
226: if (res > version) {
227: version = res;
228: }
229: } catch (NumberFormatException e) {
230: } // It's okay to skip these.
231: }
232:
233: return version;
234: }
235:
236: /**
237: * Returns the file extension. For example "test.png" returns "png".
238: * <p>
239: * If file has no extension, will return "bin"
240: */
241: protected static String getFileExtension(String filename) {
242: String fileExt = "bin";
243:
244: int dot = filename.lastIndexOf('.');
245: if (dot >= 0 && dot < filename.length() - 1) {
246: fileExt = mangleName(filename.substring(dot + 1));
247: }
248:
249: return fileExt;
250: }
251:
252: /**
253: * Writes the page properties back to the file system.
254: * Note that it WILL overwrite any previous properties.
255: */
256: private void putPageProperties(Attachment att, Properties properties)
257: throws IOException, ProviderException {
258: File attDir = findAttachmentDir(att);
259: File propertyFile = new File(attDir, PROPERTY_FILE);
260:
261: OutputStream out = new FileOutputStream(propertyFile);
262:
263: properties.store(out, " JSPWiki page properties for "
264: + att.getName() + ". DO NOT MODIFY!");
265:
266: out.close();
267: }
268:
269: /**
270: * Reads page properties from the file system.
271: */
272: private Properties getPageProperties(Attachment att)
273: throws IOException, ProviderException {
274: Properties props = new Properties();
275:
276: File propertyFile = new File(findAttachmentDir(att),
277: PROPERTY_FILE);
278:
279: if (propertyFile.exists()) {
280: InputStream in = new FileInputStream(propertyFile);
281:
282: props.load(in);
283:
284: in.close();
285: }
286:
287: return props;
288: }
289:
290: public void putAttachmentData(Attachment att, InputStream data)
291: throws ProviderException, IOException {
292: OutputStream out = null;
293: File attDir = findAttachmentDir(att);
294:
295: if (!attDir.exists()) {
296: attDir.mkdirs();
297: }
298:
299: int latestVersion = findLatestVersion(att);
300:
301: // System.out.println("Latest version is "+latestVersion);
302:
303: try {
304: int versionNumber = latestVersion + 1;
305:
306: File newfile = new File(attDir, versionNumber + "."
307: + getFileExtension(att.getFileName()));
308:
309: log.info("Uploading attachment " + att.getFileName()
310: + " to page " + att.getParentName());
311: log.info("Saving attachment contents to "
312: + newfile.getAbsolutePath());
313: out = new FileOutputStream(newfile);
314:
315: FileUtil.copyContents(data, out);
316:
317: out.close();
318:
319: Properties props = getPageProperties(att);
320:
321: String author = att.getAuthor();
322:
323: if (author == null) {
324: author = "unknown";
325: }
326:
327: props.setProperty(versionNumber + ".author", author);
328:
329: String changeNote = (String) att
330: .getAttribute(WikiPage.CHANGENOTE);
331: if (changeNote != null) {
332: props.setProperty(versionNumber + ".changenote",
333: changeNote);
334: }
335:
336: putPageProperties(att, props);
337: } catch (IOException e) {
338: log.error("Could not save attachment data: ", e);
339: throw (IOException) e.fillInStackTrace();
340: } finally {
341: if (out != null)
342: out.close();
343: }
344: }
345:
346: public String getProviderInfo() {
347: return "";
348: }
349:
350: private File findFile(File dir, Attachment att)
351: throws FileNotFoundException, ProviderException {
352: int version = att.getVersion();
353:
354: if (version == WikiProvider.LATEST_VERSION) {
355: version = findLatestVersion(att);
356: }
357:
358: String ext = getFileExtension(att.getFileName());
359: File f = new File(dir, version + "." + ext);
360:
361: if (!f.exists()) {
362: if ("bin".equals(ext)) {
363: File fOld = new File(dir, version + ".");
364: if (fOld.exists())
365: f = fOld;
366: }
367: if (!f.exists()) {
368: throw new FileNotFoundException("No such file: "
369: + f.getAbsolutePath() + " exists.");
370: }
371: }
372:
373: return f;
374: }
375:
376: public InputStream getAttachmentData(Attachment att)
377: throws IOException, ProviderException {
378: File attDir = findAttachmentDir(att);
379:
380: try {
381: File f = findFile(attDir, att);
382:
383: return new FileInputStream(f);
384: } catch (FileNotFoundException e) {
385: log.error("File not found: " + e.getMessage());
386: throw new ProviderException("No such page was found.");
387: }
388: }
389:
390: public Collection listAttachments(WikiPage page)
391: throws ProviderException {
392: Collection result = new ArrayList();
393:
394: File dir = findPageDir(page.getName());
395:
396: if (dir != null) {
397: String[] attachments = dir.list();
398:
399: if (attachments != null) {
400: //
401: // We now have a list of all potential attachments in
402: // the directory.
403: //
404: for (int i = 0; i < attachments.length; i++) {
405: File f = new File(dir, attachments[i]);
406:
407: if (f.isDirectory()) {
408: String attachmentName = unmangleName(attachments[i]);
409:
410: //
411: // Is it a new-stylea attachment directory? If yes,
412: // we'll just deduce the name. If not, however,
413: // we'll check if there's a suitable property file
414: // in the directory.
415: //
416: if (attachmentName.endsWith(ATTDIR_EXTENSION)) {
417: attachmentName = attachmentName
418: .substring(0, attachmentName
419: .length()
420: - ATTDIR_EXTENSION.length());
421: } else {
422: File propFile = new File(f, PROPERTY_FILE);
423:
424: if (!propFile.exists()) {
425: //
426: // This is not obviously a JSPWiki attachment,
427: // so let's just skip it.
428: //
429: continue;
430: }
431: }
432:
433: Attachment att = getAttachmentInfo(page,
434: attachmentName,
435: WikiProvider.LATEST_VERSION);
436:
437: //
438: // Sanity check - shouldn't really be happening, unless
439: // you mess with the repository directly.
440: //
441: if (att == null) {
442: throw new ProviderException(
443: "Attachment disappeared while reading information:"
444: + " if you did not touch the repository, there is a serious bug somewhere. "
445: + "Attachment = "
446: + attachments[i]
447: + ", decoded = "
448: + attachmentName);
449: }
450:
451: result.add(att);
452: }
453: }
454: }
455: }
456:
457: return result;
458: }
459:
460: public Collection findAttachments(QueryItem[] query) {
461: return null;
462: }
463:
464: // FIXME: Very unoptimized.
465: public List listAllChanged(Date timestamp) throws ProviderException {
466: File attDir = new File(m_storageDir);
467:
468: if (!attDir.exists()) {
469: throw new ProviderException(
470: "Specified attachment directory " + m_storageDir
471: + " does not exist!");
472: }
473:
474: ArrayList list = new ArrayList();
475:
476: String[] pagesWithAttachments = attDir
477: .list(new AttachmentFilter());
478:
479: for (int i = 0; i < pagesWithAttachments.length; i++) {
480: String pageId = unmangleName(pagesWithAttachments[i]);
481: pageId = pageId.substring(0, pageId.length()
482: - DIR_EXTENSION.length());
483:
484: Collection c = listAttachments(new WikiPage(m_engine,
485: pageId));
486:
487: for (Iterator it = c.iterator(); it.hasNext();) {
488: Attachment att = (Attachment) it.next();
489:
490: if (att.getLastModified().after(timestamp)) {
491: list.add(att);
492: }
493: }
494: }
495:
496: Collections.sort(list, new PageTimeComparator());
497:
498: return list;
499: }
500:
501: public Attachment getAttachmentInfo(WikiPage page, String name,
502: int version) throws ProviderException {
503: Attachment att = new Attachment(m_engine, page.getName(), name);
504: File dir = findAttachmentDir(att);
505:
506: if (!dir.exists()) {
507: // log.debug("Attachment dir not found - thus no attachment can exist.");
508: return null;
509: }
510:
511: if (version == WikiProvider.LATEST_VERSION) {
512: version = findLatestVersion(att);
513: }
514:
515: att.setVersion(version);
516:
517: // Should attachment be cachable by the client (browser)?
518: if (m_disableCache != null) {
519: Matcher matcher = m_disableCache.matcher(name);
520: if (matcher.matches()) {
521: att.setCacheable(false);
522: }
523: }
524:
525: // System.out.println("Fetching info on version "+version);
526: try {
527: Properties props = getPageProperties(att);
528:
529: att.setAuthor(props.getProperty(version + ".author"));
530:
531: String changeNote = props.getProperty(version
532: + ".changenote");
533: if (changeNote != null) {
534: att.setAttribute(WikiPage.CHANGENOTE, changeNote);
535: }
536:
537: File f = findFile(dir, att);
538:
539: att.setSize(f.length());
540: att.setLastModified(new Date(f.lastModified()));
541: } catch (FileNotFoundException e) {
542: return null;
543: } catch (IOException e) {
544: log.error("Can't read page properties", e);
545: throw new ProviderException("Cannot read page properties: "
546: + e.getMessage());
547: }
548: // FIXME: Check for existence of this particular version.
549:
550: return att;
551: }
552:
553: public List getVersionHistory(Attachment att) {
554: ArrayList list = new ArrayList();
555:
556: try {
557: int latest = findLatestVersion(att);
558:
559: for (int i = latest; i >= 1; i--) {
560: Attachment a = getAttachmentInfo(new WikiPage(m_engine,
561: att.getParentName()), att.getFileName(), i);
562:
563: if (a != null) {
564: list.add(a);
565: }
566: }
567: } catch (ProviderException e) {
568: log.error(
569: "Getting version history failed for page: " + att,
570: e);
571: // FIXME: SHould this fail?
572: }
573:
574: return list;
575: }
576:
577: public void deleteVersion(Attachment att) throws ProviderException {
578: // FIXME: Does nothing yet.
579: }
580:
581: public void deleteAttachment(Attachment att)
582: throws ProviderException {
583: File dir = findAttachmentDir(att);
584: String[] files = dir.list();
585:
586: for (int i = 0; i < files.length; i++) {
587: File file = new File(dir.getAbsolutePath() + "/" + files[i]);
588: file.delete();
589: }
590: dir.delete();
591: }
592:
593: /**
594: * Returns only those directories that contain attachments.
595: */
596: public static class AttachmentFilter implements FilenameFilter {
597: public boolean accept(File dir, String name) {
598: return name.endsWith(DIR_EXTENSION);
599: }
600: }
601:
602: /**
603: * Accepts only files that are actual versions, no control files.
604: */
605: public static class AttachmentVersionFilter implements
606: FilenameFilter {
607: public boolean accept(File dir, String name) {
608: return !name.equals(PROPERTY_FILE);
609: }
610: }
611:
612: public void moveAttachmentsForPage(String oldParent,
613: String newParent) throws ProviderException {
614: File srcDir = findPageDir(oldParent);
615: File destDir = findPageDir(newParent);
616:
617: log.debug("Trying to move all attachments from " + srcDir
618: + " to " + destDir);
619:
620: // If it exists, we're overwriting an old page (this has already been
621: // confirmed at a higher level), so delete any existing attachments.
622: if (destDir.exists()) {
623: log.error("Page rename failed because target dirctory "
624: + destDir + " exists");
625: } else {
626: //destDir.getParentFile().mkdir();
627: srcDir.renameTo(destDir);
628: }
629: }
630: }
|