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}