001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.DateFormat; 007import java.text.ParseException; 008import java.text.SimpleDateFormat; 009import java.util.Date; 010import java.util.Locale; 011import java.util.regex.Matcher; 012import java.util.regex.Pattern; 013 014import org.openstreetmap.josm.Main; 015 016/** 017 * A ChangesetClosedException is thrown if the server replies with a HTTP 018 * return code 409 (Conflict) with the error header {@link #ERROR_HEADER_PATTERN}. 019 * 020 * Depending on the context the exception is thrown in we have to react differently. 021 * <ul> 022 * <li>if it is thrown when we try to update a changeset, the changeset was most 023 * likely closed before, either explicitly by the user or because of a timeout</li> 024 * <li>if it is thrown when we try to upload data to the changeset, the changeset 025 * was most likely closed because we reached the servers capability limit for the size 026 * of a changeset.</li> 027 * </ul> 028 */ 029public class ChangesetClosedException extends OsmTransferException { 030 /** the error header pattern for in case of HTTP response 409 indicating 031 * that a changeset was closed 032 */ 033 final static public String ERROR_HEADER_PATTERN = "The changeset (\\d+) was closed at (.*)"; 034 035 public static enum Source { 036 /** 037 * The exception was thrown when a changeset was updated. This most likely means 038 * that the changeset was closed before. 039 */ 040 UPDATE_CHANGESET, 041 /** 042 * The exception was thrown when data was uploaded to the changeset. This most 043 * likely means that the servers capability limits for a changeset have been 044 * exceeded. 045 */ 046 UPLOAD_DATA, 047 /** 048 * Unspecified source 049 */ 050 UNSPECIFIED 051 } 052 053 /** 054 * Replies true if <code>errorHeader</code> matches with {@link #ERROR_HEADER_PATTERN} 055 * 056 * @param errorHeader the error header 057 * @return true if <code>errorHeader</code> matches with {@link #ERROR_HEADER_PATTERN} 058 */ 059 static public boolean errorHeaderMatchesPattern(String errorHeader) { 060 if (errorHeader == null) 061 return false; 062 Pattern p = Pattern.compile(ERROR_HEADER_PATTERN); 063 Matcher m = p.matcher(errorHeader); 064 return m.matches(); 065 } 066 067 /** the changeset id */ 068 private long changesetId; 069 /** the date on which the changeset was closed */ 070 private Date closedOn; 071 /** the source */ 072 private Source source; 073 074 protected void parseErrorHeader(String errorHeader) { 075 Pattern p = Pattern.compile(ERROR_HEADER_PATTERN); 076 Matcher m = p.matcher(errorHeader); 077 if (m.matches()) { 078 changesetId = Long.parseLong(m.group(1)); 079 // Example: "2010-09-07 14:39:41 UTC". Always parsed with US locale regardless 080 // of the current locale in JOSM 081 DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US); 082 try { 083 closedOn = formatter.parse(m.group(2)); 084 } catch(ParseException ex) { 085 Main.error(tr("Failed to parse date ''{0}'' replied by server.", m.group(2))); 086 ex.printStackTrace(); 087 } 088 } else { 089 Main.error(tr("Unexpected format of error header for conflict in changeset update. Got ''{0}''", errorHeader)); 090 } 091 } 092 093 /** 094 * Creates the exception with the given <code>errorHeader</code> 095 * 096 * @param errorHeader the error header 097 */ 098 public ChangesetClosedException(String errorHeader) { 099 super(errorHeader); 100 parseErrorHeader(errorHeader); 101 this.source = Source.UNSPECIFIED; 102 } 103 104 /** 105 * Creates the exception with the given error header and the given 106 * source. 107 * 108 * @param errorHeader the error header 109 * @param source the source for the exception 110 */ 111 public ChangesetClosedException(String errorHeader, Source source) { 112 super(errorHeader); 113 parseErrorHeader(errorHeader); 114 this.source = source == null ? Source.UNSPECIFIED : source; 115 } 116 117 /** 118 * Creates the exception 119 * 120 * @param changesetId the id if the closed changeset 121 * @param closedOn the date the changeset was closed on 122 * @param source the source for the exception 123 */ 124 public ChangesetClosedException(long changesetId, Date closedOn, Source source) { 125 super(""); 126 this.source = source == null ? Source.UNSPECIFIED : source; 127 this.changesetId = changesetId; 128 this.closedOn = closedOn; 129 } 130 131 /** 132 * Replies the id of the changeset which was closed 133 * 134 * @return the id of the changeset which was closed 135 */ 136 public long getChangesetId() { 137 return changesetId; 138 } 139 140 /** 141 * Replies the date the changeset was closed 142 * 143 * @return the date the changeset was closed. May be null if the date isn't known. 144 */ 145 public Date getClosedOn() { 146 return closedOn; 147 } 148 149 /** 150 * Replies the source where the exception was thrown 151 * 152 * @return the source 153 */ 154 public Source getSource() { 155 return source; 156 } 157 158 public void setSource(Source source) { 159 this.source = source == null ? Source.UNSPECIFIED : source; 160 } 161}