فهرست منبع

Connector Fofr

Lukas Cerny 1 سال پیش
والد
کامیت
fb5a4b967c

+ 33 - 0
config/fofrToTelemetry.yaml

@@ -0,0 +1,33 @@
+settings:
+  - Fofr:
+      name: "Fofr Fetcher"
+      provider: "cz.senslog.connector.fetch.fofr.FofrFetchProvider"
+
+      orderStatesUrl: "https://objednavky.fofrcz.cz/stavy/fofr/xml"
+      warehouseLocationsUrl: "https://objednavky.fofrcz.cz/seznam-dep/json"
+      parcelTrackingUrl: "https://objednavky.fofrcz.cz/procedure/track_and_trace_sledovani_zasilek.php"
+      parcelInfoUrl: "https://objednavky.fofrcz.cz/zasilka-info/%s/%d/json"
+      activeOrdersUrl: "http://172.17.0.1:8085/integration/tracking/all"
+
+      deliveryStatus: 6
+
+      missingWarehouses:
+        - code: CP
+          location: [48.9763139, 14.5065361]
+
+  - Telemetry:
+      name: "SensLog Telemetry"
+      provider: "cz.senslog.connector.push.telemetry.TelemetryPushProvider"
+
+      infoUrl: "http://172.17.0.1:8085/info"
+      stopDeliveryUrl: "http://172.17.0.1:8085/integration/tracking/stop"
+      updateLocationsUrl: "http://172.17.0.1:8085/campaigns/4/units/observations/locations"
+
+
+connectors:
+  - FofrTelemetry:
+      fetcher: "Fofr"
+      pusher: "Telemetry"    # name of the pusher from 'settings'
+      period: 10          # 86_400 = 24h
+#      startAt: "02:30:00" # hh:mm:ss  # non-mandatory attribute
+      initDelay: 5        # non-mandatory attribute

+ 27 - 2
connector-fetch-fofr/pom.xml

@@ -9,12 +9,37 @@
         <version>1.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>connector-fetch-fofr</artifactId>
-
     <properties>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
+    <artifactId>connector-fetch-fofr</artifactId>
+    <name>fetch-senslog-telemetry</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-fetch-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+            <version>2.11.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>

+ 77 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrConfig.java

@@ -0,0 +1,77 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+
+import java.util.*;
+
+public class FofrConfig {
+
+    private final String orderStatesUrl;
+    private final String warehouseLocationsUrl;
+    private final String parcelTrackingUrl;
+    private final String parcelInfoUrl;
+    private final String activeOrdersUrl;
+
+    private final int deliveryStatus;
+    private final Set<WarehouseInfo> missingWarehouses;
+
+    public FofrConfig(DefaultConfig defaultConfig) {
+        this.orderStatesUrl = defaultConfig.getStringProperty("orderStatesUrl");
+        this.warehouseLocationsUrl = defaultConfig.getStringProperty("warehouseLocationsUrl");
+        this.parcelTrackingUrl = defaultConfig.getStringProperty("parcelTrackingUrl");
+        this.parcelInfoUrl = defaultConfig.getStringProperty("parcelInfoUrl");
+        this.activeOrdersUrl = defaultConfig.getStringProperty("activeOrdersUrl");
+        this.deliveryStatus = defaultConfig.getIntegerProperty("deliveryStatus");
+        if (defaultConfig.containsProperty("missingWarehouses")) {
+            Object wObject = defaultConfig.getProperty("missingWarehouses");
+            if (wObject instanceof List) {
+                List<?> wList = (List<?>) wObject;
+                this.missingWarehouses = new HashSet<>(wList.size());
+                for (Object w : wList) {
+                    if (w instanceof Map) {
+                        Map<?, ?> wMap = (Map<?, ?>) w;
+                        List<?> coord = (List<?>) wMap.get("location");
+                        if (coord.size() == 2) {
+                            String code = (String) wMap.get("code");
+                            Double lat = (Double) coord.get(0);
+                            Double lon = (Double) coord.get(1);
+                            missingWarehouses.add(new WarehouseInfo(code, lat, lon));
+                        }
+                    }
+                }
+            } else {
+                this.missingWarehouses = Collections.emptySet();
+            }
+        } else {
+            this.missingWarehouses = Collections.emptySet();
+        }
+    }
+
+    public int getDeliveryStatus() {
+        return deliveryStatus;
+    }
+
+    public String getActiveOrdersUrl() {
+        return activeOrdersUrl;
+    }
+
+    public String getOrderStatesUrl() {
+        return orderStatesUrl;
+    }
+
+    public String getWarehouseLocationsUrl() {
+        return warehouseLocationsUrl;
+    }
+
+    public String getParcelTrackingUrl() {
+        return parcelTrackingUrl;
+    }
+
+    public String getParcelInfoUrl() {
+        return parcelInfoUrl;
+    }
+
+    public Set<WarehouseInfo> getMissingWarehouses() {
+        return missingWarehouses;
+    }
+}

+ 33 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetchProvider.java

@@ -0,0 +1,33 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.fofr.FofrModel;
+import cz.senslog.connector.tools.http.HttpClient;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class FofrFetchProvider implements cz.senslog.connector.fetch.api.ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(FofrFetchProvider.class);
+
+    @Override
+    public ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", FofrFetchProvider.class.getSimpleName());
+
+        logger.debug("Creating a new configuration.");
+        FofrConfig config = new FofrConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+
+        logger.debug("Creating a new instance of {}.", FofrFetcher.class);
+        FofrFetcher fetcher = new FofrFetcher(config, HttpClient.newHttpSSLClient());
+        logger.info("Fetcher for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+        ExecutableFetcher<FofrModel> executor = ExecutableFetcher.create(fetcher);
+        logger.info("Fetcher executor for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+        return executor;
+    }
+}

+ 250 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetcher.java

@@ -0,0 +1,250 @@
+package cz.senslog.connector.fetch.fofr;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.model.api.VoidSession;
+import cz.senslog.connector.model.fofr.FofrModel;
+import cz.senslog.connector.model.fofr.ParcelUpdate;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.util.Encoding;
+import cz.senslog.connector.tools.util.Tuple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
+import static cz.senslog.connector.tools.json.BasicJson.objectToJson;
+import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+public class FofrFetcher implements ConnectorFetcher<VoidSession, FofrModel> {
+
+    private static final Logger logger = LogManager.getLogger(FofrFetcher.class);
+
+
+    private final FofrConfig config;
+    private final HttpClient httpClient;
+    private final XmlMapper xmlMapper;
+
+    private Map<String, WarehouseInfo> warehouses;
+    private Map<Integer, String> orderStates;
+
+    protected FofrFetcher(FofrConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+        this.xmlMapper = new XmlMapper();
+
+        this.warehouses = Collections.emptyMap();
+        this.orderStates = Collections.emptyMap();
+    }
+
+    @Override
+    public void init() throws Exception {
+        {
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getWarehouseLocationsUrl()).build())
+                    .build();
+
+            HttpResponse response = httpClient.send(request);
+
+            if (response.isError()) {
+                throw logger.throwing(new Exception(format(
+                        "Can not get information about the warehouses. %s", response.getBody()
+                )));
+            }
+
+            String responseBody = response.getBody();
+            List<?> responseDTO = jsonToObject(responseBody, List.class);
+            warehouses = responseDTO.stream().map(Map.class::cast).map(w -> new WarehouseInfo(
+                        (String)w.get("KOD_STREDISKA"),
+                        Double.parseDouble((String)w.get("GEOCODE_LAT")),
+                        Double.parseDouble((String)w.get("GEOCODE_LNG"))
+                    )).collect(Collectors.toMap(WarehouseInfo::getCode, Function.identity()));
+
+            for (WarehouseInfo missingWarehouse : config.getMissingWarehouses()) {
+                warehouses.put(missingWarehouse.getCode(), missingWarehouse);
+            }
+        }
+
+        {
+
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getOrderStatesUrl()).build())
+                    .build();
+
+            HttpResponse response = httpClient.send(request, Encoding.WINDOWS_1250);
+
+            if (response.isError()) {
+                throw logger.throwing(new Exception(format(
+                        "Can not get information about the statuses. %s", response.getBody()
+                )));
+            }
+
+            String responseBody = response.getBody();
+            List<?> xml = xmlMapper.readValue(responseBody, List.class);
+            orderStates = xml.stream().map(Map.class::cast).map(m -> Tuple.of(
+                    Integer.parseInt((String)m.get("ID")),
+                    (String)m.get("POPIS"))
+            ).collect(Collectors.toMap(Tuple::getItem1, Tuple::getItem2));
+
+        }
+    }
+
+    @Override
+    public FofrModel fetch(Optional<VoidSession> session) {
+
+        List<OrderInfo> orderInfos = Collections.emptyList();
+//
+//        {
+//            HttpRequest request = HttpRequest.newBuilder().GET()
+//                    .url(URLBuilder.newBuilder(config.getActiveOrdersUrl()).build())
+//                    .build();
+//
+//            HttpResponse response = httpClient.send(request);
+//
+//            if (response.isOk()) {
+//                String responseBody = response.getBody();
+//                List<?> orderList = jsonToObject(responseBody, List.class);
+//                orderInfos = new ArrayList<>(orderList.size());
+//                for (Object orderO : orderList) {
+//                    if (orderO instanceof Map) {
+//                        Map<?, ?> orderMap = (Map<?, ?>) orderO;
+//                        orderInfos.add(new OrderInfo(
+//                                ((Double) orderMap.get("orderId")).longValue(),
+//                                ((Double) orderMap.get("parcelId")).longValue(),
+//                                ((Double) orderMap.get("unitId")).longValue()
+//                        ));
+//                    }
+//                }
+//            }
+//        }
+
+        orderInfos = Arrays.asList(new OrderInfo(0, 2590161966L, 0));
+
+        List<ParcelUpdate> parcelsToUpdate = new ArrayList<>();
+        for (OrderInfo orderInfo : orderInfos) {
+
+            long parcelId = orderInfo.getParcelId();
+
+            HttpRequest reqTrackingInfo = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getParcelTrackingUrl())
+                            .addParam("shipment", parcelId)
+                            .build())
+                    .build();
+
+            HttpResponse resTrackingInfo = httpClient.send(reqTrackingInfo, Encoding.WINDOWS_1250);
+            if (resTrackingInfo.isError()) {
+                logger.error(resTrackingInfo.getBody()); continue;
+            }
+
+            try {
+                String trackingInfoBody = resTrackingInfo.getBody();
+                ParcelTrackingStatus trackingInfo = xmlMapper.readValue(trackingInfoBody, ParcelTrackingStatus.class);
+
+                if (trackingInfo.getId() != (parcelId)) {
+                    logger.warn("Received tracking ID does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
+                }
+
+                // TODO compare last status with the one from session if any updates
+                // TODO if no update, continue to next parcelId
+
+                if (trackingInfo.getStatuses().isEmpty()) {
+                    logger.warn("No status for the parcel ID {}", trackingInfo.getId()); continue;
+                }
+
+                int parcelYear = trackingInfo.getStatuses().get(0).getTimestamp().getYear();
+
+                HttpRequest reqParcelInfo = HttpRequest.newBuilder().GET()
+                        .url(URLBuilder.newBuilder(String.format(config.getParcelInfoUrl(), parcelId, parcelYear)
+                                ).build())
+                        .build();
+
+                HttpResponse resParcelInfo = httpClient.send(reqParcelInfo);
+                if (resParcelInfo.isError()) {
+                    logger.error(resParcelInfo.getBody()); continue;
+                }
+
+                String parcelInfoBody = resParcelInfo.getBody();
+                List<?> parcelInfoList = jsonToObject(parcelInfoBody, List.class);
+                if (parcelInfoList.isEmpty()) {
+                    logger.error("Parcel info of the ID " + parcelId + " not found"); continue;
+                }
+
+                if (!(parcelInfoList.get(0) instanceof Map)) {
+                    logger.error("Wrong type of parcel info."); continue;
+                }
+
+                Map<?,?> parcelInfo = (Map<?,?>) parcelInfoList.get(0);
+                long shipmentNum = Long.parseLong((String) parcelInfo.get("shipment_num"));
+                if (parcelId != (shipmentNum)) {
+                    logger.warn("Received parcel info data does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
+                }
+
+                String officeBranchCode = (String) parcelInfo.get("kod_pobocky");
+                WarehouseInfo officeBranch = warehouses.get(officeBranchCode);
+
+                int lastTrackingStatusCode = ((Double)parcelInfo.get("posledni_statut")).intValue();
+                List<ParcelTrackingStatus.Status> statuses = trackingInfo.getStatuses();
+                if (statuses.get(statuses.size()-1).getCode() != lastTrackingStatusCode) {
+                    statuses.sort(Comparator.comparing(ParcelTrackingStatus.Status::getTimestamp));
+                }
+                List<ParcelInfo.TrackingStatus> trackingStatuses = new ArrayList<>(statuses.size());
+                for (ParcelTrackingStatus.Status status : statuses) {
+                    String statusName = orderStates.get(status.getCode());
+                    if (statusName == null) {
+                        logger.warn("No status name for '{}'", status.getCode());
+                    }
+                    WarehouseInfo transitWarehouse = warehouses.get(status.getWarehouseCode());
+                    if (transitWarehouse == null) {
+                        logger.warn("No warehouse for '{}'", status.getWarehouseCode());
+                    }
+                    trackingStatuses.add(new ParcelInfo.TrackingStatus(
+                            status.getCode(), statusName, status.getTimestamp(), transitWarehouse
+                    ));
+                }
+
+                LocalDateTime exportDate = LocalDateTime.parse((String)parcelInfo.get("datum_exportu"), ofPattern("yyyy-MM-dd HH:mm:ss"));
+
+                ParcelInfo parcel = new ParcelInfo(parcelId, officeBranch, exportDate, trackingStatuses,
+                        new ParcelInfo.Address(
+                                (String) parcelInfo.get("odes_firma"),
+                                (String) parcelInfo.get("odes_stat"),
+                                (String) parcelInfo.get("odes_mesto"),
+                                (String) parcelInfo.get("odes_ulice"),
+                                (String) parcelInfo.get("odes_psc")
+                        ),
+                        new ParcelInfo.Address(
+                                (String) parcelInfo.get("prij_firma"),
+                                (String) parcelInfo.get("prij_stat"),
+                                (String) parcelInfo.get("prij_mesto"),
+                                (String) parcelInfo.get("prij_ulice"),
+                                (String) parcelInfo.get("prij_psc")
+                        )
+                );
+
+                if (parcel.lastTrackingStatus().getId() == config.getDeliveryStatus()) {
+                    // TODO parcel is delivered
+                    // GET GPS coordination from delivery address
+                }
+
+                System.out.println(objectToJson(parcel));
+
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        return new FofrModel(parcelsToUpdate, OffsetDateTime.now(), OffsetDateTime.now());
+    }
+
+
+}

+ 28 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/OrderInfo.java

@@ -0,0 +1,28 @@
+package cz.senslog.connector.fetch.fofr;
+
+import java.time.OffsetDateTime;
+
+public class OrderInfo {
+
+    private final long orderId;
+    private final long parcelId;
+    private final long unitId;
+
+    OrderInfo(long orderId, long parcelId, long unitId) {
+        this.orderId = orderId;
+        this.parcelId = parcelId;
+        this.unitId = unitId;
+    }
+
+    public long getOrderId() {
+        return orderId;
+    }
+
+    public long getParcelId() {
+        return parcelId;
+    }
+
+    public long getUnitId() {
+        return unitId;
+    }
+}

+ 144 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelInfo.java

@@ -0,0 +1,144 @@
+package cz.senslog.connector.fetch.fofr;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+public class ParcelInfo {
+
+    public static class Address {
+        private final String name;
+        private final String country;
+        private final String city;
+        private final String street;
+        private final String zip;
+
+        public Address(String name, String country, String city, String street, String zip) {
+            this.name = name;
+            this.country = country;
+            this.city = city;
+            this.street = street;
+            this.zip = zip;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getCountry() {
+            return country;
+        }
+
+        public String getCity() {
+            return city;
+        }
+
+        public String getStreet() {
+            return street;
+        }
+
+        public String getZip() {
+            return zip;
+        }
+    }
+
+    public static class TrackingStatus {
+        private final int id;
+        private final String name;
+        private final LocalDateTime timestamp;
+        private final WarehouseInfo warehouseInfo;
+
+        public TrackingStatus(int id, String name, LocalDateTime timestamp, WarehouseInfo warehouseInfo) {
+            this.id = id;
+            this.name = name;
+            this.timestamp = timestamp;
+            this.warehouseInfo = warehouseInfo;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public LocalDateTime getTimestamp() {
+            return timestamp;
+        }
+
+        public WarehouseInfo getWarehouseInfo() {
+            return warehouseInfo;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TrackingStatus that = (TrackingStatus) o;
+            return id == that.id && Objects.equals(timestamp, that.timestamp);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, timestamp);
+        }
+    }
+
+    private final long parcelId;
+    private final WarehouseInfo warehouseOrigin;
+    private final LocalDateTime exportDatetime;
+    private final List<TrackingStatus> trackingStatuses;
+    private final Address dispatcherAddress;
+    private final Address recipientAddress;
+
+    public ParcelInfo(long parcelId, WarehouseInfo warehouseOrigin, LocalDateTime exportDatetime, List<TrackingStatus> trackingStatuses, Address dispatcherAddress, Address recipientAddress) {
+        this.parcelId = parcelId;
+        this.warehouseOrigin = warehouseOrigin;
+        this.exportDatetime = exportDatetime;
+        this.trackingStatuses = trackingStatuses;
+        this.dispatcherAddress = dispatcherAddress;
+        this.recipientAddress = recipientAddress;
+    }
+
+    public long getParcelId() {
+        return parcelId;
+    }
+
+    public WarehouseInfo getWarehouseOrigin() {
+        return warehouseOrigin;
+    }
+
+    public LocalDateTime getExportDatetime() {
+        return exportDatetime;
+    }
+
+    public List<TrackingStatus> getTrackingStatuses() {
+        return trackingStatuses;
+    }
+
+    public Address getDispatcherAddress() {
+        return dispatcherAddress;
+    }
+
+    public Address getRecipientAddress() {
+        return recipientAddress;
+    }
+
+    public TrackingStatus lastTrackingStatus() {
+        return trackingStatuses.get(trackingStatuses.size()-1);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ParcelInfo that = (ParcelInfo) o;
+        return parcelId == that.parcelId && Objects.equals(exportDatetime, that.exportDatetime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(parcelId, exportDatetime);
+    }
+}

+ 88 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelTrackingStatus.java

@@ -0,0 +1,88 @@
+package cz.senslog.connector.fetch.fofr;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@JacksonXmlRootElement(localName = "trackandtrace")
+public class ParcelTrackingStatus implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public static class Fofr {
+
+        @JacksonXmlProperty(isAttribute = true, localName = "ID")
+        private String id;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreOrigin")
+        private String warehouseOrigin;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreFrom")
+        private String CentreFrom;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreTo")
+        private String warehouseDestination;
+        @JacksonXmlProperty(isAttribute = true, localName = "Code")
+        private String Code;
+        @JacksonXmlProperty(isAttribute = true, localName = "Colli")
+        private String Colli;
+        @JacksonXmlProperty(isAttribute = true, localName = "Weight")
+        private String Weight;
+        @JacksonXmlProperty(isAttribute = true, localName = "Volume")
+        private String Volume;
+
+        @JacksonXmlElementWrapper(localName = "Statuses")
+        @JacksonXmlProperty(localName = "Status")
+        List<Status> statuses;
+    }
+
+    public static class Status {
+        @JacksonXmlProperty(isAttribute = true, localName = "StCode")
+        private String code;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "StDate")
+        private String timestamp;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "Depo")
+        private String warehouseCode;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "Kontakt")
+        private String contact;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "StInfo")
+        private String info;
+
+        public int getCode() {
+            return Integer.parseInt(code);
+        }
+
+        public LocalDateTime getTimestamp() {
+            return LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-d H:m"));
+        }
+
+        public String getWarehouseCode() {
+            return warehouseCode;
+        }
+    }
+
+    @JacksonXmlElementWrapper(localName = "FOFR")
+    private Fofr parcelInfo;
+
+    public long getId() {
+        return Long.parseLong(parcelInfo.id);
+    }
+
+    public String getWarehouseOrigin() {
+        return parcelInfo.warehouseOrigin;
+    }
+
+    public String getWarehouseDestination() {
+        return parcelInfo.warehouseDestination;
+    }
+
+    public List<Status> getStatuses() {
+        return parcelInfo.statuses;
+    }
+}

+ 26 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/WarehouseInfo.java

@@ -0,0 +1,26 @@
+package cz.senslog.connector.fetch.fofr;
+
+public class WarehouseInfo {
+
+    private final String code;
+    private final double latitude;
+    private final double longitude;
+
+    public WarehouseInfo(String code, double latitude, double longitude) {
+        this.code = code;
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+}

+ 1 - 0
connector-fetch-fofr/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider

@@ -0,0 +1 @@
+cz.senslog.connector.fetch.fofr.FofrFetchProvider

+ 39 - 0
connector-fetch-fofr/src/test/java/cz/senslog/connector/fetch/fofr/FofrFetcherTest.java

@@ -0,0 +1,39 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.util.DateTrunc;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FofrFetcherTest {
+
+    @Test
+    void fetch() throws Exception {
+
+        DefaultConfig defaultConfig = new DefaultConfig("fofr", null);
+        defaultConfig.setProperty("orderStatesUrl", "https://objednavky.fofrcz.cz/stavy/fofr/xml");
+            defaultConfig.setProperty("warehouseLocationsUrl", "https://objednavky.fofrcz.cz/seznam-dep/json");
+        defaultConfig.setProperty("parcelTrackingUrl", "https://objednavky.fofrcz.cz/procedure/track_and_trace_sledovani_zasilek.php");
+        defaultConfig.setProperty("parcelInfoUrl", "https://objednavky.fofrcz.cz/zasilka-info/%d/%d/json");
+        defaultConfig.setProperty("activeOrdersUrl", "http://172.17.0.1:8085/integration/tracking/all");
+        defaultConfig.setProperty("deliveryStatus", 6);
+
+        defaultConfig.setProperty("missingWarehouses", Collections.singletonList(
+                new HashMap<String, Object>() {{
+                    put("code", "CB");
+                    put("location", Arrays.asList(49.4854338, 16.6436640));
+                }}
+        ));
+
+        FofrConfig config = new FofrConfig(defaultConfig);
+
+        FofrFetcher fofrFetcher = new FofrFetcher(config, HttpClient.newHttpClient());
+
+        fofrFetcher.init();
+        fofrFetcher.fetch(Optional.empty());
+    }
+}

+ 0 - 25
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryProxySession.java

@@ -1,25 +0,0 @@
-package cz.senslog.fetch.senslog.telemetry;
-
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.FetchProxySession;
-import cz.senslog.connector.model.api.GeoJsonModel;
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.util.Optional;
-
-public class SensLogTelemetryProxySession extends FetchProxySession<SensLogTelemetrySession, GeoJsonModel> {
-
-    protected SensLogTelemetryProxySession(ConnectorFetcher<SensLogTelemetrySession, GeoJsonModel> instance) {
-        super(instance);
-    }
-
-    @Override
-    protected SensLogTelemetrySession preProcessing(Optional<SensLogTelemetrySession> previousSession) {
-        return previousSession.filter(ProxySessionModel::isActive).orElse(new SensLogTelemetrySession(true));
-    }
-
-    @Override
-    protected void postProcessing(Optional<GeoJsonModel> model, Optional<SensLogTelemetrySession> session) {
-
-    }
-}