]> git.basschouten.com Git - openhab-addons.git/blob
44a2b60dc0ad75c6be43c5b418d586b7a8787197
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.tr064.internal.phonebook;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.InputStream;
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.Optional;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 import java.util.stream.Collectors;
24
25 import javax.xml.bind.JAXBContext;
26 import javax.xml.bind.JAXBException;
27 import javax.xml.bind.Unmarshaller;
28 import javax.xml.stream.XMLInputFactory;
29 import javax.xml.stream.XMLStreamException;
30 import javax.xml.stream.XMLStreamReader;
31 import javax.xml.transform.stream.StreamSource;
32
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jetty.client.HttpClient;
35 import org.eclipse.jetty.client.api.ContentResponse;
36 import org.eclipse.jetty.http.HttpMethod;
37 import org.openhab.binding.tr064.internal.dto.additions.PhonebooksType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * The {@link Tr064PhonebookImpl} class implements a phonebook
43  *
44  * @author Jan N. Klug - Initial contribution
45  */
46 @NonNullByDefault
47 public class Tr064PhonebookImpl implements Phonebook {
48     private final Logger logger = LoggerFactory.getLogger(Tr064PhonebookImpl.class);
49
50     private Map<String, String> phonebook = new HashMap<>();
51
52     private final HttpClient httpClient;
53     private final String phonebookUrl;
54
55     private String phonebookName = "";
56
57     public Tr064PhonebookImpl(HttpClient httpClient, String phonebookUrl) {
58         this.httpClient = httpClient;
59         this.phonebookUrl = phonebookUrl;
60         getPhonebook();
61     }
62
63     private void getPhonebook() {
64         try {
65             ContentResponse contentResponse = httpClient.newRequest(phonebookUrl).method(HttpMethod.GET)
66                     .timeout(2, TimeUnit.SECONDS).send();
67             InputStream xml = new ByteArrayInputStream(contentResponse.getContent());
68
69             JAXBContext context = JAXBContext.newInstance(PhonebooksType.class);
70             XMLInputFactory xif = XMLInputFactory.newFactory();
71             xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
72             xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
73             XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource(xml));
74             Unmarshaller um = context.createUnmarshaller();
75             PhonebooksType phonebooksType = um.unmarshal(xsr, PhonebooksType.class).getValue();
76
77             phonebookName = phonebooksType.getPhonebook().getName();
78
79             phonebook = phonebooksType.getPhonebook().getContact().stream().map(contact -> {
80                 String contactName = contact.getPerson().getRealName();
81                 return contact.getTelephony().getNumber().stream()
82                         .collect(Collectors.toMap(number -> normalizeNumber(number.getValue()), number -> contactName,
83                                 this::mergeSameContactNames));
84             }).collect(HashMap::new, HashMap::putAll, HashMap::putAll);
85             logger.debug("Downloaded phonebook {}: {}", phonebookName, phonebook);
86         } catch (JAXBException | InterruptedException | ExecutionException | TimeoutException | XMLStreamException e) {
87             logger.warn("Failed to get phonebook with URL {}:", phonebookUrl, e);
88         }
89     }
90
91     // in case there are multiple phone entries with same number -> name mapping, i.e. in phonebooks exported from
92     // mobiles containing multiple accounts like: local, cloudprovider1, messenger1, messenger2,...
93     private String mergeSameContactNames(String nameA, String nameB) {
94         if (nameA != null && nameA.equals(nameB)) {
95             return nameA;
96         }
97         throw new IllegalStateException(
98                 "Found different names for the same number: '" + nameA + "' and '" + nameB + "'");
99     }
100
101     @Override
102     public String getName() {
103         return phonebookName;
104     }
105
106     @Override
107     public Optional<String> lookupNumber(String number, int matchCount) {
108         String normalized = normalizeNumber(number);
109         String matchString = matchCount > 0 && matchCount < normalized.length()
110                 ? normalized.substring(normalized.length() - matchCount)
111                 : normalized;
112         logger.trace("Normalized '{}' to '{}', matchString is '{}'", number, normalized, matchString);
113         return matchString.isBlank() ? Optional.empty()
114                 : phonebook.keySet().stream().filter(n -> n.endsWith(matchString)).findFirst().map(phonebook::get);
115     }
116
117     @Override
118     public String toString() {
119         return "Phonebook{" + "phonebookName='" + phonebookName + "', phonebook=" + phonebook + '}';
120     }
121
122     private String normalizeNumber(String number) {
123         // Naive normalization: remove all non-digit characters
124         return number.replaceAll("[^0-9]\\+\\*", "");
125     }
126 }