| 1 | |
package org.webslinger.ext.support; |
| 2 | |
|
| 3 | |
import java.io.IOException; |
| 4 | |
import java.sql.Timestamp; |
| 5 | |
import java.text.DateFormat; |
| 6 | |
import java.util.ArrayList; |
| 7 | |
import java.util.Comparator; |
| 8 | |
import java.util.HashMap; |
| 9 | |
import java.util.Iterator; |
| 10 | |
import java.util.LinkedHashMap; |
| 11 | |
import java.util.List; |
| 12 | |
import java.util.Map; |
| 13 | |
import java.util.TreeSet; |
| 14 | |
import java.util.Set; |
| 15 | |
import java.util.StringTokenizer; |
| 16 | |
import javax.servlet.ServletException; |
| 17 | |
import javax.servlet.http.HttpServletRequest; |
| 18 | |
import javax.servlet.http.HttpServletResponse; |
| 19 | |
|
| 20 | |
import org.apache.commons.collections.Transformer; |
| 21 | |
import org.apache.commons.collections.map.AbstractReferenceMap; |
| 22 | |
import org.apache.commons.collections.map.LazyMap; |
| 23 | |
import org.apache.commons.collections.map.ReferenceMap; |
| 24 | |
import org.apache.commons.fileupload.FileItem; |
| 25 | |
import org.apache.commons.vfs.FileContent; |
| 26 | |
import org.apache.commons.vfs.FileName; |
| 27 | |
import org.apache.commons.vfs.FileObject; |
| 28 | |
|
| 29 | |
import org.webslinger.PathContext; |
| 30 | |
import org.webslinger.ThemingFactory; |
| 31 | |
import org.webslinger.TypeHandler; |
| 32 | |
import org.webslinger.Webslinger; |
| 33 | |
import org.webslinger.WebslingerServletContext; |
| 34 | |
import org.webslinger.collections.KeyedMapEntryComparator; |
| 35 | |
import org.webslinger.commons.vfs.CachingLister; |
| 36 | |
import org.webslinger.commons.vfs.ReloadingCachingLister; |
| 37 | |
import org.webslinger.container.FileInfo; |
| 38 | |
import org.webslinger.ext.types.thumb; |
| 39 | |
import org.webslinger.servlet.MultipartFormUtil; |
| 40 | |
import org.webslinger.util.TTLObject; |
| 41 | |
|
| 42 | 3 | public class DirectoryLister extends TTLObject<Map<Comparator, Map<String, Map<String, Object>>>> { |
| 43 | |
static { |
| 44 | 1 | TTLObject.setDefaultTTLForClass(DirectoryLister.class, 1000); |
| 45 | |
} |
| 46 | |
|
| 47 | 15 | protected static class FileSorter extends KeyedMapEntryComparator { |
| 48 | |
protected FileSorter(String name, boolean descending) { |
| 49 | 1 | super(name, descending); |
| 50 | 1 | } |
| 51 | |
|
| 52 | |
public int compare(Map left, Map right) { |
| 53 | 15 | Integer leftSeq = (Integer) left.get("sequence"); |
| 54 | 15 | Integer rightSeq = (Integer) right.get("sequence"); |
| 55 | 15 | int r = leftSeq.intValue() - rightSeq.intValue(); |
| 56 | 15 | if (r == 0) return super.compare(left, right); |
| 57 | 0 | return direction ? -r : r; |
| 58 | |
} |
| 59 | |
}; |
| 60 | |
|
| 61 | 1 | private static final Comparator DEFAULT_COMPARATOR = new FileSorter("name", false); |
| 62 | |
|
| 63 | |
public final WebslingerServletContext wsc; |
| 64 | |
public final FileObject base; |
| 65 | |
public final String path; |
| 66 | |
public final FileObject file; |
| 67 | |
|
| 68 | 2 | public DirectoryLister(WebslingerServletContext wsc, FileObject base, String path) throws IOException { |
| 69 | 2 | this.wsc = wsc; |
| 70 | 2 | this.base = base; |
| 71 | 3 | while (path.length() > 0 && path.startsWith("/")) path = path.substring(1); |
| 72 | 2 | this.path = path; |
| 73 | 2 | file = base.resolveFile(path); |
| 74 | 2 | } |
| 75 | |
|
| 76 | |
protected Map<Comparator, Map<String, Map<String, Object>>> load(Map<Comparator, Map<String, Map<String, Object>>> oldContext) throws IOException { |
| 77 | 3 | file.refresh(); |
| 78 | 3 | if (!file.getType().hasChildren()) return null; |
| 79 | 3 | final Map<String, Map<String, Object>> unsortedFiles = new LinkedHashMap<String, Map<String, Object>>(); |
| 80 | 3 | FileObject[] children = file.getChildren(); |
| 81 | 15 | for (int i = 0; i < children.length; i++) { |
| 82 | 12 | FileObject child = children[i]; |
| 83 | 12 | FileContent content = child.getContent(); |
| 84 | 12 | Map<String, Object> datum = new HashMap<String, Object>(); |
| 85 | 12 | datum.put("path", child.getName().getPath()); |
| 86 | 12 | datum.put("time", new Timestamp(content.getLastModifiedTime())); |
| 87 | 12 | datum.put("description", content.getAttribute("description")); |
| 88 | 12 | FileInfo fileInfo = wsc.getContainer().getFileInfo(child); |
| 89 | 12 | datum.put("content-type", fileInfo.getContentType()); |
| 90 | 12 | String icon = (String) fileInfo.getAttribute("icon"); |
| 91 | 12 | if (icon == null) icon = "unknown.png"; |
| 92 | 12 | datum.put("icon", icon); |
| 93 | 12 | String sequence = (String) fileInfo.getAttribute("sequence"); |
| 94 | 12 | if (sequence == null) sequence = "0"; |
| 95 | 12 | datum.put("sequence", Integer.valueOf(sequence)); |
| 96 | 12 | if (child.getType().hasChildren()) { |
| 97 | 2 | datum.put("name", child.getName().getBaseName() + "/"); |
| 98 | 2 | datum.put("alt", "inode/directory"); |
| 99 | |
} else { |
| 100 | 10 | datum.put("name", child.getName().getBaseName()); |
| 101 | 10 | datum.put("size", new Long(content.getSize())); |
| 102 | |
} |
| 103 | 12 | unsortedFiles.put(child.getName().getBaseName(), datum); |
| 104 | |
} |
| 105 | |
|
| 106 | 3 | final Map<Comparator, Map<String, Map<String, Object>>> sorted = LazyMap.decorate(new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT), new Transformer() { |
| 107 | |
public Object transform(Object key) { |
| 108 | 3 | TreeSet<Map<String, Object>> set = new TreeSet<Map<String, Object>>((Comparator<Map<String, Object>>) key); |
| 109 | 3 | set.addAll(unsortedFiles.values()); |
| 110 | 3 | Map<String, Map<String, Object>> files = new LinkedHashMap<String, Map<String, Object>>(set.size()); |
| 111 | 3 | Iterator<Map<String, Object>> it = set.iterator(); |
| 112 | 15 | while (it.hasNext()) { |
| 113 | 12 | Map<String, Object> datum = it.next(); |
| 114 | 12 | files.put((String) datum.get("name"), datum); |
| 115 | 12 | } |
| 116 | 3 | return files; |
| 117 | |
} |
| 118 | |
}); |
| 119 | 3 | return sorted; |
| 120 | |
} |
| 121 | |
|
| 122 | |
public Map<String, Map<String, Object>> getFiles() throws IOException { |
| 123 | 0 | Map<Comparator, Map<String, Map<String, Object>>> sorted = getObject(); |
| 124 | 0 | return sorted != null ? sorted.get(DEFAULT_COMPARATOR) : null; |
| 125 | |
} |
| 126 | |
|
| 127 | |
public void list(Webslinger webslinger, String prefix, ThemingFactory<OutputConfig> configFactory) throws IOException, ServletException { |
| 128 | 3 | list(webslinger, webslinger.getPathContext(), webslinger.getRequest(), webslinger.getResponse(), webslinger.getContext(), prefix, configFactory.getThemedObject(webslinger.getThemePath())); |
| 129 | 3 | } |
| 130 | |
|
| 131 | |
public void list(Webslinger webslinger, Map<String, Object> context, String prefix, ThemingFactory<OutputConfig> configFactory) throws IOException, ServletException { |
| 132 | 0 | list(webslinger, webslinger.getPathContext(), webslinger.getRequest(), webslinger.getResponse(), context, prefix, configFactory.getThemedObject(webslinger.getThemePath())); |
| 133 | 0 | } |
| 134 | |
|
| 135 | |
public void list(Webslinger webslinger, HttpServletRequest request, HttpServletResponse response, Map<String, Object> context, String prefix, ThemingFactory<OutputConfig> configFactory) throws IOException, ServletException { |
| 136 | 0 | list(webslinger, webslinger.getPathContext(), request, response, context, prefix, configFactory.getThemedObject(webslinger.getThemePath())); |
| 137 | 0 | } |
| 138 | |
|
| 139 | |
public void list(Webslinger webslinger, String prefix, OutputConfig config) throws IOException, ServletException { |
| 140 | 0 | list(webslinger, webslinger.getPathContext(), webslinger.getRequest(), webslinger.getResponse(), webslinger.getContext(), prefix, config); |
| 141 | 0 | } |
| 142 | |
|
| 143 | |
public void list(Webslinger webslinger, Map<String, Object> context, String prefix, OutputConfig config) throws IOException, ServletException { |
| 144 | 0 | list(webslinger, webslinger.getPathContext(), webslinger.getRequest(), webslinger.getResponse(), context, prefix, config); |
| 145 | 0 | } |
| 146 | |
|
| 147 | |
public void list(Webslinger webslinger, PathContext pc, HttpServletRequest request, HttpServletResponse response, Map<String, Object> context, String prefix, OutputConfig config) throws IOException, ServletException { |
| 148 | 3 | Map<String, List<FileItem>> allParameters = (Map<String, List<FileItem>>) context.get("allParameters"); |
| 149 | 3 | if (allParameters == null) allParameters = MultipartFormUtil.parseRequestAsMap(request); |
| 150 | 3 | Map<String, String> parameters = MultipartFormUtil.filterOutStrings(allParameters); |
| 151 | 3 | if (file.getType().hasContent()) { |
| 152 | 0 | if (parameters.get("thumb") != null) { |
| 153 | 0 | thumb thumbHandler = (thumb) wsc.getTypeHandler("thumb"); |
| 154 | 0 | thumbHandler.serveThumb(wsc.resolve(file), request, response, 100, 100); |
| 155 | 0 | } else { |
| 156 | 0 | webslinger.forward(wsc.resolve(file)); |
| 157 | |
} |
| 158 | 0 | return; |
| 159 | |
} |
| 160 | 3 | context.put("TargetFile", file); |
| 161 | 3 | context.put("Base", base); |
| 162 | 3 | context.put("Path", path); |
| 163 | 3 | context.put("Config", config); |
| 164 | 3 | context.put("ListingPath", prefix + '/' + path); |
| 165 | 3 | context.put("Errors", new ArrayList()); |
| 166 | 3 | context.put("DateFormat", DateFormat.getDateInstance(DateFormat.LONG)); |
| 167 | 3 | context.put("TimeFormat", DateFormat.getTimeInstance(DateFormat.LONG)); |
| 168 | 3 | context.put("DateTimeFormat", DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.FULL)); |
| 169 | 3 | String view = parameters.get("view"); |
| 170 | 3 | if (view == null) { |
| 171 | 3 | if (file.exists()) view = (String) file.getContent().getAttribute("listing-view"); |
| 172 | 3 | if (view == null) { |
| 173 | 3 | view = (String) pc.getAttribute("listing-view"); |
| 174 | 3 | if (view == null) view = "Details"; |
| 175 | |
} |
| 176 | |
} |
| 177 | 3 | context.put("View", view); |
| 178 | |
|
| 179 | 3 | Iterator<Map.Entry<String, String>> it = parameters.entrySet().iterator(); |
| 180 | 3 | while (it.hasNext()) { |
| 181 | 0 | Map.Entry<String, String> entry = it.next(); |
| 182 | 0 | String key = entry.getKey(); |
| 183 | 0 | if (!key.startsWith("Action.")) continue; |
| 184 | 0 | StringTokenizer st = new StringTokenizer(key.substring(7), "."); |
| 185 | 0 | if (st.hasMoreTokens()) { |
| 186 | 0 | String whichName = st.nextToken(); |
| 187 | |
CachingLister actionLister; |
| 188 | 0 | if ("Global".equals(whichName)) { |
| 189 | 0 | actionLister = config.globalActions; |
| 190 | 0 | } else if ("File".equals(whichName)) { |
| 191 | 0 | actionLister = config.fileActions; |
| 192 | |
} else { |
| 193 | |
continue; |
| 194 | |
} |
| 195 | 0 | if (actionLister != null && st.hasMoreTokens()) { |
| 196 | 0 | String action = st.nextToken(); |
| 197 | 0 | FileObject actionFile = actionLister.getDir().resolveFile(action); |
| 198 | 0 | context.put("Name", entry.getValue()); |
| 199 | 0 | String argument = key.substring(7 + whichName.length() + 1 + action.length()); |
| 200 | 0 | if (st.hasMoreTokens()) argument = argument.substring(1); |
| 201 | 0 | context.put("ActionArgument", argument); |
| 202 | 0 | webslinger.event(webslinger.resolvePath(actionFile), context, "action", allParameters); |
| 203 | 0 | refresh(); |
| 204 | |
} |
| 205 | |
} |
| 206 | 0 | } |
| 207 | |
|
| 208 | 3 | Map<Comparator, Map<String, Map<String, Object>>> sorted = getObject(); |
| 209 | 3 | if (sorted == null) { |
| 210 | 0 | webslinger.forward(wsc.resolve(file)); |
| 211 | 0 | return; |
| 212 | |
} |
| 213 | |
Map<String, Map<String, Object>> files; |
| 214 | 3 | String orderBy = parameters.get("orderBy"); |
| 215 | |
boolean nameDirection, timeDirection, sizeDirection, descending; |
| 216 | 3 | nameDirection = timeDirection = sizeDirection = descending = "desc".equals(parameters.get("direction")); |
| 217 | 3 | ArrayList<String> qParams = new ArrayList<String>(); |
| 218 | 3 | if ("name".equals(orderBy)) { |
| 219 | 0 | nameDirection = !descending; |
| 220 | 0 | files = sorted.get(new FileSorter("name", descending)); |
| 221 | 0 | qParams.add("orderBy=name"); |
| 222 | 3 | } else if ("time".equals(orderBy)) { |
| 223 | 0 | timeDirection = !descending; |
| 224 | 0 | files = sorted.get(new FileSorter("time", descending)); |
| 225 | 0 | qParams.add("orderBy=time"); |
| 226 | 3 | } else if ("size".equals(orderBy)) { |
| 227 | 0 | sizeDirection = !descending; |
| 228 | 0 | files = sorted.get(new FileSorter("size", descending)); |
| 229 | 0 | qParams.add("orderBy=size"); |
| 230 | |
} else { |
| 231 | 3 | files = sorted.get(DEFAULT_COMPARATOR); |
| 232 | |
} |
| 233 | 3 | qParams.add("desc=" + (descending ? "desc" : "asc")); |
| 234 | |
|
| 235 | 3 | context.put("ListingFiles", files); |
| 236 | 3 | context.put("ListingFilesList", new ArrayList<Map<String, Object>>(files.values())); |
| 237 | 3 | context.put("orderBy", orderBy); |
| 238 | 3 | context.put("direction", descending ? "desc" : "asc"); |
| 239 | 3 | context.put("nameDirection", nameDirection ? "desc" : "asc"); |
| 240 | 3 | context.put("timeDirection", timeDirection ? "desc" : "asc"); |
| 241 | 3 | context.put("sizeDirection", sizeDirection ? "desc" : "asc"); |
| 242 | |
|
| 243 | 3 | webslinger.merge(wsc.resolve(config.views.getDir()), context); |
| 244 | 3 | } |
| 245 | |
|
| 246 | |
public static class OutputConfig { |
| 247 | |
public final CachingLister views; |
| 248 | |
public final CachingLister globalActions; |
| 249 | |
public final CachingLister fileActions; |
| 250 | |
|
| 251 | 3 | public OutputConfig(CachingLister views, CachingLister globalActions, CachingLister fileActions) { |
| 252 | 3 | this.views = views; |
| 253 | 3 | this.globalActions = globalActions; |
| 254 | 3 | this.fileActions = fileActions; |
| 255 | 3 | } |
| 256 | |
|
| 257 | |
public CachingLister getViews() { |
| 258 | 3 | return views; |
| 259 | |
} |
| 260 | |
|
| 261 | |
public CachingLister getGlobalActions() { |
| 262 | 3 | return globalActions; |
| 263 | |
} |
| 264 | |
|
| 265 | |
public CachingLister getFileActions() { |
| 266 | 3 | return fileActions; |
| 267 | |
} |
| 268 | |
} |
| 269 | |
|
| 270 | 3 | public static class StandardThemingOutputConfig implements ThemingFactory<OutputConfig> { |
| 271 | |
private final FileObject root; |
| 272 | |
private final String viewsDir; |
| 273 | |
private final String globalActionsDir; |
| 274 | |
private final String fileActionsDir; |
| 275 | |
|
| 276 | 1 | public StandardThemingOutputConfig(FileObject root, String viewsDir, String globalActionsDir, String fileActionsDir) { |
| 277 | 1 | this.root = root; |
| 278 | 1 | this.viewsDir = viewsDir; |
| 279 | 1 | this.globalActionsDir = globalActionsDir; |
| 280 | 1 | this.fileActionsDir = fileActionsDir; |
| 281 | 1 | } |
| 282 | |
|
| 283 | |
public OutputConfig getThemedObject(String themePath) throws IOException { |
| 284 | 3 | return makeOutputConfig(root.resolveFile(themePath), viewsDir, globalActionsDir, fileActionsDir); |
| 285 | |
} |
| 286 | |
} |
| 287 | |
|
| 288 | |
public static OutputConfig makeOutputConfig(FileObject base, String viewsDir, String globalActionsDir, String fileActionsDir) throws IOException { |
| 289 | 3 | return new OutputConfig( |
| 290 | |
new ReloadingCachingLister(base.resolveFile(viewsDir)), |
| 291 | |
new ReloadingCachingLister(base.resolveFile(globalActionsDir)), |
| 292 | |
new ReloadingCachingLister(base.resolveFile(fileActionsDir)) |
| 293 | |
); |
| 294 | |
} |
| 295 | |
} |