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.ParseException; 007import java.util.Date; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010 011import org.openstreetmap.josm.tools.Logging; 012import org.openstreetmap.josm.tools.date.DateUtils; 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 */ 027public 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 public static final String ERROR_HEADER_PATTERN = "The changeset (\\d+) was closed at (.*)"; 032 033 public 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 public static 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 final 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 try { 078 closedOn = DateUtils.newOsmApiDateTimeFormat().parse(m.group(2)); 079 } catch (ParseException ex) { 080 Logging.error(tr("Failed to parse date ''{0}'' replied by server.", m.group(2))); 081 Logging.error(ex); 082 } 083 } else { 084 Logging.error(tr("Unexpected format of error header for conflict in changeset update. Got ''{0}''", errorHeader)); 085 } 086 } 087 088 /** 089 * Creates the exception with the given <code>errorHeader</code> 090 * 091 * @param errorHeader the error header 092 */ 093 public ChangesetClosedException(String errorHeader) { 094 super(errorHeader); 095 parseErrorHeader(errorHeader); 096 this.source = Source.UNSPECIFIED; 097 } 098 099 /** 100 * Creates the exception with the given error header and source. 101 * 102 * @param errorHeader the error header 103 * @param source the source for the exception 104 */ 105 public ChangesetClosedException(String errorHeader, Source source) { 106 this(errorHeader, source, null); 107 } 108 109 /** 110 * Creates the exception with the given error header, source and cause. 111 * 112 * @param errorHeader the error header 113 * @param source the source for the exception 114 * @param cause The cause (which is saved for later retrieval by the {@link #getCause} method). 115 * A null value is permitted, and indicates that the cause is nonexistent or unknown. 116 * @since 13207 117 */ 118 public ChangesetClosedException(String errorHeader, Source source, Throwable cause) { 119 super(errorHeader, cause); 120 parseErrorHeader(errorHeader); 121 this.source = source == null ? Source.UNSPECIFIED : source; 122 } 123 124 /** 125 * Creates the exception 126 * 127 * @param changesetId the id if the closed changeset 128 * @param closedOn the date the changeset was closed on 129 * @param source the source for the exception 130 */ 131 public ChangesetClosedException(long changesetId, Date closedOn, Source source) { 132 super(""); 133 this.source = source == null ? Source.UNSPECIFIED : source; 134 this.changesetId = changesetId; 135 this.closedOn = DateUtils.cloneDate(closedOn); 136 } 137 138 /** 139 * Replies the id of the changeset which was closed 140 * 141 * @return the id of the changeset which was closed 142 */ 143 public long getChangesetId() { 144 return changesetId; 145 } 146 147 /** 148 * Replies the date the changeset was closed 149 * 150 * @return the date the changeset was closed. May be null if the date isn't known. 151 */ 152 public Date getClosedOn() { 153 return DateUtils.cloneDate(closedOn); 154 } 155 156 /** 157 * Replies the source where the exception was thrown 158 * 159 * @return the source 160 */ 161 public Source getSource() { 162 return source; 163 } 164 165 public void setSource(Source source) { 166 this.source = source == null ? Source.UNSPECIFIED : source; 167 } 168}