001package com.plivo.api.util;
002
003import java.io.UnsupportedEncodingException;
004import java.net.MalformedURLException;
005import java.net.URL;
006import java.security.InvalidKeyException;
007import java.security.NoSuchAlgorithmException;
008import java.text.Normalizer;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Base64;
012import java.util.HashMap;
013import java.util.LinkedHashMap;
014import java.util.List;
015import java.util.Map;
016import java.util.Map.Entry;
017import java.util.Objects;
018import java.util.regex.Pattern;
019import java.util.stream.Collectors;
020import java.util.stream.Stream;
021
022import javax.crypto.Mac;
023import javax.crypto.spec.SecretKeySpec;
024
025import com.fasterxml.jackson.databind.ObjectMapper;
026import com.plivo.api.exceptions.PlivoXmlException;
027
028public class Utils {
029
030  public static boolean allNotNull(Object... objects) {
031    return Stream.of(objects)
032      .noneMatch(Objects::isNull);
033  }
034
035  public static boolean isSubaccountIdValid(String id) {
036    return id != null && id.startsWith("SA") && id.length() == 20;
037  }
038
039  public static boolean isAccountIdValid(String id) {
040    return id != null && id.startsWith("MA") && id.length() == 20;
041  }
042
043  public static boolean anyNotNull(Object... objects) {
044    return Stream.of(objects)
045      .anyMatch(Objects::nonNull);
046  }
047
048  public static Map<String, Object> objectToMap(ObjectMapper objectMapper, Object object) {
049    Map<String, Object> origMap = objectMapper.convertValue(object, Map.class);
050    Map<String, Object> map = new LinkedHashMap<>();
051    for (Entry<String, Object> entry : origMap.entrySet()) {
052      if (entry.getValue() != null) {
053        if (entry.getValue() instanceof Map) {
054          Map<String, Object> innerEntries = objectMapper.convertValue(entry.getValue(), Map.class);
055          for (Entry<String, Object> innerEntry : innerEntries.entrySet()) {
056            map.put(entry.getKey() + innerEntry.getKey(), innerEntry.getValue());
057          }
058        } else {
059          map.put(entry.getKey(), entry.getValue());
060        }
061      }
062    }
063    return map;
064  }
065
066  private final static String SIGNATURE_ALGORITHM = "HmacSHA256";
067
068  public static String computeSignature(String url, String nonce, String authToken)
069    throws NoSuchAlgorithmException, InvalidKeyException, MalformedURLException, UnsupportedEncodingException {
070    if (!allNotNull(url, nonce, authToken)) {
071      throw new IllegalArgumentException("url, nonce and authToken must be non-null");
072    }
073
074    URL parsedURL = new URL(url);
075    String baseUrl = parsedURL.getProtocol() + "://" + parsedURL.getHost();
076    if (parsedURL.getPort() != -1) {
077      baseUrl += ":" + Integer.toString(parsedURL.getPort());
078    }
079    baseUrl += parsedURL.getPath();
080    String payload = baseUrl + nonce;
081    SecretKeySpec signingKey = new SecretKeySpec(authToken.getBytes("UTF-8"), SIGNATURE_ALGORITHM);
082    Mac mac = Mac.getInstance(SIGNATURE_ALGORITHM);
083    mac.init(signingKey);
084    return new String(Base64.getEncoder().encode(mac.doFinal(payload.getBytes("UTF-8"))));
085  }
086
087  public static boolean validateSignature(String url, String nonce, String signature, String authToken)
088    throws NoSuchAlgorithmException, InvalidKeyException, MalformedURLException, UnsupportedEncodingException {
089    return computeSignature(url, nonce, authToken).equals(signature);
090  }
091
092  private static Map<String, List<String>> getLanguageVoices() {
093    Map<String, List<String>> languageVoices = new HashMap<>();
094    languageVoices.put("arb", new ArrayList<String>(Arrays.asList("Zeina")));
095    languageVoices.put("cmn-CN", new ArrayList<String>(Arrays.asList("Zhiyu")));
096    languageVoices.put("da-DK", new ArrayList<String>(Arrays.asList("Naja", "Mads")));
097    languageVoices.put("nl-NL", new ArrayList<String>(Arrays.asList("Lotte", "Ruben")));
098    languageVoices.put("en-AU", new ArrayList<String>(Arrays.asList("Nicole", "Russell")));
099    languageVoices.put("en-GB", new ArrayList<String>(Arrays.asList("Amy", "Emma", "Brian")));
100    languageVoices.put("en-IN", new ArrayList<String>(Arrays.asList("Raveena", "Aditi")));
101    languageVoices.put("en-US", new ArrayList<String>(Arrays.asList("Joanna", "Salli", "Kendra", "Kimberly", "Ivy", "Matthew", "Justin", "Joey")));
102    languageVoices.put("en-GB-WLS", new ArrayList<String>(Arrays.asList("Geraint")));
103    languageVoices.put("fr-CA", new ArrayList<String>(Arrays.asList("Chantal", "Chantal")));
104    languageVoices.put("fr-FR", new ArrayList<String>(Arrays.asList("Léa", "Céline", "Mathieu")));
105    languageVoices.put("de-DE", new ArrayList<String>(Arrays.asList("Vicki", "Hans")));
106    languageVoices.put("hi-IN", new ArrayList<String>(Arrays.asList("Aditi")));
107    languageVoices.put("is-IS", new ArrayList<String>(Arrays.asList("Dóra", "Karl")));
108    languageVoices.put("it-IT", new ArrayList<String>(Arrays.asList("Carla", "Giorgio")));
109    languageVoices.put("ja-JP", new ArrayList<String>(Arrays.asList("Mizuki", "Takumi")));
110    languageVoices.put("ko-KR", new ArrayList<String>(Arrays.asList("Seoyeon")));
111    languageVoices.put("nb-NO", new ArrayList<String>(Arrays.asList("Liv")));
112    languageVoices.put("pl-PL", new ArrayList<String>(Arrays.asList("Ewa", "Maja", "Jacek", "Jan")));
113    languageVoices.put("pt-BR", new ArrayList<String>(Arrays.asList("Vitória", "Ricardo")));
114    languageVoices.put("pt-PT", new ArrayList<String>(Arrays.asList("Inês", "Cristiano")));
115    languageVoices.put("ro-RO", new ArrayList<String>(Arrays.asList("Carmen")));
116    languageVoices.put("ru-RU", new ArrayList<String>(Arrays.asList("Tatyana", "Maxim")));
117    languageVoices.put("es-ES", new ArrayList<String>(Arrays.asList("Conchita", "Lucia", "Enrique")));
118    languageVoices.put("es-MX", new ArrayList<String>(Arrays.asList("Mia")));
119    languageVoices.put("es-US", new ArrayList<String>(Arrays.asList("Penélope", "Miguel")));
120    languageVoices.put("sv-SE", new ArrayList<String>(Arrays.asList("Astrid")));
121    languageVoices.put("tr-TR", new ArrayList<String>(Arrays.asList("Filiz")));
122    languageVoices.put("cy-GB", new ArrayList<String>(Arrays.asList("Gwyneth")));
123
124    return languageVoices;
125  }
126
127  public static void validateLanguageVoice(String language, String voice) throws PlivoXmlException {
128    String[] voiceParts = voice.split("\\.");
129    System.out.println(language);
130    if (voiceParts.length != 2 || !voiceParts[0].equals("Polly")) {
131      throw new PlivoXmlException("XML Validation Error: Invalid language. Voice " + voice + " is not valid. " +
132                                  "Refer <https://www.plivo.com/docs/voice/getting-started/advanced/getting-started-with-ssml/#ssml-voices> for the list of supported voices.");
133    }
134
135    Map<String, List<String>> languageVoices = getLanguageVoices();
136    // Validate supported languages.
137    if (languageVoices.get(language) == null || languageVoices.get(language).isEmpty()) {
138      throw new PlivoXmlException("XML Validation Error: Invalid language. Language " + language + " is not supported.");
139    }
140
141    // Transform the available language voices and the voice name into a common format.
142    List<String> availableLanguageVoices = languageVoices.get(language);
143    for (int i = 0; i < availableLanguageVoices.size(); i++) {
144      availableLanguageVoices.set(i, transformString(availableLanguageVoices.get(i)));
145    }
146    String transformedVoiceName = transformString(voiceParts[1]);
147
148    if (!voiceParts[1].equals("*") && !availableLanguageVoices.contains(transformedVoiceName) ){
149      throw new PlivoXmlException("XML Validation Error: <Speak> voice '" + voice +
150                                  "' is not valid. Refer <https://www.plivo.com/docs/voice/getting-started/advanced/getting-started-with-ssml/#ssml-voices> for list of supported voices.");
151    }
152  }
153
154  public static String transformString(String s) {
155    String transformedString;
156    // Replace all accented characters with comparable english alphabets
157    transformedString = Normalizer.normalize(s.trim(), Normalizer.Form.NFD);
158    Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
159    transformedString = pattern.matcher(transformedString).replaceAll("");
160
161    // To title case and replace spaces with '_'
162    transformedString = (new ArrayList<>(Arrays.asList(transformedString.toLowerCase().split(" "))))
163      .stream()
164      .map(word -> Character.toTitleCase(word.charAt(0)) + word.substring(1))
165      .collect(Collectors.joining("_"));
166    return transformedString;
167  }
168  
169  
170}