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