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    }