Coverage Report - org.webslinger.modules.edit.Editor
 
Classes in this File Line Coverage Branch Coverage Complexity
Editor
55%
49/89
64%
7/11
0
Editor$Context
63%
87/138
74%
14/19
0
Editor$Request
79%
87/110
97%
28/29
0
 
 1  
 package org.webslinger.modules.edit;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.io.Serializable;
 5  
 import java.util.ArrayList;
 6  
 import java.util.Collections;
 7  
 import java.util.Enumeration;
 8  
 import java.util.HashMap;
 9  
 import java.util.HashSet;
 10  
 import java.util.Iterator;
 11  
 import java.util.List;
 12  
 import java.util.Map;
 13  
 import java.util.Set;
 14  
 import java.util.TreeMap;
 15  
 import java.util.logging.Logger;
 16  
 import javax.servlet.http.HttpServletRequest;
 17  
 import javax.servlet.http.HttpSession;
 18  
 import javax.servlet.http.HttpSessionBindingEvent;
 19  
 import javax.servlet.http.HttpSessionBindingListener;
 20  
 
 21  
 import org.apache.commons.fileupload.FileItem;
 22  
 import org.apache.commons.lang.StringUtils;
 23  
 import org.apache.commons.vfs.FileContent;
 24  
 import org.apache.commons.vfs.FileObject;
 25  
 
 26  
 import org.webslinger.Webslinger;
 27  
 import org.webslinger.WebslingerServletContext;
 28  
 import org.webslinger.container.CommonsVfsFileInfo;
 29  
 import org.webslinger.container.FileInfo;
 30  
 import org.webslinger.io.Charsets;
 31  
 import org.webslinger.io.IOUtil;
 32  
 import static org.webslinger.servlet.MultipartFormUtil.parseRequestAsList;
 33  
 import static org.webslinger.servlet.MultipartFormUtil.createFileItem;
 34  
 
 35  11
 public class Editor implements HttpSessionBindingListener, Serializable {
 36  12
     private static final Logger logger = Logger.getLogger(Editor.class.getName());
 37  
     protected final WebslingerServletContext servletContext;
 38  
     protected final FileObject base;
 39  
     protected final String prefix;
 40  12
     protected final HashMap contexts = new HashMap();
 41  
 
 42  12
     public Editor(WebslingerServletContext servletContext, FileObject base, String prefix) {
 43  12
         this.servletContext = servletContext;
 44  12
         this.base = base;
 45  12
         this.prefix = prefix;
 46  12
     }
 47  
 
 48  
     public void valueBound(HttpSessionBindingEvent event) {
 49  0
     }
 50  
 
 51  
     public void valueUnbound(HttpSessionBindingEvent event) {
 52  0
         Iterator it = contexts.values().iterator();
 53  0
         while (it.hasNext()) {
 54  0
             Context context = (Context) it.next();
 55  0
             context.clear();
 56  0
         }
 57  0
         contexts.clear();
 58  0
     }
 59  
 
 60  
     public static Editor getEditor(Webslinger webslinger) throws IOException {
 61  23
         return getEditor(webslinger, webslinger.getWebslingerContainer().getRoot(), "www/");
 62  
     }
 63  
 
 64  
     public static Editor getEditor(Webslinger webslinger, FileObject root, String prefix) throws IOException {
 65  23
         HttpServletRequest request = webslinger.getRequest();
 66  23
         HttpSession session = request.getSession(true);
 67  23
         if (session == null) return new Editor(webslinger.getWebslingerServletContext(), root, prefix);
 68  
         Map editorsByRoot;
 69  23
         synchronized (session) {
 70  23
             editorsByRoot = (Map) session.getAttribute(Editor.class.getName());
 71  23
             if (editorsByRoot == null) {
 72  12
                 editorsByRoot = new HashMap();
 73  12
                 session.setAttribute(Editor.class.getName(), editorsByRoot);
 74  
             }
 75  23
         }
 76  
         Map editorByPrefix;
 77  23
         synchronized (editorsByRoot) {
 78  23
             editorByPrefix = (Map) editorsByRoot.get(root);
 79  23
             if (editorByPrefix == null) {
 80  12
                 editorByPrefix = new HashMap();
 81  12
                 editorsByRoot.put(root, editorByPrefix);
 82  
             }
 83  23
         }
 84  23
         synchronized (editorByPrefix) {
 85  23
             Editor data = (Editor) editorByPrefix.get(prefix);
 86  23
             if (data != null) return data;
 87  12
             data = new Editor(webslinger.getWebslingerServletContext(), root, prefix);
 88  12
             editorByPrefix.put(prefix, data);
 89  12
             return data;
 90  0
         }
 91  
     }
 92  
 
 93  
     public class Request {
 94  
         protected final List items;
 95  
         public final Context context;
 96  
 
 97  23
         public Request(String path, List items) throws EditorException {
 98  23
             this.items = items;
 99  23
             Iterator it = items.iterator();
 100  94
             while (it.hasNext()) {
 101  71
                 FileItem item = (FileItem) it.next();
 102  71
                 String name = item.getFieldName();
 103  71
                 if (!name.equals("path")) continue;
 104  
                 try {
 105  0
                     path = IOUtil.readString(item.getInputStream(), Charsets.UTF8);
 106  0
                     item.delete();
 107  0
                     it.remove();
 108  0
                 } catch (IOException e) {
 109  0
                     throw new EditorException("Couldn't process parameter(path)", e);
 110  0
                 }
 111  0
             }
 112  23
             context = Editor.this.getContext(path);
 113  23
         }
 114  
 
 115  
         public Request(String path, HttpServletRequest request) throws EditorException, IOException {
 116  23
             this(path, parseRequestAsList(request));
 117  23
         }
 118  
 
 119  
         public void action_deleteAttributes(List errors) throws EditorException {
 120  23
             Iterator it = items.iterator();
 121  94
             while (it.hasNext()) {
 122  71
                 FileItem item = (FileItem) it.next();
 123  71
                 String name = item.getFieldName();
 124  
                 try {
 125  71
                     if (name.startsWith("delete.")) {
 126  2
                         context.deleteAttribute(name.substring(7));
 127  69
                     } else if (name.equals("deleted")) {
 128  1
                         context.deleteAttribute(IOUtil.readString(item.getInputStream(), Charsets.UTF8));
 129  
                     } else {
 130  68
                         continue;
 131  
                     }
 132  3
                     item.delete();
 133  0
                 } catch (EditorException e) {
 134  0
                     errors.add(e);
 135  0
                 } catch (IOException e) {
 136  0
                     errors.add(new EditorException("Couldn't process parameter(" + name + ")", e));
 137  3
                 }
 138  3
             }
 139  23
         }
 140  
 
 141  
         public void action_setAttributes(List errors) throws EditorException {
 142  23
             Iterator it = items.iterator();
 143  23
             String attrName = null, attrValue = null;
 144  23
             boolean doAdd = false;
 145  94
             while (it.hasNext()) {
 146  71
                 FileItem item = (FileItem) it.next();
 147  71
                 String name = item.getFieldName();
 148  
                 try {
 149  71
                     if (name.equals("add")) {
 150  1
                         doAdd = true;
 151  70
                     } else if (name.startsWith("attr.")) {
 152  13
                         context.setAttribute(name.substring(5), IOUtil.readString(item.getInputStream(), Charsets.UTF8));
 153  57
                     } else if (name.equals("attrName")) {
 154  11
                         attrName = IOUtil.readString(item.getInputStream(), Charsets.UTF8);
 155  46
                     } else if (name.equals("attrValue")) {
 156  11
                         attrValue = IOUtil.readString(item.getInputStream(), Charsets.UTF8);
 157  
                     } else {
 158  35
                         continue;
 159  
                     }
 160  36
                     item.delete();
 161  0
                 } catch (EditorException e) {
 162  0
                     errors.add(e);
 163  0
                 } catch (IOException e) {
 164  0
                     errors.add(new EditorException("Couldn't process parameter(" + name + ")", e));
 165  36
                 }
 166  36
             }
 167  23
             if (doAdd) {
 168  1
                 if (attrName == null) {
 169  0
                     errors.add(new EditorException("Couldn't add attribute, name is null"));
 170  1
                 } else if (attrValue == null) {
 171  0
                     errors.add(new EditorException("Couldn't add attribute(" + attrName + "), value is null"));
 172  
                 } else {
 173  1
                     context.setAttribute(attrName, attrValue);
 174  
                 }
 175  
             }
 176  23
         }
 177  
 
 178  
         protected FileItem checkItem(FileItem oldItem, FileItem newItem) {
 179  0
             if (oldItem != null) oldItem.delete();
 180  0
             return newItem;
 181  
         }
 182  
 
 183  
         public void action_content(List errors) throws EditorException {
 184  23
             Iterator it = items.iterator();
 185  23
             FileItem uploadedFile = null;
 186  23
             boolean doUpload = false;
 187  94
             while (it.hasNext()) {
 188  71
                 FileItem item = (FileItem) it.next();
 189  71
                 String name = item.getFieldName();
 190  71
                 if (name.equals("content")) {
 191  11
                     context.setContent(item);
 192  60
                 } else if (name.equals("file")) {
 193  4
                     if (uploadedFile != null) uploadedFile.delete();
 194  4
                     uploadedFile = item;
 195  56
                 } else if (name.equals("upload")) {
 196  4
                     doUpload = true;
 197  
                 } else {
 198  
                     continue;
 199  
                 }
 200  19
                 it.remove();
 201  19
             }
 202  23
             if (doUpload) {
 203  4
                 context.setContent(uploadedFile);
 204  4
                 context.setAttribute("content-type", uploadedFile.getContentType());
 205  
             }
 206  23
         }
 207  
 
 208  
         protected String checkCommand(String command, String newCommand) throws EditorException {
 209  13
             if (command == null) return newCommand;
 210  0
             throw new EditorException("Tried to override command(" + command + ") with (" + newCommand + ")");
 211  
         }
 212  
 
 213  
         public void action_command(List errors) throws EditorException {
 214  23
             Iterator it = items.iterator();
 215  23
             String command = null;
 216  75
             while (it.hasNext()) {
 217  52
                 FileItem item = (FileItem) it.next();
 218  52
                 String name = item.getFieldName();
 219  52
                 if (name.startsWith("cmd.")) {
 220  0
                     command = checkCommand(command, name.substring(4));
 221  52
                 } else if (name.equals("delete")) {
 222  2
                     command = checkCommand(command, "delete");
 223  50
                 } else if (name.equals("revert")) {
 224  0
                     command = checkCommand(command, "revert");
 225  50
                 } else if (name.equals("save")) {
 226  11
                     command = checkCommand(command, "save");
 227  
                 } else {
 228  
                     continue;
 229  
                 }
 230  13
                 item.delete();
 231  13
                 it.remove();
 232  13
             }
 233  23
             if ("delete".equals(command)) {
 234  2
                 context.delete();
 235  21
             } else if ("revert".equals(command)) {
 236  0
                 context.revert();
 237  21
             } else if ("save".equals(command)) {
 238  11
                 context.save();
 239  
             } else {
 240  
             }
 241  23
         }
 242  
     }
 243  
 
 244  
     public Request parseRequest(String path, HttpServletRequest request) throws EditorException {
 245  
         try {
 246  23
             return new Request(path, request);
 247  0
         } catch (IOException e) {
 248  0
             throw new EditorException("Couldn't parse request", e);
 249  
         }
 250  
     }
 251  
 
 252  
     public Request parseRequest(String path, List items) throws EditorException {
 253  0
         return new Request(path, items);
 254  
     }
 255  
 
 256  
     public String processRequest(String path, HttpServletRequest request) throws EditorException {
 257  0
         Request req = parseRequest(path, request);
 258  0
         ArrayList errors = new ArrayList();
 259  0
         processRequest(req, errors);
 260  0
         return req.context.filePath;
 261  
     }
 262  
 
 263  
     public void processRequest(Request request, List errors) throws EditorException {
 264  23
         request.action_setAttributes(errors);
 265  23
         request.action_deleteAttributes(errors);
 266  23
         request.action_content(errors);
 267  23
         request.action_command(errors);
 268  23
     }
 269  
 
 270  
     protected Context getContext(String path) throws EditorException {
 271  23
         synchronized (contexts) {
 272  23
             Context context = (Context) contexts.get(path);
 273  23
             if (context != null) return context;
 274  12
             context = new Context(path);
 275  12
             contexts.put(path, context);
 276  12
             return context;
 277  0
         }
 278  
     }
 279  
 
 280  
     public String getDefaultExtension() throws IOException {
 281  0
         return (String) servletContext.getConfigItem("Edit.DefaultExtension");
 282  
     }
 283  
 
 284  
     public String getDefaultEditor() throws IOException {
 285  8
         return (String) servletContext.getConfigItem("Edit.DefaultEditor");
 286  
     }
 287  
 
 288  
     protected FileInfo createFileInfo(String path) throws EditorException {
 289  
         try {
 290  82
             return new CommonsVfsFileInfo(servletContext.getContainer(), base.resolveFile(path));
 291  0
         } catch (IOException e) {
 292  0
             throw new EditorException("Couldn't get file info(" + path + ")", e);
 293  
         }
 294  
     }
 295  
 
 296  
     protected String findExtensionByMimeType(String contentType) throws EditorException {
 297  
         try {
 298  0
             return servletContext.getContainer().findExtensionByMimeType(contentType);
 299  0
         } catch (IOException e) {
 300  0
             throw new EditorException("Couldn't find extension for(" + contentType + ")", e);
 301  
         }
 302  
     }
 303  
 
 304  
     protected String[] getExtensionList() throws EditorException {
 305  
         try {
 306  11
             return servletContext.getContainer().getExtensionList();
 307  0
         } catch (IOException e) {
 308  0
             throw new EditorException("Couldn't get extension list", e);
 309  
         }
 310  
     }
 311  
 
 312  
     protected boolean checkForMigration(FileObject ptr) throws EditorException {
 313  
         try {
 314  11
             logger.fine("checkForMigration(" + ptr + ")");
 315  11
             String[] extensions = getExtensionList();
 316  11
             if (extensions == null || extensions.length == 0) return true;
 317  11
             if (ptr.exists() || ptr.getName().getPath().length() == 0) return false;
 318  0
             FileObject parent = ptr.getParent();
 319  0
             if (checkForMigration(parent)) return true;
 320  0
             String baseName = ptr.getName().getBaseName();
 321  0
             FileObject targetDir = parent.resolveFile(baseName);
 322  0
             targetDir.createFolder();
 323  0
             for (int i = 0; i < extensions.length; i++) {
 324  0
                 String extension = extensions[i];
 325  0
                 FileObject src = parent.resolveFile(ptr.getName().getBaseName() + extension);
 326  0
                 logger.finer("src=" + src);
 327  0
                 if (!src.exists()) continue;
 328  0
                 FileObject dest = targetDir.resolveFile("index" + extension);
 329  0
                 src.moveTo(dest);
 330  
             }
 331  0
             return true;
 332  0
         } catch (IOException e) {
 333  0
             throw new EditorException("Couldn't migrate(" + ptr.getName().getPath() + ")", e);
 334  
         }
 335  
     }
 336  
 
 337  
     public class Context implements Serializable {
 338  
         public final String filePath;
 339  12
         protected final HashMap originalAttributes = new HashMap();
 340  12
         protected final HashMap attributes = new HashMap();
 341  12
         protected final HashSet deletedAttributes = new HashSet();
 342  
         protected FileItem content;
 343  
         protected long lastModifiedTime;
 344  
         protected boolean exists;
 345  
 
 346  12
         protected Context(String filePath) throws EditorException {
 347  12
             this.filePath = filePath;
 348  12
             load();
 349  12
         }
 350  
 
 351  
         // Hooks for EditorContentContext
 352  
         FileItem getContent() {
 353  0
             return content;
 354  
         }
 355  
 
 356  
         public void setAttribute(String name, Object value) {
 357  18
             if (value == null) {
 358  0
                 deleteAttribute(name);
 359  0
                 return;
 360  
             }
 361  18
             attributes.put(name, value);
 362  18
             deletedAttributes.remove(name);
 363  18
         }
 364  
 
 365  
         public void setAllAttributes(Map toSet) {
 366  0
             Iterator it = toSet.entrySet().iterator();
 367  0
             while (it.hasNext()) {
 368  0
                 Map.Entry entry = (Map.Entry) it.next();
 369  0
                 setAttribute((String) entry.getKey(), entry.getValue());
 370  0
             }
 371  0
         }
 372  
 
 373  
         public void deleteAttribute(String name) {
 374  3
             attributes.remove(name);
 375  3
             deletedAttributes.add(name);
 376  3
         }
 377  
 
 378  
         public void deleteAllAttributes(Set toDelete) {
 379  0
             deletedAttributes.addAll(toDelete);
 380  0
         }
 381  
 
 382  
         public Set getDeletedAttributes() {
 383  23
             return Collections.unmodifiableSet(deletedAttributes);
 384  
         }
 385  
 
 386  
         public Object getAttribute(String name) {
 387  4
             if (deletedAttributes.contains(name)) return null;
 388  4
             return attributes.get(name);
 389  
         }
 390  
 
 391  
         public Map getAttributes() {
 392  23
             return Collections.unmodifiableMap(attributes);
 393  
         }
 394  
 
 395  
         public boolean exists() {
 396  0
             return exists;
 397  
         }
 398  
 
 399  
         public long getLastModifiedTime() {
 400  0
             return lastModifiedTime;
 401  
         }
 402  
 
 403  
         public String getString() throws EditorException {
 404  
             try {
 405  19
                 return content != null ? IOUtil.readString(content.getInputStream()) : null;
 406  0
             } catch (EditorException e) {
 407  0
                 throw e;
 408  0
             } catch (IOException e) {
 409  0
                 throw new EditorException("Couldn't get string from content(" + filePath + ")", e);
 410  
             }
 411  
         }
 412  
 
 413  
         public String getContentType() throws EditorException {
 414  4
             return (String) getAttribute("content-type");
 415  
         }
 416  
 
 417  
         public String getContentEncoding() throws EditorException {
 418  0
             return (String) getAttribute("content-encoding");
 419  
         }
 420  
 
 421  
         protected FileObject getFile() throws IOException {
 422  0
             return getFileInfo().getFile();
 423  
         }
 424  
 
 425  
         public void setContent(FileItem content) {
 426  15
             if (this.content != null) this.content.delete();
 427  15
             this.content = content;
 428  15
             lastModifiedTime = System.currentTimeMillis();
 429  15
         }
 430  
 
 431  
         public void clear() {
 432  13
             attributes.clear();
 433  13
             if (content != null) {
 434  13
                 content.delete();
 435  13
                 content = null;
 436  
             }
 437  13
             deletedAttributes.clear();
 438  13
             originalAttributes.clear();
 439  13
         }
 440  
 
 441  
         protected void load() throws EditorException {
 442  
             try {
 443  23
                 FileObject file = getFileInfo().getFile();
 444  23
                 if (file.exists()) {
 445  17
                     Iterator it = file.getContent().getAttributes().entrySet().iterator();
 446  29
                     while (it.hasNext()) {
 447  12
                         Map.Entry entry = (Map.Entry) it.next();
 448  12
                         originalAttributes.put((String) entry.getKey(), entry.getValue());
 449  12
                     }
 450  17
                     attributes.putAll(originalAttributes);
 451  17
                     content = createFileItem("content", file.getContent().getContentInfo().getContentType(), false, null);
 452  17
                     IOUtil.copy(file.getContent().getInputStream(), true, content.getOutputStream(), true);
 453  17
                     lastModifiedTime = file.getContent().getLastModifiedTime();
 454  17
                     exists = true;
 455  
                 }
 456  0
             } catch (EditorException e) {
 457  0
                 throw e;
 458  0
             } catch (IOException e) {
 459  0
                 throw new EditorException("Couldn't load content(" + filePath + ")", e);
 460  23
             }
 461  23
         }
 462  
 
 463  
         public void delete() throws EditorException {
 464  
             try {
 465  2
                 getFileInfo().getFile().delete();
 466  0
             } catch (EditorException e) {
 467  0
                 throw e;
 468  0
             } catch (IOException e) {
 469  0
                 throw new EditorException("Couldn't delete file(" + filePath + ")", e);
 470  2
             }
 471  2
             clear();
 472  2
         }
 473  
 
 474  
         public void revert() throws EditorException {
 475  0
             clear();
 476  0
             load();
 477  0
         }
 478  
 
 479  
         public void save() throws EditorException {
 480  
             try {
 481  11
                 logger.info(this + ".save()");
 482  11
                 FileInfo fileInfo = getFileInfo();
 483  11
                 FileObject file = fileInfo.getFile();
 484  11
                 Editor.this.checkForMigration(file.getParent());
 485  11
                 FileContent fileContent = file.getContent();
 486  11
                 IOUtil.copy(content.getInputStream(), true, fileContent.getOutputStream(), true);
 487  11
                 Iterator it = deletedAttributes.iterator();
 488  13
                 while (it.hasNext()) {
 489  2
                     String name = (String) it.next();
 490  2
                     if (fileContent.attributeExists(name)) {
 491  2
                         fileContent.removeAttribute(name);
 492  
                     } else {
 493  0
                         fileContent.setAttribute(name, null);
 494  
                     }
 495  2
                 }
 496  11
                 Map defaultAttributes = fileInfo.getDefaultAttributes();
 497  11
                 it = attributes.entrySet().iterator();
 498  21
                 while (it.hasNext()) {
 499  10
                     Map.Entry entry = (Map.Entry) it.next();
 500  10
                     String name = (String) entry.getKey();
 501  10
                     Object value = entry.getValue();
 502  10
                     if (value.equals(defaultAttributes.get(name))) {
 503  2
                         fileContent.removeAttribute(name);
 504  
                     } else {
 505  8
                         fileContent.setAttribute(name, value);
 506  
                     }
 507  10
                 }
 508  11
                 clear();
 509  11
                 load();
 510  0
             } catch (EditorException e) {
 511  0
                 throw e;
 512  0
             } catch (IOException e) {
 513  0
                 throw new EditorException("Couldn't save(" + filePath + ")", e);
 514  11
             }
 515  11
         }
 516  
 
 517  
         protected FileInfo getFileInfo() throws IOException {
 518  
             try {
 519  82
                 FileInfo fileInfo = Editor.this.createFileInfo(prefix + filePath);
 520  82
                 FileObject file = fileInfo.getFile();
 521  82
                 String contentType = (String) attributes.get("content-type");
 522  82
                 if (file.getName().getExtension().length() == 0) {
 523  
                     // no extension
 524  
                     String extension;
 525  0
                     if (contentType == null) contentType = (String) fileInfo.getAttribute("content-type");
 526  0
                     if (contentType != null) {
 527  0
                         extension = Editor.this.findExtensionByMimeType(contentType);
 528  0
                         if (extension == null) extension = Editor.this.getDefaultExtension();
 529  
                     } else {
 530  0
                         extension = Editor.this.getDefaultExtension();
 531  
                     }
 532  0
                     if (extension != null && extension.length() > 0) {
 533  0
                         file = file.getParent().resolveFile(file.getName().getBaseName() + '.' + extension);
 534  0
                         fileInfo = Editor.this.createFileInfo(file.getName().getPath());
 535  
                     }
 536  
                 }
 537  82
                 return fileInfo;
 538  0
             } catch (EditorException e) {
 539  0
                 throw e;
 540  0
             } catch (IOException e) {
 541  0
                 throw new EditorException("Couldn't find file info(" + filePath + ")", e);
 542  
             }
 543  
         }
 544  
 
 545  
         public String getType() throws EditorException {
 546  
             try {
 547  46
                 String type = (String) attributes.get("editor-type");
 548  46
                 if (type != null) return type;
 549  46
                 FileInfo fileInfo = getFileInfo();
 550  46
                 type = (String) fileInfo.getAttribute("editor-type");
 551  46
                 if (type != null) return type;
 552  8
                 return Editor.this.getDefaultEditor();
 553  0
             } catch (EditorException e) {
 554  0
                 throw e;
 555  0
             } catch (IOException e) {
 556  0
                 throw new EditorException("Couldn't find editor type(" + filePath + ")", e);
 557  
             }
 558  
         }
 559  
 
 560  
         public String toString() {
 561  11
             return "EditorContext(" + filePath + ")";
 562  
         }
 563  
     }
 564  
 }