/[projects]/android/TrainInfoService/src/dk/thoerup/traininfoservice/banedk/DepartureFetcher.java
ViewVC logotype

Contents of /android/TrainInfoService/src/dk/thoerup/traininfoservice/banedk/DepartureFetcher.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1562 - (show annotations) (download)
Fri Jul 8 16:26:09 2011 UTC (12 years, 10 months ago) by torben
File size: 18373 byte(s)
Flesh out all logic from TrainInfoCommon so it just contains simple beans
1 package dk.thoerup.traininfoservice.banedk;
2
3
4 import java.net.URL;
5 import java.net.URLEncoder;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.Map;
9 import java.util.logging.Logger;
10
11 import org.jsoup.nodes.Document;
12 import org.jsoup.nodes.Element;
13 import org.jsoup.select.Elements;
14
15 import dk.thoerup.android.traininfo.common.DepartureBean;
16 import dk.thoerup.android.traininfo.common.DepartureEntry;
17 import dk.thoerup.android.traininfo.common.StationEntry;
18 import dk.thoerup.circuitbreaker.CircuitBreaker;
19 import dk.thoerup.circuitbreaker.CircuitBreakerManager;
20 import dk.thoerup.genericjavautils.HttpUtil;
21 import dk.thoerup.genericjavautils.TimeoutMap;
22 import dk.thoerup.traininfoservice.Statistics;
23 import dk.thoerup.traininfoservice.TraininfoSettings;
24 import dk.thoerup.traininfoservice.db.StationDAO;
25
26 public class DepartureFetcher {
27
28 enum TrainType{
29 STOG,
30 REGIONAL
31 }
32
33 enum FetchTrainType {
34 STOG,
35 REGIONAL,
36 BOTH
37 }
38
39 Logger logger = Logger.getLogger(DepartureFetcher.class.getName());
40
41 Map<String, DepartureBean> cache;
42
43 StationDAO stationDao = new StationDAO();
44
45
46 private TraininfoSettings settings;
47
48 Comparator<DepartureEntry> departureTimeComparator = new Comparator<DepartureEntry>() {
49
50 @Override
51 public int compare(DepartureEntry arg0, DepartureEntry arg1) {
52 String timeStr1 = arg0.getTime().replace(":","").trim();
53 String timeStr2 = arg1.getTime().replace(":","").trim();
54
55 int time1 = 0;
56 int time2 = 0;
57
58 if (timeStr1.length() > 0)
59 time1 = Integer.parseInt(timeStr1);
60
61 if (timeStr2.length() > 0)
62 time2 = Integer.parseInt(timeStr2);
63
64 //work correctly when clock wraps around at midnight
65 if (Math.abs(time1-time2) < 1200) {
66 if (time1 > time2)
67 return 1;
68 else
69 return -1;
70 } else {
71 if (time1 < time2)
72 return 1;
73 else
74 return -1;
75
76 }
77
78 }
79
80 };
81
82 public DepartureFetcher(TraininfoSettings settings) {
83 this.settings = settings;
84 cache = new TimeoutMap<String,DepartureBean>( settings.getCacheTimeout() );
85 }
86
87
88
89
90 public DepartureBean cachedLookupDepartures(int stationID, boolean arrival, FetchTrainType type) throws Exception {
91
92 final String key = "" + stationID + ":" + arrival + ":" + type.toString();
93
94 DepartureBean departureBean = cache.get(key);
95
96
97 if (departureBean == null) {
98 departureBean = lookupDepartures(stationID, arrival, type);
99 cache.put(key, departureBean);
100 } else {
101 Statistics.getInstance().incrementDepartureCacheHits();
102 logger.info("Departure: Cache hit " + key); //remove before production
103 }
104 return departureBean;
105 }
106
107
108 public DepartureBean lookupDepartures(int stationID, boolean arrival, FetchTrainType type) throws Exception {
109
110 DepartureBean departureBean = new DepartureBean();
111
112 StationEntry station = stationDao.getById(stationID);
113
114 departureBean.stationName = station.getName();
115
116 //TODO: FetchTraintype.Both should be removed some time after 0.9.5 release
117 if (station.getRegional() != null && (type == FetchTrainType.REGIONAL||type == FetchTrainType.BOTH) ) {
118 DepartureBean tempBean = lookupDepartures(station.getRegional(), TrainType.REGIONAL, arrival);
119 departureBean.entries.addAll( tempBean.entries );
120 departureBean.notifications.addAll(tempBean.notifications);
121 }
122
123 if (station.getStrain() != null && (type == FetchTrainType.STOG||type == FetchTrainType.BOTH)) {
124 DepartureBean tempBean = lookupDepartures(station.getStrain(), TrainType.STOG, arrival);
125 departureBean.entries.addAll( tempBean.entries );
126 departureBean.notifications.addAll(tempBean.notifications);
127 }
128
129 if (departureBean.entries.size() == 0) {
130 logger.info("No departures found for station " + stationID);
131 }
132
133 //TODO: FetchTraintype.Both should be removed some time after 0.9.5 release
134 if (type == FetchTrainType.BOTH) { //if we have both S-tog and regional order by departure/arrival time
135 Collections.sort( departureBean.entries, departureTimeComparator);
136 }
137
138
139 return departureBean;
140 }
141
142 public DepartureBean lookupDepartures(String stationcode, TrainType type, boolean arrival) throws Exception {
143 if ( settings.getBackend() == TraininfoSettings.Backend.Azure) {
144 return lookupDeparturesAzureSite(stationcode, type, arrival);
145 } else {
146 return lookupDeparturesMobileSite(stationcode, type, arrival);
147 }
148 }
149
150 private String getTypeStringAzure(TrainType type) {
151 switch (type) {
152 case STOG:
153 return "S-Tog";
154 case REGIONAL:
155 return "Fjerntog";
156 default:
157 return ""; //Can not happen
158 }
159 }
160
161 private String getTypeStringWww(TrainType type) {
162 switch (type) {
163 case STOG:
164 return "S2";
165 case REGIONAL:
166 return "FJRN";
167 default:
168 return ""; //Can not happen
169 }
170 }
171
172 public DepartureBean lookupDeparturesAzureSite(String stationcode, TrainType type, boolean arrival) throws Exception {
173
174 DepartureBean departureBean = new DepartureBean();
175
176
177 String typeString = getTypeStringAzure(type);
178 String arrivalDeparture = (arrival==false) ? "Afgang" : "Ankomst";
179
180 stationcode = URLEncoder.encode(stationcode,"ISO-8859-1");
181
182 String uri = "http://trafikinfo.bane.dk/Trafikinformation/AfgangAnkomst/" + arrivalDeparture + "/" + stationcode + "/" + typeString + "/UdvidetVisning";
183
184 logger.fine("URI: " + uri);
185 JsoupInvocation wrapper = new JsoupInvocation( new URL(uri), settings.getReplyTimeout() );
186 CircuitBreaker breaker = CircuitBreakerManager.getManager().getCircuitBreaker("banedk");
187
188 Document page = (Document) breaker.invoke(wrapper);
189
190 String tableName = arrival == false ? "afgangtabel" : "ankomsttabel";
191 Element table = page.getElementById(tableName);
192
193 if (table != null) {
194 Elements tableRows = table.getElementsByTag("tr");
195
196 //boolean tidsstregExists = (table.getElementsByAttributeValue("class", "Tidsstreg").size() > 0);
197 //boolean passedTidsstreg = false;
198
199 for (Element currentRow : tableRows) {
200 String rowClass = currentRow.attr("class");
201 /*
202 if (tidsstregExists == true && passedTidsstreg == false) {
203 if (currentRow.getElementsByAttributeValue("class", "Tidsstreg").size() > 0) {
204 passedTidsstreg = true;
205 } else {
206 continue;
207 }
208 }*/
209
210 if (rowClass != null && rowClass.toLowerCase().contains("station") ) {
211
212 Elements fields = currentRow.getElementsByTag("td");
213
214 DepartureEntry departure = new DepartureEntry();
215
216 String time = fields.get(0).text();
217 if (time.equals(""))
218 time = "0:00"; //Bane.dk bug work-around
219 departure.setTime(time);
220
221 int updated = extractUpdated( fields.get(1) );
222 departure.setUpdated(updated);
223
224 String trainNumber = fields.get(2).text();
225 if (type == TrainType.STOG) //If it is S-train we need to extract the trainNumber
226 trainNumber = trainNumber + " " + extractTrainNumberAzure(fields.get(2));
227 departure.setTrainNumber(trainNumber);
228
229 String destination = fields.get(3).text();
230 departure.setDestination(destination);
231
232 String origin = fields.get(4).text();
233 departure.setOrigin(origin);
234
235 String location = fields.get(5).text();
236 departure.setLocation(location);
237
238 String status = fields.get(6).text().trim();
239 departure.setStatus(status);
240
241 String note = extractNote( fields.get(7) );
242 departure.setNote(note);
243
244 departure.setType(typeString);
245
246 departureBean.entries.add( departure );
247 }
248 }
249 } else {
250 logger.warning("No departures found for station=" + stationcode + ", type=" + type);
251 }
252
253 Element notifDiv = page.getElementById("station_planlagte_text");
254 if (notifDiv != null) {
255
256 Elements tables = notifDiv.getElementsByTag("table");
257 for (Element tab : tables) {
258
259 Elements anchors = tab.getElementsByTag("a");
260 if (anchors.size() == 2) {
261 departureBean.notifications.add( anchors.get(1).text() );
262 }
263 }
264
265 }
266
267
268 return departureBean;
269 }
270
271 public DepartureBean lookupDeparturesMobileSite(String stationcode, TrainType traintype, boolean arrival) throws Exception {
272
273 DepartureBean departureBean = new DepartureBean();
274
275
276 String typeString = getTypeStringWww(traintype);
277 String arrivalDeparture = (arrival==false) ? "afgang" : "ankomst";
278
279 stationcode = URLEncoder.encode(stationcode,"ISO-8859-1");
280
281
282 String uri = "http://mobil.bane.dk/mobilStation.asp?artikelID=5332&stat_kode=" + stationcode + "&webprofil=" + typeString +"&beskrivelse=&mode=ankomstafgang&ankomstafgang=" + arrivalDeparture + "&gemstation=&fuldvisning=1";
283 logger.fine("URI: " + uri);
284 JsoupInvocation wrapper = new JsoupInvocation( new URL(uri), settings.getReplyTimeout() );
285 CircuitBreaker breaker = CircuitBreakerManager.getManager().getCircuitBreaker("banedk");
286
287 Document page = (Document) breaker.invoke(wrapper);
288
289
290 Element content = page.getElementsByClass("contentDiv").get(0);
291
292
293 if (content != null) {
294 Elements tableRows = content.child(0).children();
295
296
297
298 for (Element currentRow : tableRows) {
299 if (currentRow.tagName().equals("br") ) {
300 break;
301 }
302
303
304 String link = currentRow.child(0).attr("href");
305
306 logger.fine( currentRow.text() );
307 logger.fine("Href: " + link);
308
309
310 String parts[] = currentRow.text().split(",");
311
312
313 DepartureEntry departure = new DepartureEntry();
314
315 //if we do these things upfront, then we are allowed to use continue statement when row contains no more data
316 departure.setType(typeString);
317 departureBean.entries.add( departure );
318
319 /*
320 http://mobil.bane.dk/mobilStation.asp?artikelID=5332&tognummer=111&webprofil=FJRN&mode=rute&strBemaerkning=Afg%E5r+fra+%C5rhus+H+kl%2E07%3A21++&strRefURL=%2FmobilStation%2Easp%3FartikelID%3D5332%26stat%5Fkode%3DAR%26webprofil%3DFJRN%26beskrivelse%3D%25C5rhus%2BH%26mode%3Dankomstafgang%26ankomstafgang%3Dafgang%26gemstation%3D
321 */
322 int offset = 0;
323
324 String time = parts[offset++];
325 if (time.equals(""))
326 time = "0:00"; //Bane.dk bug work-around
327 departure.setTime(time);
328
329 int updated = 4; //does not exist on mobile
330 departure.setUpdated(updated);
331
332 String trainNumber = extractTrainNumberMobile(link);
333 /*if (traintype == TrainType.STOG) //If it is S-train we need to extract the trainNumber
334 trainNumber = trainNumber + " " + extractTrainNumberAzure(fields.get(2));*/
335 departure.setTrainNumber(trainNumber);
336
337 if (traintype == TrainType.STOG) { //if it is stog the next vield is the "Line" code - this should be used somewhere, but skippint ahead for now
338 String stogLine = parts[offset++].trim();
339 departure.setTrainNumber(stogLine + " " + trainNumber);
340 }
341
342 String destination = parts[offset++].trim();;
343 departure.setDestination(destination);
344
345 String origin = "-"; // fields.get(4).text(); does not exist on mobile
346 departure.setOrigin(origin);
347
348 String location = ""; // fields.get(5).text(); does not exist on mobile
349 departure.setLocation(location);
350
351 if (offset == parts.length) {
352 continue;
353 }
354
355 if (parts[offset].trim().equalsIgnoreCase("NB!")) {
356 offset++;
357 }
358
359 if (offset == parts.length) {
360 continue;
361 }
362
363 String status = parts[offset++].trim();; //fields.get(6).text().trim(); - extract from url
364 departure.setStatus(status);
365
366 String note = ""; //extractNote( fields.get(7) ); - extract from url
367 departure.setNote(note);
368
369 }
370 } else {
371 logger.warning("No departures found for station=" + stationcode + ", type=" + traintype);
372 }
373
374 return departureBean;
375 }
376
377
378
379 public static String cleanText(String input) {
380 //apparently JSoup translates &nbsp; characters on www.bane.dk to 0xA0
381 return input.replace((char) 0xA0, (char)0x20).trim();
382 }
383
384
385 // old www site is not available any more
386 @Deprecated
387 public DepartureBean lookupDeparturesWwwSite(String stationcode, TrainType trainType, boolean arrival) throws Exception {
388
389 DepartureBean departureBean = new DepartureBean();
390
391 String type = getTypeStringWww(trainType);
392
393 stationcode = URLEncoder.encode(stationcode, "ISO-8859-1");
394
395
396 String uri = "http://www.bane.dk/visStation.asp?ArtikelID=4275&W=" + type + "&S=" + stationcode;
397 logger.fine("URI:" + uri);
398
399
400 JsoupInvocation wrapper = new JsoupInvocation( new URL(uri), settings.getReplyTimeout() );
401 CircuitBreaker breaker = CircuitBreakerManager.getManager().getCircuitBreaker("banedk");
402
403 Element page = (Element) breaker.invoke(wrapper);
404
405 String tableName = arrival == false ? "afgangtabel" : "ankomsttabel";
406 Element table = page.getElementById(tableName);
407
408
409
410 if (table != null) {
411 Elements tableRows = table.getElementsByTag("tr");
412
413 //boolean passedTidsstreg = false;
414 //boolean tidsstregExists = (table.getElementsByAttributeValue("class", "Tidsstreg").size() > 0);
415
416 for (Element currentRow : tableRows) {
417 String rowClass = currentRow.attr("class");
418 /*
419 if (tidsstregExists == true && passedTidsstreg == false) {
420 if (currentRow.getElementsByAttributeValue("class", "Tidsstreg").size() > 0) {
421 passedTidsstreg = true;
422 } else {
423 continue;
424 }
425 }*/
426
427
428 if (rowClass != null && rowClass.toLowerCase().contains("station") ) {
429 Elements fields = currentRow.getElementsByTag("td");
430
431 DepartureEntry departure = new DepartureEntry();
432
433
434
435 String time = cleanText( fields.get(0).getAllElements().get(2).text() );
436 if (time.equals(""))
437 time = "0:00"; //Bane.dk bug work-around
438 departure.setTime(time);
439
440 int updated = extractUpdated( fields.get(1) );
441 departure.setUpdated(updated);
442
443 String trainNumber = cleanText( fields.get(2).text() );
444 if (type.equalsIgnoreCase("S2")) //If it is S-train we need to extract the trainNumber
445 trainNumber = trainNumber + " " + extractTrainNumberWww(fields.get(2));
446 departure.setTrainNumber(trainNumber);
447
448 String destination = cleanText( fields.get(3).text() );
449 departure.setDestination(destination);
450
451 String origin = cleanText( fields.get(4).text() );
452 departure.setOrigin(origin);
453
454 String location = cleanText( fields.get(5).text() );
455 departure.setLocation(location);
456
457 String status = cleanText( fields.get(6).text() );
458 departure.setStatus(status);
459
460 String note = cleanText( extractNote( fields.get(7) ) );
461 departure.setNote(note);
462
463 departure.setType(type);
464
465 departureBean.entries.add(departure);
466
467
468 }
469 }
470 } else {
471 logger.warning("No departures found for station=" + stationcode + ", type=" + type);
472 }
473
474
475 return departureBean;
476 }
477
478
479 private int extractUpdated(Element updatedTd) { //extract the digit (in this case: 4) from "media/trafikinfo/opdater4.gif"
480 int updated = -1;
481
482 Elements updatedImgs = updatedTd.getElementsByTag("img");
483 String updatedStr = updatedImgs.get(0).attr("src");
484
485 if (updatedStr != null) {
486 for (int i=0; i<updatedStr.length(); i++) {
487 char c = updatedStr.charAt(i);
488 if ( Character.isDigit(c)) {
489 updated = Character.digit(c, 10);
490 break;
491 }
492 }
493 }
494 return updated;
495 }
496
497 private String extractNote(Element noteTd) {
498 String note = noteTd.text().trim();
499
500
501 Elements elems = noteTd.getElementsByClass("bemtype");
502 if (elems.size() > 0 && note.charAt(note.length()-1) == 'i')
503 note = note.substring(0,note.length() -1 );
504
505 return note.trim();
506 }
507
508 private String extractTrainNumberAzure(Element trainTd) {
509 Element anchorElement = trainTd.getElementsByTag("a").get(0);
510 String href = anchorElement.attr("href");
511
512 int pos = href.lastIndexOf('/');
513 String number = href.substring(pos+1);
514
515 return number;
516 }
517
518 private String extractTrainNumberMobile(String link) {
519 Map<String,String> elements = HttpUtil.decodeParams(link);
520
521 return elements.get("tognummer");
522 }
523
524 private String extractTrainNumberWww(Element trainTd) {
525 String number = "";
526 Element anchorElement = trainTd.getElementsByTag("a").get(0);
527 String href = anchorElement.attr("href");
528
529 String argstring = href.split("?")[1];
530 Map<String,String> elements = HttpUtil.decodeParams(argstring);
531 number = elements.get("TogNr");
532
533
534 /*String argstring = href.substring( href.indexOf('?') + 1);
535 String args[] = argstring.split("&");
536 for (String arg : args) {
537 String pair[] = arg.split("="); // Key=pair[0], Value=pair[1]
538
539 if (pair[0].equalsIgnoreCase("TogNr"))
540 number = pair[1];
541 }*/
542
543
544 return number;
545 }
546
547
548 //test
549 /*
550 public static void main(String args[]) throws Exception {
551 DepartureFetcher f = new DepartureFetcher();
552 List<DepartureBean> deps = f.lookupDepartures("AR", "FJRN");
553 for(DepartureBean d : deps) {
554 System.out.println( d.getTime() + ";" + d.getUpdated() + ";" + d.getTrainNumber() + ";" +
555 d.getDestination() + ";" + d.getOrigin() + ";" + d.getLocation() + ";" + d.getStatus() + ";" + d.getNote() );
556 }
557
558 System.out.println("--------------------------");
559 }*/
560 }

  ViewVC Help
Powered by ViewVC 1.1.20