001: // CvsDAVFileFrame.java
002: // $Id: CvsDAVFileFrame.java,v 1.5 2007/02/11 10:52:15 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigedit.webdav.frames;
007:
008: import java.io.File;
009: import java.io.IOException;
010: import java.io.PrintStream;
011:
012: import org.w3c.tools.resources.Attribute;
013: import org.w3c.tools.resources.AttributeHolder;
014: import org.w3c.tools.resources.AttributeRegistry;
015: import org.w3c.tools.resources.BooleanAttribute;
016: import org.w3c.tools.resources.DirectoryResource;
017: import org.w3c.tools.resources.FileResource;
018: import org.w3c.tools.resources.FramedResource;
019: import org.w3c.tools.resources.InvalidResourceException;
020: import org.w3c.tools.resources.MultipleLockException;
021: import org.w3c.tools.resources.ProtocolException;
022: import org.w3c.tools.resources.ReplyInterface;
023: import org.w3c.tools.resources.RequestInterface;
024: import org.w3c.tools.resources.Resource;
025: import org.w3c.tools.resources.ResourceException;
026: import org.w3c.tools.resources.ResourceFrame;
027: import org.w3c.tools.resources.ResourceReference;
028: import org.w3c.tools.resources.ServerInterface;
029:
030: import org.w3c.jigsaw.webdav.DAVFrame;
031: import org.w3c.jigsaw.webdav.DAVRequest;
032:
033: import org.w3c.jigsaw.http.HTTPException;
034: import org.w3c.jigsaw.http.Reply;
035: import org.w3c.jigsaw.http.Request;
036:
037: import org.w3c.jigsaw.auth.AuthFilter;
038:
039: import org.w3c.www.http.HTTP;
040: import org.w3c.www.http.HttpEntityMessage;
041: import org.w3c.www.http.HttpMessage;
042: import org.w3c.www.http.HttpReplyMessage;
043: import org.w3c.www.http.HttpRequestMessage;
044:
045: import org.w3c.www.mime.MimeType;
046:
047: import org.w3c.jigsaw.html.HtmlGenerator;
048:
049: import org.w3c.cvs.CVS;
050: import org.w3c.cvs.CvsAddException;
051: import org.w3c.cvs.CvsDirectory;
052: import org.w3c.cvs.CvsException;
053: import org.w3c.cvs.UpToDateCheckFailedException;
054:
055: import org.w3c.jigedit.cvs.CvsFrame;
056: import org.w3c.jigedit.cvs.CvsModule;
057:
058: /**
059: * This subclass of HTTPFrame check cvs before performing a PUT request.
060: * If a CVS directory exists<BR>
061: * <ul>
062: * If the resource file exists<BR>
063: * <ul>
064: * If resource file not up to date Fail.<BR>
065: * Else perform PUT and commit it into cvs.<BR>
066: * </ul>
067: * Else perform PUT, add and commit it into cvs.<BR>
068: * </ul>
069: * Else perform PUT.
070: * @author Benoit Mahe <bmahe@sophia.inria.fr>
071: */
072:
073: public class CvsDAVFileFrame extends DAVFrame {
074:
075: public static final boolean debug = true;
076:
077: private final Object monitor = new Object();
078:
079: /**
080: * Attribute index, tell if we must update the resource everytime it is
081: * acceded (not recommended as it generates many cvs commands)
082: */
083: private static int ATTR_AUTOUPDATE = -1;
084:
085: static {
086: Attribute a = null;
087: Class cls = null;
088:
089: try {
090: cls = Class
091: .forName("org.w3c.jigedit.webdav.frames.CvsDAVFileFrame");
092: } catch (Exception ex) {
093: ex.printStackTrace();
094: System.exit(1);
095: }
096: // The browsable flag:
097: a = new BooleanAttribute("autoupdate", Boolean.FALSE,
098: Attribute.EDITABLE);
099: ATTR_AUTOUPDATE = AttributeRegistry.registerAttribute(cls, a);
100: }
101:
102: protected static Reply error(Request request, int status,
103: String title, String msg) {
104: Reply error = request.makeReply(status);
105: HtmlGenerator g = CvsFrame.getHtmlGenerator(title);
106: g.append("<span class=\"title\">", title, "</span>\n");
107: g.append("<p>", msg);
108: error.setStream(g);
109: return error;
110: }
111:
112: /**
113: * tell if we must always do an update.
114: */
115: public boolean isAutoUpdatable() {
116: return getBoolean(ATTR_AUTOUPDATE, false);
117: }
118:
119: protected File resDirectory = null;
120:
121: protected synchronized File getResourceDirectory() {
122: if (resDirectory == null) {
123: ResourceReference rr = getFileResource().getParent();
124: ResourceReference rrtemp = null;
125: Resource p = null;
126: while (true) {
127: try {
128: if (rr == null)
129: return null;
130: p = rr.lock();
131: if (p instanceof DirectoryResource) {
132: resDirectory = ((DirectoryResource) p)
133: .getDirectory();
134: break;
135: }
136: rrtemp = p.getParent();
137: } catch (InvalidResourceException ex) {
138: return null;
139: } finally {
140: if (rr != null)
141: rr.unlock();
142: }
143: rr = rrtemp;
144: }
145: }
146: return resDirectory;
147: }
148:
149: protected synchronized CvsDirectory getCvsManager()
150: throws CvsException {
151: return CvsModule.getCvsManager(getResourceDirectory(),
152: getContext(), getServer().getProperties());
153: }
154:
155: protected boolean checkCvsManager() {
156: try {
157: return (getCvsManager() != null);
158: } catch (CvsException ex) {
159: return false;
160: }
161: }
162:
163: /**
164: * Get this resource Etag string, it will be computed using
165: * FileETag MTime Size directive like in Apache, this will fit some
166: * needs for our own server farm, it won't hurt anyway/
167: * @return a string or null if not applicable
168: */
169: public String computeETag() {
170: String etag_s = null;
171: if (fresource != null) {
172: long lstamp = fresource.getFileStamp();
173: long lsize = (long) fresource.getFileLength();
174: if (lstamp >= 0L) {
175: etag_s = Long.toHexString(lsize) + "-"
176: + Long.toHexString(lstamp);
177: }
178: }
179: return etag_s;
180: }
181:
182: /**
183: * @exception CvsException if the CVS process failed
184: */
185: protected void add(Request request) throws CvsException {
186: String u = (String) request.getState(AuthFilter.STATE_AUTHUSER);
187: String env[] = { "USER=" + u, "LOGNAME=" + u };
188: String names[] = null;
189: MimeType mtype;
190: mtype = request.getContentType();
191: if (mtype != null
192: && (mtype.match(MimeType.TEXT) != MimeType.MATCH_SUBTYPE)
193: && (mtype.match(MimeType.APPLICATION_XHTML_XML) != MimeType.MATCH_SPECIFIC_SUBTYPE)) {
194: names = new String[2];
195: names[0] = "-kb";
196: names[1] = getFileResource().getFile().getName();
197: } else {
198: names = new String[1];
199: names[0] = getFileResource().getFile().getName();
200: }
201: CvsDirectory cvsdir = null;
202: cvsdir = getCvsManager();
203: cvsdir.add(names, env);
204: }
205:
206: /**
207: * @exception CvsException if the CVS process failed
208: */
209: protected void commit(Request request) throws CvsException {
210: commit(request, "Changed through Jigsaw.");
211: }
212:
213: /**
214: * @exception CvsException if the CVS process failed
215: */
216: protected void commit(Request request, String msg)
217: throws CvsException {
218: String u = (String) request.getState(AuthFilter.STATE_AUTHUSER);
219: String env[] = { "USER=" + u, "LOGNAME=" + u };
220: String comment = "(" + u + ") " + msg;
221: CvsDirectory cvsdir = null;
222: cvsdir = getCvsManager();
223: cvsdir.commit(getFileResource().getFile().getName(), comment,
224: env);
225: }
226:
227: /**
228: * @exception CvsException if the CVS process failed
229: */
230: protected void update() throws CvsException {
231: CvsDirectory cvsdir = getCvsManager();
232: cvsdir.update(getFileResource().getFile().getName());
233: }
234:
235: /**
236: * @exception CvsException if the CVS process failed
237: */
238: protected int status() throws CvsException {
239: CvsDirectory cvsdir = getCvsManager();
240: return cvsdir.status(getFileResource().getFile().getName());
241: }
242:
243: protected String statusToString(int status) {
244: return CvsDirectory.statusToString(status);
245: }
246:
247: protected File getBackupFile() {
248: File file = getFileResource().getFile();
249: return new File(file.getParent(), file.getName() + ".bak");
250: }
251:
252: /**
253: * Change the content of the associated FileResource.
254: * +cvs action (commit, update)
255: * @param request The incomming request.
256: * @exception org.w3c.tools.resources.ProtocolException
257: * if a protocol error occurs
258: * @exception org.w3c.tools.resources.ResourceException
259: * if a server error occurs
260: */
261: protected Reply putFileResource(Request request)
262: throws ProtocolException, ResourceException {
263: if (debug)
264: System.out.println("Put on "
265: + getResource().getIdentifier() + "...");
266: if (getResourceDirectory() != null) {
267: if (checkCvsManager()) {
268: File resfile = getFileResource().getFile();
269: int cvs_status = -1;
270: try {
271: cvs_status = status();
272: } catch (CvsException ex) {
273: //file not on disk yet, let's try to add this file..
274: cvs_status = CVS.FILE_Q;
275: }
276: if (debug)
277: System.out
278: .println("... with a cvs status equals to : "
279: + statusToString(cvs_status));
280: if (cvs_status == CVS.FILE_C) {
281: String msg = "Conflict between working "
282: + "revision and repository revision.<br> "
283: + "Please update the file manually or use the CVS form.";
284: return error(request, HTTP.CONFLICT, "Conflict",
285: msg);
286: } else if (cvs_status == CVS.FILE_NCO) {
287: String msg = "File already in the repository "
288: + "(added independently by second party).<br>"
289: + "Please update the file manually or use the CVS form.";
290: return error(request, HTTP.CONFLICT, "Conflict",
291: msg);
292: } else if (resfile.exists()
293: && (cvs_status != CVS.FILE_Q)) {
294: File backup = getBackupFile();
295: try {
296: if (cvs_status == CVS.FILE_OK) {
297: // Write the file to web space
298: //save the current file
299: try {
300: org.w3c.util.IO.copy(resfile, backup);
301: } catch (IOException ex) {
302: //not exactly what we want but, it ~works
303: backup.delete();
304: resfile.renameTo(backup);
305: }
306: //perform the put
307: Reply r = super .putFileResource(request);
308: int rstatus = r.getStatus();
309: // if OK
310: if ((rstatus / 100) == 2) {
311: // Do a CVS commit with user name
312: commit(request);
313: }
314: //well done, remove the backup file now
315: backup.delete();
316: // update the file attrs
317: getFileResource().checkContent();
318: //return the reply.
319: if ((rstatus / 100) == 2) {
320: return createDefaultReply(request, r
321: .getStatus());
322: } else {
323: return r;
324: }
325: } else {
326: // fail
327: String msg = "File is not up to date, "
328: + "please update the file manually "
329: + "or use the CVS form.";
330: return error(request, HTTP.CONFLICT,
331: "Error", msg);
332: }
333: } catch (UpToDateCheckFailedException utd_ex) {
334: resfile.delete();
335: //restore the backup file
336: backup.renameTo(resfile);
337: //send error
338: String msg = utd_ex.getFilename()
339: + " is not up to date, "
340: + "please update the file manually "
341: + "or use the CVS form.";
342: return error(request, HTTP.CONFLICT, "Error",
343: msg);
344: } catch (CvsException ex) {
345: backup.delete();
346: // fail too
347: String msg = "CvsException : "
348: + ex.getMessage();
349: getServer().errlog(
350: getIdentifier() + " : "
351: + ex.getMessage());
352: return error(request, HTTP.SERVICE_UNAVAILABLE,
353: "Error", msg);
354: }
355: } else {
356: // Write the file to web space
357: Reply r = super .putFileResource(request);
358: try {
359: // Do a CVS add with user name
360: add(request);
361: // Do a CVS commit with user name
362: commit(request);
363: getFileResource().checkContent();
364: } catch (CvsAddException ex) {
365: String msg = "Cvs add failed : <br>'"
366: + ex.getMessage()
367: + "'<br>please update the file manually "
368: + "or use the CVS form";
369: return error(request, HTTP.SERVICE_UNAVAILABLE,
370: "Error", msg);
371: } catch (CvsException ex) {
372: ex.printStackTrace();
373: String msg = "Problem during cvs process : "
374: + ex.getMessage();
375: getServer().errlog(
376: getIdentifier() + " : " + msg);
377: return error(request,
378: HTTP.INTERNAL_SERVER_ERROR,
379: "Internal Server Error", msg);
380: }
381: if (r.getStatus() == HTTP.CREATED) {
382: Reply reply = request.makeReply(r.getStatus());
383: reply
384: .setContent("<P>Resource succesfully created");
385: if (request.hasState(STATE_CONTENT_LOCATION))
386: reply.setContentLocation(getURL(request)
387: .toExternalForm());
388: r = createDefaultReply(request, HTTP.CREATED);
389: reply.setETag(r.getETag());
390: return reply;
391: }
392: return createDefaultReply(request, r.getStatus());
393: }
394: } else {
395: return super .putFileResource(request);
396: }
397: } else {
398: String msg = "Server misconfigured : "
399: + "unable to find resource directory";
400: getServer().errlog(getIdentifier() + " : " + msg);
401: return error(request, HTTP.INTERNAL_SERVER_ERROR,
402: "Internal Server Error", msg);
403: }
404: }
405:
406: /**
407: * The DELETE method delete the file and perform a cvs remove.
408: * @param request The request to handle.
409: * @exception ProtocolException if a protocol error occurs
410: * @exception ResourceException If the resource got a fatal error.
411: */
412:
413: protected synchronized Reply deleteFileResource(Request request)
414: throws ProtocolException, ResourceException {
415: if (fresource == null) {
416: String msg = "Can't delete resource: "
417: + resource.getIdentifier()
418: + " is not associated with a FILE. Try again later.";
419: Reply error = error(request, HTTP.FORBIDDEN, "Error", msg);
420: throw new HTTPException(error);
421: }
422: //Get the user name
423: String u = (String) request.getState(AuthFilter.STATE_AUTHUSER);
424: String env[] = { "USER=" + u, "LOGNAME=" + u };
425: //delete the file
426: fresource.getFile().delete();
427: //cvs remove
428: String names[] = new String[1];
429:
430: names[0] = fresource.getFilename();
431: if (names[0] == null)
432: names[0] = fresource.getIdentifier();
433: try {
434: if (getCvsManager().status(names[0]) != CVS.FILE_Q) {
435: getCvsManager().remove(names,
436: "Deleted via HTTP delete method.", env);
437: }
438: } catch (CvsException cvs_ex) {
439: String msg = "Cvs remove failed : " + cvs_ex.getMessage();
440: getServer().errlog(this , msg);
441: }
442: try {
443: //delete the FileResource
444: fresource.delete();
445: } catch (MultipleLockException ex) {
446: String msg = "Can't delete resource: "
447: + resource.getIdentifier()
448: + " is locked. Try again later.";
449: Reply error = error(request, HTTP.FORBIDDEN, "Error", msg);
450: throw new HTTPException(error);
451: }
452: //well done
453: return request.makeReply(HTTP.NO_CONTENT);
454: }
455:
456: /**
457: * The WEBDAV DELETE method, actually the resource (file, directory)
458: * is moved into the trash directory which is not accessible via HTTP.
459: * @param request The request to handle.
460: * @exception ProtocolException If processsing the request failed.
461: * @exception ResourceException If the resource got a fatal error.
462: */
463:
464: public Reply delete(Request request) throws ProtocolException,
465: ResourceException {
466: synchronized (monitor) {
467: return super .delete(request);
468: }
469: }
470:
471: /**
472: * The WEBDAV PUT method.
473: * @param request The request to handle.
474: * @exception ProtocolException If processsing the request failed.
475: * @exception ResourceException If the resource got a fatal error.
476: */
477:
478: public Reply put(Request request) throws ProtocolException,
479: ResourceException {
480: synchronized (monitor) {
481: return super .put(request);
482: }
483: }
484:
485: /**
486: * Perform a cvs update before perform a GET,HEAD,POST request.
487: * @param req The request to handle.
488: * @exception ProtocolException If processsing the request failed.
489: * @exception ResourceException If the resource got a fatal error.
490: */
491:
492: public ReplyInterface perform(RequestInterface req)
493: throws ProtocolException, ResourceException {
494: if (!checkRequest(req))
495: return null;
496:
497: String method = ((Request) req).getMethod();
498: if ((method.equals("GET") || method.equals("HEAD")
499: || method.equals("POST") || method.equals("PUT"))
500: && isAutoUpdatable() && (fresource != null)) {
501:
502: try {
503: update();
504: } catch (CvsException ex) {
505: String msg = "cvs update \""
506: + getFileResource().getFile().getName()
507: + "\" failed.";
508: getServer().errlog(this, msg);
509: }
510: }
511: return super.perform(req);
512: }
513: }
|