diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolver.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolver.java new file mode 100644 index 0000000000..628f20e6ac --- /dev/null +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolver.java @@ -0,0 +1,93 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2014, Telestax Inc and individual contributors + * by the @authors tag. + * + * This program is free software: you can redistribute it and/or modify + * under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +package org.restcomm.connect.http.client.rcmlserver.resolver; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A relative URI resolver for Rcmlserver/RVD. It values defined in rcmlserver restcomm.xml configuration + * section (rcmlserver.base-url, rcmlserver.api-path) to convert relative urls to absolute i.e. prepent Rcmlserver origin (http://rcmlserver.domain:port). + * By design, the resolver is used when resolving Application.rcmlUrl only. An additional filter prefix + * is used and helps the resolver affect only urls that start with "/restcomm-rvd/". This filter value is + * configurable through rcmlserver.api-path. It will use whatever it between the first pair of slashes "/.../". + * If configuration is missing or rcmlserver is deployed bundled with restcomm the resolver won't affect the + * uri resolved, typically leaving UriUtils class take care of it. + * + * @author otsakir@gmail.com - Orestis Tsakiridis + */ +public class RcmlserverResolver { + protected static Logger logger = Logger.getLogger(RcmlserverResolver.class); + static RcmlserverResolver singleton; + + String rvdOrigin; + String filterPrefix; // a pattern used to match against relative urls in application.rcmlUrl. If null, to resolving will occur. + + static final String DEFAULT_FILTER_PREFIX = "/restcomm-rvd/"; + + // not really a clean singleton pattern but a way to init once and use many. Only the first time this method is called the parameters are used in the initialization + public static RcmlserverResolver getInstance(String rvdOrigin, String apiPath, boolean reinit) { + if (singleton == null || reinit) { + singleton = new RcmlserverResolver(rvdOrigin, apiPath); + } + return singleton; + } + + public static RcmlserverResolver getInstance(String rvdOrigin, String apiPath) { + return getInstance(rvdOrigin, apiPath, false); + } + + public RcmlserverResolver(String rvdOrigin, String apiPath) { + this.rvdOrigin = rvdOrigin; + if ( ! StringUtils.isEmpty(apiPath) ) { + // extract the first part of the path and use it as a filter prefix + Pattern pattern = Pattern.compile("/([^/]*)((/.*)|$)"); + Matcher matcher = pattern.matcher(apiPath); + if (matcher.matches() && matcher.group(1) != null) { + filterPrefix = "/" + matcher.group(1) + "/"; + } else + filterPrefix = DEFAULT_FILTER_PREFIX; + logger.info("RcmlserverResolver initialized. Urls starting with '" + filterPrefix + "' will get prepended with '" + (rvdOrigin == null ? "" : rvdOrigin) + "'"); + } else + filterPrefix = null; + } + + // if rvdOrigin is null no point in trying to resolve RVD location. We will return passed uri instead + public URI resolveRelative(URI uri) { + if (uri != null && rvdOrigin != null && filterPrefix != null) { + if (uri.isAbsolute()) + return uri; + try { + String uriString = uri.toString(); + if (uriString.startsWith(filterPrefix)) + return new URI(rvdOrigin + uri.toString()); + } catch (URISyntaxException e) { + logger.error("Cannot resolve uri: " + uri.toString() + ". Ignoring...", e); + } + } + return uri; + } +} diff --git a/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolverTest.java b/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolverTest.java new file mode 100644 index 0000000000..c8ccbe241c --- /dev/null +++ b/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/client/rcmlserver/resolver/RcmlserverResolverTest.java @@ -0,0 +1,62 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2014, Telestax Inc and individual contributors + * by the @authors tag. + * + * This program is free software: you can redistribute it and/or modify + * under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * + */ + +package org.restcomm.connect.http.client.rcmlserver.resolver; + +import junit.framework.Assert; +import org.junit.Test; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * @author otsakir@gmail.com - Orestis Tsakiridis + */ +public class RcmlserverResolverTest { + @Test + public void testResolving() throws URISyntaxException { + RcmlserverResolver resolver = RcmlserverResolver.getInstance("http://rvdserver.org","/restcomm-rvd/services/", true); + URI uri = resolver.resolveRelative(new URI("/restcomm-rvd/services/apps/AP5f58b0baf6e14001b6eec02295fab05a/controller")); + // relative urls to actual RVD applications should get prefixed + Assert.assertEquals("http://rvdserver.org/restcomm-rvd/services/apps/AP5f58b0baf6e14001b6eec02295fab05a/controller", uri.toString()); + // absolute urls should not be touched + uri = resolver.resolveRelative(new URI("http://externalserver/AP5f58b0baf6e14001b6eec02295fab05a/controller")); + Assert.assertEquals("http://externalserver/AP5f58b0baf6e14001b6eec02295fab05a/controller", uri.toString()); + // relative urls pointing to other (non-rvd) apps + uri = resolver.resolveRelative(new URI("/restcomm/demos/hellp-play.xml")); + Assert.assertEquals("/restcomm/demos/hellp-play.xml", uri.toString()); + // make sure that RVD path can vary. Assume it's '/new-rvd' now + resolver = RcmlserverResolver.getInstance("http://rvdserver.org","/new-rvd/services/", true); + uri = resolver.resolveRelative(new URI("/new-rvd/myapp.xml")); + Assert.assertEquals("http://rvdserver.org/new-rvd/myapp.xml", uri.toString()); + // if rcmlserver.baseUrl is null, no resolving should occur + resolver = RcmlserverResolver.getInstance(null,"/restcomm-rvd/services", true); + uri = resolver.resolveRelative(new URI("/restcomm-rvd/services/apps/xxxx")); + Assert.assertEquals("/restcomm-rvd/services/apps/xxxx", uri.toString()); + // if rcmlserver.apiPath is null or empty, no resolving should occur + resolver = RcmlserverResolver.getInstance("http://rvdotsakir.org","", true); + uri = resolver.resolveRelative(new URI("/restcomm-rvd/services/apps/xxxx")); + Assert.assertEquals("/restcomm-rvd/services/apps/xxxx", uri.toString()); + // all nulls + resolver = RcmlserverResolver.getInstance(null,null, true); + uri = resolver.resolveRelative(new URI("/restcomm-rvd/services/apps/xxxx")); + Assert.assertEquals("/restcomm-rvd/services/apps/xxxx", uri.toString()); + } +} diff --git a/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/SmsService.java b/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/SmsService.java index e58b3815cb..44a4bfc464 100644 --- a/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/SmsService.java +++ b/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/SmsService.java @@ -31,6 +31,8 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; import org.apache.commons.configuration.Configuration; import org.joda.time.DateTime; +import org.restcomm.connect.commons.configuration.RestcommConfiguration; +import org.restcomm.connect.commons.configuration.sets.RcmlserverConfigurationSet; import org.restcomm.connect.commons.dao.Sid; import org.restcomm.connect.commons.faulttolerance.RestcommUntypedActor; import org.restcomm.connect.commons.util.UriUtils; @@ -53,6 +55,7 @@ import org.restcomm.connect.extension.api.RestcommExtensionException; import org.restcomm.connect.extension.api.RestcommExtensionGeneric; import org.restcomm.connect.extension.controller.ExtensionController; +import org.restcomm.connect.http.client.rcmlserver.resolver.RcmlserverResolver; import org.restcomm.connect.interpreter.SmsInterpreter; import org.restcomm.connect.interpreter.SmsInterpreterParams; import org.restcomm.connect.interpreter.StartInterpreter; @@ -299,7 +302,9 @@ private boolean redirectToHostedSmsApp(final ActorRef self, final SipServletRequ final Sid sid = number.getSmsApplicationSid(); if (sid != null) { final Application application = applications.getApplication(sid); - builder.setUrl(UriUtils.resolve(application.getRcmlUrl())); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver resolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + builder.setUrl(UriUtils.resolve(resolver.resolveRelative(application.getRcmlUrl()))); } else { builder.setUrl(UriUtils.resolve(appUri)); } diff --git a/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/smpp/SmppMessageHandler.java b/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/smpp/SmppMessageHandler.java index e00354ca0a..7b1b595e63 100644 --- a/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/smpp/SmppMessageHandler.java +++ b/restcomm/restcomm.sms/src/main/java/org/restcomm/connect/sms/smpp/SmppMessageHandler.java @@ -18,6 +18,8 @@ import com.cloudhopper.smpp.type.UnrecoverablePduException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.apache.commons.configuration.Configuration; +import org.restcomm.connect.commons.configuration.RestcommConfiguration; +import org.restcomm.connect.commons.configuration.sets.RcmlserverConfigurationSet; import org.restcomm.connect.commons.dao.Sid; import org.restcomm.connect.commons.faulttolerance.RestcommUntypedActor; import org.restcomm.connect.commons.util.UriUtils; @@ -32,6 +34,7 @@ import org.restcomm.connect.extension.api.RestcommExtensionException; import org.restcomm.connect.extension.api.RestcommExtensionGeneric; import org.restcomm.connect.extension.controller.ExtensionController; +import org.restcomm.connect.http.client.rcmlserver.resolver.RcmlserverResolver; import org.restcomm.connect.interpreter.StartInterpreter; import org.restcomm.connect.monitoringservice.MonitoringService; import org.restcomm.connect.sms.SmsSession; @@ -166,7 +169,9 @@ private boolean redirectToHostedSmsApp(final ActorRef self, final SmppInboundMes final Sid sid = number.getSmsApplicationSid(); if (sid != null) { final Application application = applications.getApplication(sid); - builder.setUrl(UriUtils.resolve(application.getRcmlUrl())); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver resolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + builder.setUrl(UriUtils.resolve(resolver.resolveRelative(application.getRcmlUrl()))); } else if (appUri != null) { builder.setUrl(UriUtils.resolve(appUri)); } else { diff --git a/restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/CallManager.java b/restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/CallManager.java index 46c37fe15c..4ca9f6182c 100644 --- a/restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/CallManager.java +++ b/restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/CallManager.java @@ -38,6 +38,7 @@ import org.apache.commons.configuration.HierarchicalConfiguration; import org.joda.time.DateTime; import org.restcomm.connect.commons.configuration.RestcommConfiguration; +import org.restcomm.connect.commons.configuration.sets.RcmlserverConfigurationSet; import org.restcomm.connect.commons.dao.Sid; import org.restcomm.connect.commons.faulttolerance.RestcommUntypedActor; import org.restcomm.connect.commons.patterns.StopObserving; @@ -65,6 +66,7 @@ import org.restcomm.connect.extension.api.RestcommExtensionException; import org.restcomm.connect.extension.api.RestcommExtensionGeneric; import org.restcomm.connect.extension.controller.ExtensionController; +import org.restcomm.connect.http.client.rcmlserver.resolver.RcmlserverResolver; import org.restcomm.connect.interpreter.StartInterpreter; import org.restcomm.connect.interpreter.StopInterpreter; import org.restcomm.connect.interpreter.VoiceInterpreter; @@ -1124,7 +1126,9 @@ private void transfer(SipServletRequest request) throws Exception { if (number.getReferApplicationSid() != null) { Application application = storage.getApplicationsDao().getApplication(number.getReferApplicationSid()); - builder.setUrl(UriUtils.resolve(application.getRcmlUrl())); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver resolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + builder.setUrl(UriUtils.resolve(resolver.resolveRelative(application.getRcmlUrl()))); } else { builder.setUrl(UriUtils.resolve(number.getReferUrl())); } @@ -1229,7 +1233,9 @@ private boolean redirectToHostedVoiceApp(final ActorRef self, final SipServletRe final Sid sid = number.getVoiceApplicationSid(); if (sid != null) { final Application application = applications.getApplication(sid); - builder.setUrl(UriUtils.resolve(application.getRcmlUrl())); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver rcmlserverResolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + builder.setUrl(UriUtils.resolve(rcmlserverResolver.resolveRelative(application.getRcmlUrl()))); } else { builder.setUrl(UriUtils.resolve(number.getVoiceUrl())); } @@ -1287,7 +1293,9 @@ private boolean redirectToClientVoiceApp(final ActorRef self, final SipServletRe URI clientAppVoiceUrl = null; if (applicationSid != null) { final Application application = applications.getApplication(applicationSid); - clientAppVoiceUrl = UriUtils.resolve(application.getRcmlUrl()); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver resolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + clientAppVoiceUrl = UriUtils.resolve(resolver.resolveRelative(application.getRcmlUrl())); } if (clientAppVoiceUrl == null) { clientAppVoiceUrl = client.getVoiceUrl(); diff --git a/restcomm/restcomm.ussd/src/main/java/org/restcomm/connect/ussd/telephony/UssdCallManager.java b/restcomm/restcomm.ussd/src/main/java/org/restcomm/connect/ussd/telephony/UssdCallManager.java index f4a8cd57d3..aa0490b4e7 100644 --- a/restcomm/restcomm.ussd/src/main/java/org/restcomm/connect/ussd/telephony/UssdCallManager.java +++ b/restcomm/restcomm.ussd/src/main/java/org/restcomm/connect/ussd/telephony/UssdCallManager.java @@ -26,6 +26,8 @@ import akka.event.Logging; import akka.event.LoggingAdapter; import org.apache.commons.configuration.Configuration; +import org.restcomm.connect.commons.configuration.RestcommConfiguration; +import org.restcomm.connect.commons.configuration.sets.RcmlserverConfigurationSet; import org.restcomm.connect.commons.dao.Sid; import org.restcomm.connect.commons.faulttolerance.RestcommUntypedActor; import org.restcomm.connect.commons.util.UriUtils; @@ -36,6 +38,7 @@ import org.restcomm.connect.dao.entities.Account; import org.restcomm.connect.dao.entities.Application; import org.restcomm.connect.dao.entities.IncomingPhoneNumber; +import org.restcomm.connect.http.client.rcmlserver.resolver.RcmlserverResolver; import org.restcomm.connect.interpreter.StartInterpreter; import org.restcomm.connect.telephony.api.CallManagerResponse; import org.restcomm.connect.telephony.api.CreateCall; @@ -217,7 +220,9 @@ private boolean redirectToHostedVoiceApp(final ActorRef self, final SipServletRe final Sid sid = number.getUssdApplicationSid(); if (sid != null) { final Application application = applications.getApplication(sid); - builder.setUrl(UriUtils.resolve(application.getRcmlUrl())); + RcmlserverConfigurationSet rcmlserverConfig = RestcommConfiguration.getInstance().getRcmlserver(); + RcmlserverResolver resolver = RcmlserverResolver.getInstance(rcmlserverConfig.getBaseUrl(), rcmlserverConfig.getApiPath()); + builder.setUrl(UriUtils.resolve(resolver.resolveRelative(application.getRcmlUrl()))); } else { builder.setUrl(UriUtils.resolve(number.getUssdUrl())); }