From b1f82c7554e21277ae2c05a2b6b64f6c56bb3acc Mon Sep 17 00:00:00 2001 From: Johanes Soetanto Date: Wed, 19 Dec 2012 15:43:02 +1100 Subject: [PATCH 1/4] INTEXT-30 - Add SMPP Support - migrate old sandbox project - add schema for channel adapters and gateways - change some package structure based on spring integration module package structure --- spring-integration-smpp/README.md | 63 ++ spring-integration-smpp/build.gradle | 323 ++++++++++ spring-integration-smpp/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 39770 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + spring-integration-smpp/gradlew | 164 +++++ spring-integration-smpp/gradlew.bat | 90 +++ spring-integration-smpp/publish-maven.gradle | 61 ++ spring-integration-smpp/src/api/overview.html | 22 + .../src/dist/changelog.txt | 12 + spring-integration-smpp/src/dist/license.txt | 201 ++++++ spring-integration-smpp/src/dist/notice.txt | 21 + spring-integration-smpp/src/dist/readme.txt | 13 + .../xml/SmppInboundChannelAdapterParser.java | 55 ++ .../config/xml/SmppInboundGatewayParser.java | 65 ++ .../smpp/config/xml/SmppNamespaceHandler.java | 38 ++ .../xml/SmppOutboundChannelAdapterParser.java | 52 ++ .../config/xml/SmppOutboundGatewayParser.java | 52 ++ .../smpp/config/xml/SmppParserUtils.java | 133 ++++ .../smpp/config/xml/package-info.java | 4 + .../AbstractReceivingMessageListener.java | 61 ++ .../smpp/core/SmesMessageSpecification.java | 579 ++++++++++++++++++ .../integration/smpp/core/SmppConstants.java | 52 ++ .../integration/smpp/core/SmppHeaders.java | 34 + .../integration/smpp/core/package-info.java | 4 + .../inbound/SmppInboundChannelAdapter.java | 87 +++ .../smpp/inbound/SmppInboundGateway.java | 134 ++++ .../smpp/inbound/package-info.java | 4 + .../outbound/SmppOutboundChannelAdapter.java | 106 ++++ .../smpp/outbound/SmppOutboundGateway.java | 102 +++ .../smpp/outbound/package-info.java | 4 + .../integration/smpp/package-info.java | 4 + .../DelegatingMessageReceiverListener.java | 52 ++ .../smpp/session/ExtendedSmppSession.java | 37 ++ .../ExtendedSmppSessionAdaptingDelegate.java | 170 +++++ .../smpp/session/SmppSessionFactoryBean.java | 356 +++++++++++ .../smpp/session/package-info.java | 4 + .../util/CurrentExecutingMethodHolder.java | 44 ++ ...urrentMethodExposingMethodInterceptor.java | 27 + .../integration/smpp/util/package-info.java | 4 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 2 + .../main/resources/META-INF/spring.tooling | 4 + .../src/main/resources/log4j.properties | 9 + .../src/main/resources/logback.xml | 11 + .../xml/spring-integration-smpp-2.2.xsd | 439 +++++++++++++ .../config/xml/spring-integration-smpp.gif | Bin 0 -> 572 bytes .../src/main/resources/todo.txt | 1 + .../src/reference/docbook/history.xml | 19 + .../src/reference/docbook/images/logo.png | Bin 0 -> 17393 bytes .../src/reference/docbook/index.xml | 66 ++ .../src/reference/docbook/resources.xml | 32 + .../src/reference/docbook/smpp.xml | 85 +++ .../src/reference/docbook/whats-new.xml | 24 + .../integration/smpp/TestSmppConnection.java | 76 +++ .../smpp/TestSmppInboundChannelAdapter.java | 75 +++ .../smpp/TestSmppInboundGateway.java | 90 +++ .../smpp/TestSmppOutboundChannelAdapter.java | 60 ++ .../smpp/TestSmppOutboundGateway.java | 59 ++ .../smpp/TestSmppSessionFactoryBean.java | 128 ++++ .../config/xml/MockSmppSessionFactory.java | 30 + .../SmppInboundChannelAdapterParserTests.java | 66 ++ .../xml/SmppInboundGatewayParserTests.java | 94 +++ ...SmppOutboundChannelAdapterParserTests.java | 92 +++ .../xml/SmppOutboundGatewayParserTests.java | 89 +++ .../TestCurrentExecutingMethodAdvice.java | 40 ++ .../resources/TestSmppConnection-context.xml | 46 ++ .../TestSmppInboundChannelAdapter-context.xml | 60 ++ .../TestSmppInboundGateway-context.xml | 64 ++ ...TestSmppOutboundChannelAdapter-context.xml | 31 + .../TestSmppOutboundGateway-context.xml | 31 + .../TestSmppSessionFactoryBean-context.xml | 25 + .../src/test/resources/log4j.properties | 8 + .../SmppInboundChannelAdapterParserTests.xml | 27 + .../xml/SmppInboundGatewayParserTests.xml | 55 ++ .../SmppOutboundChannelAdapterParserTests.xml | 40 ++ .../xml/SmppOutboundGatewayParserTests.xml | 45 ++ .../src/test/resources/readme.txt | 10 + .../src/test/resources/smpp.properties | 7 + 79 files changed, 5282 insertions(+) create mode 100644 spring-integration-smpp/README.md create mode 100644 spring-integration-smpp/build.gradle create mode 100644 spring-integration-smpp/gradle.properties create mode 100644 spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar create mode 100644 spring-integration-smpp/gradle/wrapper/gradle-wrapper.properties create mode 100755 spring-integration-smpp/gradlew create mode 100644 spring-integration-smpp/gradlew.bat create mode 100644 spring-integration-smpp/publish-maven.gradle create mode 100644 spring-integration-smpp/src/api/overview.html create mode 100644 spring-integration-smpp/src/dist/changelog.txt create mode 100644 spring-integration-smpp/src/dist/license.txt create mode 100644 spring-integration-smpp/src/dist/notice.txt create mode 100644 spring-integration-smpp/src/dist/readme.txt create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java create mode 100644 spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java create mode 100644 spring-integration-smpp/src/main/resources/META-INF/spring.handlers create mode 100644 spring-integration-smpp/src/main/resources/META-INF/spring.schemas create mode 100644 spring-integration-smpp/src/main/resources/META-INF/spring.tooling create mode 100644 spring-integration-smpp/src/main/resources/log4j.properties create mode 100644 spring-integration-smpp/src/main/resources/logback.xml create mode 100644 spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd create mode 100644 spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif create mode 100644 spring-integration-smpp/src/main/resources/todo.txt create mode 100644 spring-integration-smpp/src/reference/docbook/history.xml create mode 100644 spring-integration-smpp/src/reference/docbook/images/logo.png create mode 100644 spring-integration-smpp/src/reference/docbook/index.xml create mode 100644 spring-integration-smpp/src/reference/docbook/resources.xml create mode 100644 spring-integration-smpp/src/reference/docbook/smpp.xml create mode 100644 spring-integration-smpp/src/reference/docbook/whats-new.xml create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java create mode 100644 spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java create mode 100644 spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml create mode 100644 spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml create mode 100644 spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml create mode 100644 spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml create mode 100644 spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml create mode 100644 spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml create mode 100644 spring-integration-smpp/src/test/resources/log4j.properties create mode 100644 spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml create mode 100644 spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml create mode 100644 spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml create mode 100644 spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml create mode 100644 spring-integration-smpp/src/test/resources/readme.txt create mode 100644 spring-integration-smpp/src/test/resources/smpp.properties diff --git a/spring-integration-smpp/README.md b/spring-integration-smpp/README.md new file mode 100644 index 0000000..42f692a --- /dev/null +++ b/spring-integration-smpp/README.md @@ -0,0 +1,63 @@ +Spring Integration Smpp Adapter +================================================= + +The Spring Integration Smpp allows you to receive/send sms to SMSC using SMPP protocol. This Spring Integration +extension is migrated from Spring's sandbox spring-integration-smpp project. + +# Components + +* Outbound Channel Adapter +* Outbound Gateway +* Inbound Channel Adapter +* Inbound Gateway + + +## More to come... + +# Building + +If you encounter out of memory errors during the build, increase available heap and permgen for Gradle: + + GRADLE_OPTS='-XX:MaxPermSize=1024m -Xmx1024m' + +To build and install jars into your local Maven cache: + + ./gradlew install + +To build api Javadoc (results will be in `build/api`): + + ./gradlew api + +To build reference documentation (results will be in `build/reference`): + + ./gradlew reference + +To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`) + + ./gradlew dist + +# Using SpringSource Tool Suite + + Gradle projects can be directly imported into STS + +# Using PLain Eclipse + +To generate Eclipse metadata (.classpath and .project files), do the following: + + ./gradlew eclipse + +Once complete, you may then import the projects into Eclipse as usual: + + *File -> Import -> Existing projects into workspace* + +Browse to the *'spring-integration'* root directory. All projects should import +free of errors. + +# Using IntelliJ IDEA + +To generate IDEA metadata (.iml and .ipr files), do the following: + + ./gradlew idea + +For more information, please visit the Spring Integration website at: +[http://www.springsource.org/spring-integration](http://www.springsource.org/spring-integration) diff --git a/spring-integration-smpp/build.gradle b/spring-integration-smpp/build.gradle new file mode 100644 index 0000000..afcbe4a --- /dev/null +++ b/spring-integration-smpp/build.gradle @@ -0,0 +1,323 @@ +description = 'Spring Integration Smpp Adapter' + +buildscript { + repositories { + maven { url 'https://repo.springsource.org/plugins-snapshot' } + } + dependencies { + classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.1.5' + } +} + +apply plugin: 'java' +apply from: "${rootProject.projectDir}/publish-maven.gradle" +apply plugin: 'eclipse' +apply plugin: 'idea' + +group = 'org.springframework.integration.smpp' + +repositories { + maven { url 'http://repo.springsource.org/libs-milestone' } + maven { url 'http://repo.springsource.org/plugins-release' } // for bundlor + maven { url 'http://repo.maven.apache.org/maven2'} + maven { url 'https://source.sakaiproject.org/maven2'} +} + +// ensure JDK 5 compatibility (GRADLE-18; INT-1578) +sourceCompatibility=1.6 +targetCompatibility=1.6 + +ext { + jsmppVersion = '2.1.0' + slf4jVersion = '1.6.6' + commonsLangVersion = '2.6' + commonsBeanUtilsVersion= '1.8.3' + aspectjVersion = '1.6.8' + cglibVersion = '2.2' + commonsNetVersion = '3.0.1' + easymockVersion = '2.3' + groovyVersion = '1.8.5' + jacksonVersion = '1.9.2' + javaxActivationVersion = '1.1.1' + junitVersion = '4.8.2' + log4jVersion = '1.2.12' + mockitoVersion = '1.9.0' + // When changing Spring Versions - don't forget to update bundlor ranges + springVersion = '3.2.0.RELEASE' + springIntegrationVersion = '2.2.0.RELEASE' + springAmqpVersion = '1.1.0.RELEASE' + springDataMongoVersion = '1.1.0.M1' + springDataRedisVersion = '1.0.0.RELEASE' + springGemfireVersion = '1.1.1.RELEASE' + springSecurityVersion = '3.1.0.RELEASE' + springSocialTwitterVersion = '1.0.1.RELEASE' + springWsVersion = '2.0.3.RELEASE' +} + +dependencies { + compile "com.google.code:jsmpp:$jsmppVersion" + compile "org.slf4j:slf4j-log4j12:$slf4jVersion" + compile "commons-lang:commons-lang:$commonsLangVersion" + compile "commons-beanutils:commons-beanutils:$commonsBeanUtilsVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + compile "org.springframework.integration:spring-integration-ws:$springIntegrationVersion" + compile "org.springframework.integration:spring-integration-event:$springIntegrationVersion" + compile "org.springframework:spring-beans:$springVersion" + compile "org.springframework:spring-context:$springVersion" + compile "org.springframework:spring-context-support:$springVersion" + compile "org.springframework:spring-aop:$springVersion" + compile "org.springframework:spring-tx:$springVersion" + compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) + compile("org.springframework.amqp:spring-rabbit:$springAmqpVersion") { + exclude group: 'org.springframework', module: 'spring-aop' + exclude group: 'org.springframework', module: 'spring-beans' + exclude group: 'org.springframework', module: 'spring-context' + exclude group: 'org.springframework', module: 'spring-core' + exclude group: 'org.springframework', module: 'spring-oxm' + exclude group: 'org.springframework', module: 'spring-tx' + } + + testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" +} + + +eclipse { + project { + natures += 'org.springframework.ide.eclipse.core.springnature' + } +} + +sourceSets { + test { + resources { + srcDirs = ['src/test/resources', 'src/test/java'] + } + } +} + +// See http://www.gradle.org/docs/current/userguide/dependency_management.html#sub:configurations +// and http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html +configurations { + jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo +} + +// dependencies that are common across all java projects +dependencies { + testCompile "cglib:cglib-nodep:$cglibVersion" + testCompile "junit:junit-dep:$junitVersion" + testCompile "log4j:log4j:$log4jVersion" + testCompile "org.easymock:easymock:$easymockVersion" + testCompile "org.easymock:easymockclassextension:$easymockVersion" + testCompile "org.hamcrest:hamcrest-all:1.1" + testCompile "org.mockito:mockito-all:$mockitoVersion" + testCompile "org.springframework:spring-test:$springVersion" + jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.5.6.201201232323", classifier: "runtime" +} + +// enable all compiler warnings; individual projects may customize further +ext.xLintArg = '-Xlint:all' +[compileJava, compileTestJava]*.options*.compilerArgs = [xLintArg] + +test { + // suppress all console output during testing unless running `gradle -i` + logging.captureStandardOutput(LogLevel.INFO) + jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=*" +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allJava +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +apply plugin: 'docbook-reference' + +reference { + sourceDir = file('src/reference/docbook') +} + +apply plugin: 'sonar' + +sonar { + + if (rootProject.hasProperty('sonarHostUrl')) { + server.url = rootProject.sonarHostUrl + } + + database { + if (rootProject.hasProperty('sonarJdbcUrl')) { + url = rootProject.sonarJdbcUrl + } + if (rootProject.hasProperty('sonarJdbcDriver')) { + driverClassName = rootProject.sonarJdbcDriver + } + if (rootProject.hasProperty('sonarJdbcUsername')) { + username = rootProject.sonarJdbcUsername + } + if (rootProject.hasProperty('sonarJdbcPassword')) { + password = rootProject.sonarJdbcPassword + } + } + + project { + dynamicAnalysis = "reuseReports" + withProjectProperties { props -> + props["sonar.core.codeCoveragePlugin"] = "jacoco" + props["sonar.jacoco.reportPath"] = "${buildDir.name}/jacoco.exec" + } + } + + logger.info("Sonar parameters used: server.url='${server.url}'; database.url='${database.url}'; database.driverClassName='${database.driverClassName}'; database.username='${database.username}'") +} + +task api(type: Javadoc) { + group = 'Documentation' + description = 'Generates aggregated Javadoc API documentation.' + title = "${rootProject.description} ${version} API" + options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED + options.author = true + options.header = rootProject.description + options.overview = 'src/api/overview.html' + source subprojects.collect { project -> + project.sourceSets.main.allJava + } + destinationDir = new File(buildDir, "api") + classpath = files(subprojects.collect { project -> + project.sourceSets.main.compileClasspath + }) +} + +task schemaZip(type: Zip) { + group = 'Distribution' + classifier = 'schema' + description = "Builds -${classifier} archive containing all " + + "XSDs for deployment at static.springframework.org/schema." + + subprojects.each { subproject -> + def Properties schemas = new Properties(); + def shortName = subproject.name.replaceFirst("${rootProject.name}-", '') + if (subproject.name.endsWith("-core")) { + shortName = '' + } + + subproject.sourceSets.main.resources.find { + it.path.endsWith('META-INF/spring.schemas') + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + File xsdFile = subproject.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) + } + assert xsdFile != null + into ("integration/${shortName}") { + from xsdFile.path + } + } + } +} + +task docsZip(type: Zip) { + group = 'Distribution' + classifier = 'docs' + description = "Builds -${classifier} archive containing api and reference " + + "for deployment at static.springframework.org/spring-integration/docs." + + from('src/dist') { + include 'changelog.txt' + } + + from (api) { + into 'api' + } + + from (reference) { + into 'reference' + } +} + +task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { + group = 'Distribution' + classifier = 'dist' + description = "Builds -${classifier} archive, containing all jars and docs, " + + "suitable for community download page." + + ext.baseDir = "${project.name}-${project.version}"; + + from('src/dist') { + include 'readme.txt' + include 'license.txt' + include 'notice.txt' + into "${baseDir}" + } + + from(zipTree(docsZip.archivePath)) { + into "${baseDir}/docs" + } + + from(zipTree(schemaZip.archivePath)) { + into "${baseDir}/schema" + } + + subprojects.each { subproject -> + into ("${baseDir}/libs") { + from subproject.jar + from subproject.sourcesJar + from subproject.javadocJar + } + } +} + +// Create an optional "with dependencies" distribution. +// Not published by default; only for use when building from source. +task depsZip(type: Zip, dependsOn: distZip) { zipTask -> + group = 'Distribution' + classifier = 'dist-with-deps' + description = "Builds -${classifier} archive, containing everything " + + "in the -${distZip.classifier} archive plus all dependencies." + + from zipTree(distZip.archivePath) + + gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.hasTask(":${zipTask.name}")) { + def projectNames = rootProject.subprojects*.name + def artifacts = new HashSet() + subprojects.each { subproject -> + subproject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact -> + def dependency = artifact.moduleVersion.id + if (!projectNames.contains(dependency.name)) { + artifacts << artifact.file + } + } + } + + zipTask.from(artifacts) { + into "${distZip.baseDir}/deps" + } + } + } +} + +artifacts { + archives distZip + archives docsZip + archives schemaZip +} + +task dist(dependsOn: assemble) { + group = 'Distribution' + description = 'Builds -dist, -docs and -schema distribution archives.' +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '1.0-rc-3' +} diff --git a/spring-integration-smpp/gradle.properties b/spring-integration-smpp/gradle.properties new file mode 100644 index 0000000..bebfcbc --- /dev/null +++ b/spring-integration-smpp/gradle.properties @@ -0,0 +1 @@ +version=1.0.0.BUILD-SNAPSHOT diff --git a/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..81dcde627d5cfbc12c96dc5113aac79b66a15118 GIT binary patch literal 39770 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" +APP_HOME="`pwd -P`" +cd "$SAVED" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/spring-integration-smpp/gradlew.bat b/spring-integration-smpp/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/spring-integration-smpp/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-integration-smpp/publish-maven.gradle b/spring-integration-smpp/publish-maven.gradle new file mode 100644 index 0000000..1f053ad --- /dev/null +++ b/spring-integration-smpp/publish-maven.gradle @@ -0,0 +1,61 @@ +apply plugin: 'maven' + +ext.optionalDeps = [] +ext.providedDeps = [] + +ext.optional = { optionalDeps << it } +ext.provided = { providedDeps << it } + +install { + repositories.mavenInstaller { + customizePom(pom, project) + } +} + +def customizePom(pom, gradleProject) { + pom.whenConfigured { generatedPom -> + // respect 'optional' and 'provided' dependencies + gradleProject.optionalDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.optional = true + } + gradleProject.providedDeps.each { dep -> + generatedPom.dependencies.find { it.artifactId == dep.name }?.scope = 'provided' + } + + // eliminate test-scoped dependencies (no need in maven central poms) + generatedPom.dependencies.removeAll { dep -> + dep.scope == 'test' + } + + // add all items necessary for maven central publication + generatedPom.project { + name = gradleProject.description + description = gradleProject.description + url = 'https://github.com/SpringSource/spring-integration' + organization { + name = 'SpringSource' + url = 'http://springsource.org' + } + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + url = 'https://github.com/SpringSource/spring-integration' + connection = 'scm:git:git://github.com/SpringSource/spring-integration' + developerConnection = 'scm:git:git://github.com/SpringSource/spring-integration' + } + + developers { + developer { + id = 'not specified' + name = 'Johanes Soetanto' + email = 'not specified' + } + } + } + } +} diff --git a/spring-integration-smpp/src/api/overview.html b/spring-integration-smpp/src/api/overview.html new file mode 100644 index 0000000..e2c6bf5 --- /dev/null +++ b/spring-integration-smpp/src/api/overview.html @@ -0,0 +1,22 @@ + + +This document is the API specification for Spring Integration Smpp Module +
+
+

+ For further API reference and developer documentation, see the + Spring + Integration reference documentation. + That documentation contains more detailed, developer-targeted + descriptions, with conceptual overviews, definitions of terms, + workarounds, and working code examples. +

+ +

+ If you are interested in commercial training, consultancy, and + support for Spring Integration, please visit + http://www.springsource.com +

+
+ + diff --git a/spring-integration-smpp/src/dist/changelog.txt b/spring-integration-smpp/src/dist/changelog.txt new file mode 100644 index 0000000..7e74bd5 --- /dev/null +++ b/spring-integration-smpp/src/dist/changelog.txt @@ -0,0 +1,12 @@ +Spring Integration Smpp Adapter CHANGELOG +========================================= + +Feature in version 1.0.0 + +1. Inbound channel adapter + +2. Outbound channel adapter + +3. Inbound gateway + +4. Outbound gateway \ No newline at end of file diff --git a/spring-integration-smpp/src/dist/license.txt b/spring-integration-smpp/src/dist/license.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/spring-integration-smpp/src/dist/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/spring-integration-smpp/src/dist/notice.txt b/spring-integration-smpp/src/dist/notice.txt new file mode 100644 index 0000000..f62045a --- /dev/null +++ b/spring-integration-smpp/src/dist/notice.txt @@ -0,0 +1,21 @@ + ======================================================================== + == NOTICE file corresponding to section 4 d of the Apache License, == + == Version 2.0, in this case for the Spring Integration distribution. == + ======================================================================== + + This product includes software developed by + the Apache Software Foundation (http://www.apache.org). + + The end-user documentation included with a redistribution, if any, + must include the following acknowledgement: + + "This product includes software developed by the Spring Framework + Project (http://www.springframework.org)." + + Alternatively, this acknowledgement may appear in the software itself, + if and wherever such third-party acknowledgements normally appear. + + The names "Spring", "Spring Framework", and "Spring Integration" must + not be used to endorse or promote products derived from this software + without prior written permission. For written permission, please contact + enquiries@springsource.com. diff --git a/spring-integration-smpp/src/dist/readme.txt b/spring-integration-smpp/src/dist/readme.txt new file mode 100644 index 0000000..fb6fce8 --- /dev/null +++ b/spring-integration-smpp/src/dist/readme.txt @@ -0,0 +1,13 @@ +Spring Integration Smpp Adapter +----------------------------------- + +To find out what has changed since any earlier releases, see 'changelog.txt'. + +Please consult the documentation located within the 'docs/reference' directory +of this release and also visit the official Spring Integration home at +http://www.springsource.org/spring-integration + +There you will find links to the forum, issue tracker, and several other resources. + +See https://github.com/SpringSource/spring-integration#readme for additional +information including instructions on building from source. diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java new file mode 100644 index 0000000..e5c4f29 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParser.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter; +import org.w3c.dom.Element; + +/** + * The Smpp Inbound Channel adapter parser + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundChannelAdapterParser extends AbstractChannelAdapterParser { + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition doParse(Element e, ParserContext context, String channelName) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder + .genericBeanDefinition(SmppInboundChannelAdapter.class); + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", context, builder); + builder.addPropertyReference("channel", channelName); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "auto-startup","autoStartup"); + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java new file mode 100644 index 0000000..3683e6f --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParser.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractInboundGatewayParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.inbound.SmppInboundGateway; +import org.w3c.dom.Element; + +/** + * The Parser for Smpp Inbound Gateway. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundGatewayParser extends AbstractInboundGatewayParser { + + @Override + protected Class getBeanClass(Element element) { + return SmppInboundGateway.class; + } + + @Override + protected boolean isEligibleAttribute(String n) { + return !n.equals("source-address") && !n.equals("source-ton") && !n.equals("smpp-session-ref") + && !n.equals("request-mapper") && !n.equals("reply-mapper") + && super.isEligibleAttribute(n); + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + // because session need parserContext + SmppParserUtils.setSession(element, "smpp-session-ref", "session", "smppSession", parserContext, builder); + } + + @Override + protected void doPostProcess(BeanDefinitionBuilder builder, Element e) { + // value + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "reply-timeout", "replyTimeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "request-timeout", "requestTimeout"); + + // reference + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "request-mapper", "requestMapper"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "reply-mapper", "replyMapper"); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java new file mode 100644 index 0000000..4a0d3d3 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppNamespaceHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * The namespace handler for the Smpp namespace + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + /* (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + this.registerBeanDefinitionParser("inbound-channel-adapter", new SmppInboundChannelAdapterParser()); + this.registerBeanDefinitionParser("outbound-channel-adapter", new SmppOutboundChannelAdapterParser()); + this.registerBeanDefinitionParser("inbound-gateway", new SmppInboundGatewayParser()); + this.registerBeanDefinitionParser("outbound-gateway", new SmppOutboundGatewayParser()); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java new file mode 100644 index 0000000..143a9a4 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter; +import org.w3c.dom.Element; + +/** + * The parser for the Smpp Outbound Channel Adapter. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition parseConsumer(Element e, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SmppOutboundChannelAdapter.class); + // value attributes + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + // reference attributes + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", parserContext, builder); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "time-formatter", "timeFormatter"); + return builder.getBeanDefinition(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java new file mode 100644 index 0000000..4450650 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.smpp.outbound.SmppOutboundGateway; +import org.w3c.dom.Element; + +/** + * The Parser for Smpp Outbound Gateway. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundGatewayParser extends AbstractConsumerEndpointParser { + @Override + protected BeanDefinitionBuilder parseHandler(Element e, ParserContext parserContext) { + final BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SmppOutboundGateway.class); + // value attributes + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "source-address", "defaultSourceAddress"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "reply-timeout", "sendTimeout"); + SmppParserUtils.setTon(e, "source-ton", "defaultSourceAddressTypeOfNumber", builder); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, e, "order"); + // reference attributes + SmppParserUtils.setSession(e, "smpp-session-ref", "session", "smppSession", parserContext, builder); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "reply-channel", "outputChannel"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, e, "time-formatter", "timeFormatter"); + return builder; + } + + @Override + protected String getInputChannelAttributeName() { + return "request-channel"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java new file mode 100644 index 0000000..4a877ec --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/SmppParserUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.SMPPConstant; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.NumberingPlanIndicator; +import org.jsmpp.bean.TypeOfNumber; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Contains various utility methods for parsing Smpp Adapter + * specific namespace elements as well as for the generation of the the + * respective {@link BeanDefinition}s. + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public final class SmppParserUtils { + + /** Prevent instantiation. */ + private SmppParserUtils() { + throw new AssertionError(); + } + + static void setSession(Element e, String sessionAttribute, String sessionChild, String propName, + ParserContext context, BeanDefinitionBuilder builder) { + final String sessionRef = e.getAttribute(sessionAttribute); + final Element sessionElement = DomUtils.getChildElementByTagName(e, sessionChild); + if(StringUtils.hasText(sessionRef)) { + if(sessionElement != null) { + context.getReaderContext().error("Child element '"+sessionChild+"' is not allowed when attribute '" + +sessionAttribute+"' has been specified", context.extractSource(sessionElement)); + } + builder.addPropertyReference(propName, sessionRef); + } else if(sessionElement != null) { + final String ref = sessionElement.getAttribute("ref"); + // + BeanComponentDefinition innerBean = IntegrationNamespaceUtils + .parseInnerHandlerDefinition(sessionElement, context); + if (StringUtils.hasText(ref)) { + builder.addPropertyReference(propName, ref); + } else if (innerBean != null) { + builder.addPropertyValue(propName, innerBean); + } + } else { + context.getReaderContext().error("Child element '"+sessionChild+"' or '"+sessionAttribute+"' attribute " + + "need to be specified", context.extractSource(e)); + } + } + + static void setTon(Element e, String tonAttribute, String propName, BeanDefinitionBuilder builder) { + final String ton = e.getAttribute(tonAttribute); + if (StringUtils.hasText(ton)) { + final RootBeanDefinition tonDef = new RootBeanDefinition(TypeOfNumber.class); + tonDef.setUniqueFactoryMethodName("valueOf"); + tonDef.getConstructorArgumentValues().addGenericArgumentValue(getByteTon(ton)); + builder.addPropertyValue(propName, tonDef); + } + } + + static void setNpi(Element e, String npiAttribute, String propName, BeanDefinitionBuilder builder) { + final String npi = e.getAttribute(npiAttribute); + if (StringUtils.hasText(npi)) { + final RootBeanDefinition npiDef = new RootBeanDefinition(NumberingPlanIndicator.class); + npiDef.setUniqueFactoryMethodName("valueOf"); + npiDef.getConstructorArgumentValues().addGenericArgumentValue(getByteNpi(npi)); + builder.addPropertyValue(propName, npiDef); + } + } + + static void setBindType(Element e, String bindAttribute, String propName, BeanDefinitionBuilder builder) { + final String bt = e.getAttribute(bindAttribute); + if (StringUtils.hasText(bt)) { + final RootBeanDefinition bindTypeDef = new RootBeanDefinition(BindType.class); + bindTypeDef.setUniqueFactoryMethodName("valueOf"); + bindTypeDef.getConstructorArgumentValues().addGenericArgumentValue(getByteBind(bt)); + builder.addPropertyValue(propName, bindTypeDef); + } + } + + private static byte getByteTon(String t) { + if ("ABBREVIATED".equals(t)) return SMPPConstant.TON_ABBREVIATED; + if ("ALPHANUMERIC".equals(t)) return SMPPConstant.TON_ALPHANUMERIC; + if ("SUBSCRIBER_NUMBER".equals(t))return SMPPConstant.TON_SUBSCRIBER_NUMBER; + if ("NETWORK_SPECIFIC".equals(t))return SMPPConstant.TON_NETWORK_SPECIFIC; + if ("NATIONAL".equals(t)) return SMPPConstant.TON_NATIONAL; + if ("INTERNATIONAL".equals(t))return SMPPConstant.TON_INTERNATIONAL; + return SMPPConstant.TON_UNKNOWN; + } + + private static byte getByteNpi(String n) { + if ("WAP".equals(n)) return SMPPConstant.NPI_WAP; + if ("INTERNET".equals(n)) return SMPPConstant.NPI_INTERNET; + if ("ERMES".equals(n)) return SMPPConstant.NPI_ERMES; + if ("PRIVATE".equals(n)) return SMPPConstant.NPI_PRIVATE; + if ("NATIONAL".equals(n)) return SMPPConstant.NPI_NATIONAL; + if ("LAND_MOBILE".equals(n)) return SMPPConstant.NPI_LAND_MOBILE; + if ("TELEX".equals(n)) return SMPPConstant.NPI_TELEX; + if ("DATA".equals(n)) return SMPPConstant.NPI_DATA; + if ("ISDN".equals(n)) return SMPPConstant.NPI_ISDN; + return SMPPConstant.NPI_UNKNOWN; + } + + private static byte getByteBind(String b) { + if ("BIND_RX".equals(b)) return SMPPConstant.CID_BIND_RECEIVER; + if ("BIND_TX".equals(b)) return SMPPConstant.CID_BIND_TRANSMITTER; + return SMPPConstant.CID_BIND_TRANSCEIVER; + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java new file mode 100644 index 0000000..b06f273 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides parser classes to provide Xml namespace support for the Smpp components. + */ +package org.springframework.integration.smpp.config.xml; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java new file mode 100644 index 0000000..02355f0 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/AbstractReceivingMessageListener.java @@ -0,0 +1,61 @@ +package org.springframework.integration.smpp.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.*; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.Session; + +/** + * @author Josh Long + * @since 2.1 + */ +abstract public class AbstractReceivingMessageListener implements MessageReceiverListener { + + private Log logger = LogFactory.getLog(getClass()); + + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + if (MessageType.SMSC_DEL_RECEIPT.containedIn(deliverSm.getEsmClass())) { // delivery receipt + try { + DeliveryReceipt delReceipt = deliverSm.getShortMessageAsDeliveryReceipt(); + long id = Long.parseLong(delReceipt.getId()); + String messageId = Long.toString(id, 16).toUpperCase(); + onDeliveryReceipt(deliverSm, messageId, delReceipt); + logger.debug("Receiving delivery receipt for message '" + messageId + "' : " + delReceipt); + } catch (Exception e) { + logger.error("Failed getting delivery receipt", e); + throw new RuntimeException(e); + } + } else { + try {// this is an actual SMS message + byte[] shortMessage = deliverSm.getShortMessage(); + String txtSms = shortMessage == null ? new String() : new String(shortMessage); + logger.debug("Receiving message : " + txtSms); + onTextMessage(deliverSm, txtSms); + } catch (Exception e) { + logger.error("Failed getting short message", e); + throw new RuntimeException(e); + } + } + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + return null; + } + + /** + * specific callback for a receipt, which you'll only get if the outbound message had a specific delivery receipt setting. + */ + abstract protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception; + + /** + * specific callback for proper SMS, text-based messages. + */ + abstract protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception; +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java new file mode 100644 index 0000000..05077b5 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java @@ -0,0 +1,579 @@ +package org.springframework.integration.smpp.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.*; +import org.jsmpp.session.ClientSession; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.Date; + +import static org.springframework.integration.smpp.core.SmppConstants.*; + +/** + * fluent API to help make specifying all these parameters just a tiny bit easier. For internal use only. + * + * @author Josh Long + * @since 2.1 + */ +public class SmesMessageSpecification { + + private Log log = LogFactory.getLog(getClass()); + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private int maxLengthSmsMessages = 140; + private String sourceAddress; + private String destinationAddress; + private String serviceType; + private TypeOfNumber sourceAddressTypeOfNumber; + private NumberingPlanIndicator sourceAddressNumberingPlanIndicator; + private TypeOfNumber destinationAddressTypeOfNumber; + private NumberingPlanIndicator destinationAddressNumberingPlanIndicator; + private ESMClass esmClass; + private byte protocolId; + private byte priorityFlag; + private String scheduleDeliveryTime = timeFormatter.format(new Date()); + private String validityPeriod; + private RegisteredDelivery registeredDelivery; + private byte replaceIfPresentFlag; + private DataCoding dataCoding; + private byte smDefaultMsgId; + private byte[] shortMessage; + private ClientSession smppSession; + + /** + * this method takes an inbound SMS message and converts it to a Spring Integration message + * + * @param dsm the {@link DeliverSm} from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)} + * @param txtMessage the String from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)} + * @return a Spring Integration message + */ + public static Message toMessageFromSms(DeliverSm dsm, String txtMessage) { + + Assert.isTrue(!dsm.isSmscDeliveryReceipt(), "the message should not be a delivery confirmation receipt!"); + + MessageBuilder mb = MessageBuilder.withPayload(txtMessage); + mb.setHeader(SmppConstants.SMS, dsm); + mb.setHeader(SmppConstants.REPLACE_IF_PRESENT, dsm.getReplaceIfPresent()); + mb.setHeader(SmppConstants.SHORT_MESSAGE, dsm.getShortMessage()); + mb.setHeader(SmppConstants.OPTIONAL_PARAMETES, dsm.getOptionalParametes()); + mb.setHeader(SmppConstants.UDHI_AND_REPLY_PATH, dsm.isUdhiAndReplyPath()); + mb.setHeader(SmppConstants.VALIDITY_PERIOD, dsm.getValidityPeriod()); + mb.setHeader(SmppConstants.COMMAND_LENGTH, dsm.getCommandLength()); + mb.setHeader(SmppConstants.COMMAND_ID, dsm.getCommandId()); + mb.setHeader(SmppConstants.SME_ACK_NOT_REQUESTED, dsm.isSmeAckNotRequested()); + mb.setHeader(SmppConstants.DATA_CODING, dsm.getDataCoding()); + mb.setHeader(SmppConstants.REPLY_PATH, dsm.isReplyPath()); + mb.setHeader(SmppConstants.SOURCE_ADDR_TON, dsm.getSourceAddrTon()); + mb.setHeader(SmppConstants.SM_DEFAULT_MSG_ID, dsm.getSmDefaultMsgId()); + mb.setHeader(SmppConstants.UDHI, dsm.isUdhi()); + mb.setHeader(SmppConstants.SME_MANUAL_ACKNOWLEDGMENT, dsm.isSmeManualAcknowledgment()); + mb.setHeader(SmppConstants.CONVERSATION_ABORT, dsm.isConversationAbort()); + mb.setHeader(SmppConstants.DEST_ADDRESS, dsm.getDestAddress()); + mb.setHeader(SmppConstants.ESM_CLASS, dsm.getEsmClass()); + mb.setHeader(SmppConstants.COMMAND_ID_AS_HEX, dsm.getCommandIdAsHex()); + mb.setHeader(SmppConstants.SME_DELIVERY_AND_MANUAL_ACK_REQUESTED, dsm.isSmeDeliveryAndManualAckRequested()); + mb.setHeader(SmppConstants.SMSC_DELIVERY_RECEIPT, dsm.isSmscDeliveryReceipt()); + mb.setHeader(SmppConstants.SME_MANUAL_ACK_REQUESTED, dsm.isSmeManualAckRequested()); + mb.setHeader(SmppConstants.PRIORITY_FLAG, dsm.getPriorityFlag()); + mb.setHeader(SmppConstants.DEST_ADDR_TON, dsm.getDestAddrTon()); + mb.setHeader(SmppConstants.COMMAND_STATUS_AS_HEX, dsm.getCommandStatusAsHex()); + mb.setHeader(SmppConstants.SERVICE_TYPE, dsm.getServiceType()); + mb.setHeader(SmppConstants.INTERMEDIATE_DELIVERY_NOTIFICATION, dsm.isIntermedietDeliveryNotification()); + mb.setHeader(SmppConstants.SOURCE_ADDR_NPI, dsm.getSourceAddrNpi()); + mb.setHeader(SmppConstants.REGISTERED_DELIVERY, dsm.getRegisteredDelivery()); + mb.setHeader(SmppConstants.DEST_ADDR_NPI, dsm.getDestAddrNpi()); + mb.setHeader(SmppConstants.COMMAND_STATUS, dsm.getCommandStatus()); + mb.setHeader(SmppConstants.DEFAULT_MESSAGE_TYPE, dsm.isDefaultMessageType()); + mb.setHeader(SmppConstants.PROTOCOL_ID, dsm.getProtocolId()); + mb.setHeader(SmppConstants.SOURCE_ADDR, dsm.getSourceAddr()); + mb.setHeader(SmppConstants.SEQUENCE_NUMBER, dsm.getSequenceNumber()); + mb.setHeader(SmppConstants.SCHEDULE_DELIVERY_TIME, dsm.getScheduleDeliveryTime()); + mb.setHeader(SmppConstants.SME_DELIVERY_ACK_REQUESTED, dsm.isSmeDeliveryAckRequested()); + return mb.build(); + } + + /** + * this method will take an inbound Spring Integration {@link Message} and map it to a {@link SmesMessageSpecification} + * which we can use to send the SMS message. + * + * @param msg a new {@link Message} + * @param smppSession the SMPPSession + * @return a {@link SmesMessageSpecification} + */ + public static SmesMessageSpecification fromMessage(ClientSession smppSession, Message msg) { + System.out.println("Message: "+msg); + String srcAddy = valueIfHeaderExists(SRC_ADDR, msg); + String dstAddy = valueIfHeaderExists(DST_ADDR, msg); + String smsTxt = valueIfHeaderExists(SMS_MSG, msg); + if (!StringUtils.hasText(smsTxt)) { + Object payload = msg.getPayload(); + if (payload instanceof String) { + smsTxt = (String) payload; + } + } + SmesMessageSpecification spec = SmesMessageSpecification.newSmesMessageSpecification(smppSession, srcAddy, dstAddy, smsTxt); + spec.setDestinationAddressNumberingPlanIndicator(SmesMessageSpecification.valueIfHeaderExists(DST_NPI, msg)); + spec.setSourceAddressNumberingPlanIndicator(SmesMessageSpecification.valueIfHeaderExists(SRC_NPI, msg)); + spec.setDestinationAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(DST_TON, msg)); + spec.setSourceAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(SRC_TON, msg)); + spec.setServiceType(SmesMessageSpecification.valueIfHeaderExists(SERVICE_TYPE, msg)); + spec.setEsmClass(SmesMessageSpecification.esmClassFromHeader(msg)); + spec.setScheduleDeliveryTime(SmesMessageSpecification.valueIfHeaderExists(SCHEDULED_DELIVERY_TIME, msg)); + spec.setDataCoding(SmesMessageSpecification. dataCodingFromHeader( msg)); + spec.setValidityPeriod(SmesMessageSpecification.valueIfHeaderExists(VALIDITY_PERIOD, msg)); + + // byte landmine. autoboxing causes havoc with null bytes. + Byte priorityFlag1 = SmesMessageSpecification.valueIfHeaderExists(PRIORITY_FLAG, msg); + if (priorityFlag1 != null) + spec.setPriorityFlag(priorityFlag1); + + Byte smDefaultMsgId1 = SmesMessageSpecification.valueIfHeaderExists(SM_DEFAULT_MSG_ID, msg); + if (smDefaultMsgId1 != null) + spec.setSmDefaultMsgId(smDefaultMsgId1); + + Byte replaceIfPresentFlag1 = SmesMessageSpecification.valueIfHeaderExists(REPLACE_IF_PRESENT_FLAG, msg); + if (replaceIfPresentFlag1 != null) + spec.setReplaceIfPresentFlag(replaceIfPresentFlag1); + + Byte protocolId1 = SmesMessageSpecification.valueIfHeaderExists(PROTOCOL_ID, msg); + if (null != protocolId1) + spec.setProtocolId(protocolId1); + + spec.setRegisteredDelivery(registeredDeliveryFromHeader(msg)); + + return spec; + } + + private static DataCoding dataCodingFromHeader( Message msg) { + Object dc = msg.getHeaders().get(DATA_CODING); + if(dc instanceof DataCoding){ + return (DataCoding)dc ; + } + if( dc instanceof Byte){ + return DataCoding.newInstance((Byte)dc); + + } + + return null ; + } + + /** + * need to be a little flexibile about what we take in as {@link SmppConstants#REGISTERED_DELIVERY_MODE}. The value can + * be a String or a member of the {@link SMSCDeliveryReceipt} enum. + * + * @param msg the Spring Integration message + * @return a value for {@link RegisteredDelivery} or null, which is good because it'll simply let the existing default work + */ + private static RegisteredDelivery registeredDeliveryFromHeader(Message msg) { + Object rd = valueIfHeaderExists(REGISTERED_DELIVERY_MODE, msg); + + if (rd instanceof String) { + String rdString = (String) rd; + SMSCDeliveryReceipt smscDeliveryReceipt = SMSCDeliveryReceipt.valueOf(rdString); + Assert.notNull(smscDeliveryReceipt, "the registeredDelivery can't be null"); + return new RegisteredDelivery(smscDeliveryReceipt); + } + + if (rd instanceof SMSCDeliveryReceipt) { + SMSCDeliveryReceipt smscDeliveryReceipt = (SMSCDeliveryReceipt) rd; + return new RegisteredDelivery(smscDeliveryReceipt); + } + + if (rd instanceof RegisteredDelivery) { + return (RegisteredDelivery) rd; + } + return null; + } + + /** + * you need to use the builder API + * + * @param smppSession the SMPPSession instance against which we should work. + * @see SmesMessageSpecification#SmesMessageSpecification() + */ + + @SuppressWarnings("unused") + SmesMessageSpecification(SMPPSession smppSession) { + this.smppSession = smppSession; + } + + /** + * tries to safely extract the ESMClass + * @param im + * @return + */ + static private ESMClass esmClassFromHeader( Message im){ + String h = ESM_CLASS ; + Object o = valueIfHeaderExists(h,im); + ESMClass response = null ; + if(o instanceof Byte){ + response = new ESMClass((Byte)o); + + } + else if(o instanceof ESMClass){ + response = (ESMClass)o; + } + return response; + } + + @SuppressWarnings("unchecked") + static private T valueIfHeaderExists(String h, Message msg) { + if (msg != null && msg.getHeaders().containsKey(h)) + return (T) msg.getHeaders().get(h); + return null; + } + + /** + * Everybody else has to use the builder API. DO NOT make this private or it will not be proxied and that will make me sad! + * + * @param ss the {@link SMPPSession} + * @return the current spec + */ + SmesMessageSpecification setSmppSession(ClientSession ss) { + this.smppSession = ss; + return this; + } + + /** + * use the builder API, but we need this to cleanly proxy + */ + @SuppressWarnings("unused") + SmesMessageSpecification() { + this(null); + } + + /** + * Conceptually, you could get away with just specifying these three parameters, though I don't know how likely that is in practice. + * + * @param srcAddress the source address + * @param destAddress the destination address + * @param txtMessage the message to send (must be no more than 140 characters + * @param ss the SMPPSession + * @return the {@link SmesMessageSpecification} + */ + public static SmesMessageSpecification newSmesMessageSpecification(ClientSession ss, String srcAddress, String destAddress, String txtMessage) { + + SmesMessageSpecification smesMessageSpecification = new SmesMessageSpecification(); + + smesMessageSpecification + .reset() + .setSmppSession(ss) + .setSourceAddress(srcAddress) + .setDestinationAddress(destAddress) + .setShortTextMessage(txtMessage); + + return smesMessageSpecification; + } + + /** + * Only sets the #sourceAddressTypeOfNumber if the current value is null, otherwise, it leaves it. + * + * @param sourceAddressTypeOfNumberIfRequired + * the {@link TypeOfNumber} + * @return this + */ + public SmesMessageSpecification setSourceAddressTypeOfNumberIfRequired(TypeOfNumber sourceAddressTypeOfNumberIfRequired) { + if (this.sourceAddressTypeOfNumber == null) + this.sourceAddressTypeOfNumber = sourceAddressTypeOfNumberIfRequired; + return this; + } + + /** + * send the message on its way. + *

+ * todo can we do something smart here or through an adapter to handle the situation where we have asked for a message receipt? what about if we're using a message receipt and we're only a receiver or a sender connection and not a transceiver? We need gateway semantics across two unidirectional SMPPSessions, then + * + * @return the messageId (required if you want to then track it or correllate it with message receipt confirmations) + * @throws Exception the {@link SMPPSession#submitShortMessage(String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.ESMClass, byte, byte, String, String, org.jsmpp.bean.RegisteredDelivery, byte, org.jsmpp.bean.DataCoding, byte, byte[], org.jsmpp.bean.OptionalParameter...)} method throws lots of Exceptions, including {@link java.io.IOException} + */ + public String send() throws Exception { + validate(); + String msgId = this.smppSession.submitShortMessage( + this.serviceType, + this.sourceAddressTypeOfNumber, + this.sourceAddressNumberingPlanIndicator, + this.sourceAddress, + + this.destinationAddressTypeOfNumber, + this.destinationAddressNumberingPlanIndicator, + this.destinationAddress, + + this.esmClass, + this.protocolId, + this.priorityFlag, + this.scheduleDeliveryTime, + this.validityPeriod, + this.registeredDelivery, + this.replaceIfPresentFlag, + this.dataCoding, + this.smDefaultMsgId, + this.shortMessage); + + return msgId; + } + + protected void validate() { + Assert.notNull(this.sourceAddress, "the source address must not be null"); + Assert.notNull(this.destinationAddress, "the destination address must not be null"); + Assert.isTrue(this.shortMessage != null && this.shortMessage.length > 0, "the message must not be null"); + } + + public SmesMessageSpecification setSourceAddress(String sourceAddr) { + if (!nullHeaderWillOverwriteDefault(sourceAddr)) + this.sourceAddress = sourceAddr; + return this; + } + + /** + * the 'to' phone number + * + * @param destinationAddr the phone number + * @return the current spec + */ + public SmesMessageSpecification setDestinationAddress(String destinationAddr) { + this.destinationAddress = destinationAddr; + return this; + } + + public SmesMessageSpecification setServiceType(String serviceType) { + if (!nullHeaderWillOverwriteDefault(serviceType)) + this.serviceType = serviceType; + return this; + } + + public SmesMessageSpecification setSourceAddressTypeOfNumber(TypeOfNumber sourceAddrTon) { + if (!nullHeaderWillOverwriteDefault(sourceAddrTon)) + this.sourceAddressTypeOfNumber = sourceAddrTon; + return this; + } + + public SmesMessageSpecification setSourceAddressNumberingPlanIndicator(NumberingPlanIndicator sourceAddrNpi) { + if (!nullHeaderWillOverwriteDefault(sourceAddrNpi)) + this.sourceAddressNumberingPlanIndicator = sourceAddrNpi; + return this; + } + + public SmesMessageSpecification setDestinationAddressTypeOfNumber(TypeOfNumber destAddrTon) { + if (!nullHeaderWillOverwriteDefault(destAddrTon)) + this.destinationAddressTypeOfNumber = destAddrTon; + return this; + } + + /** + * guard against overwriting perfectly good defaults with null values. + * + * @param v value the value + * @return can the write proceed unabated? + */ + private boolean nullHeaderWillOverwriteDefault(Object v) { + if (v == null) { + if (log.isDebugEnabled()) log.debug("There is a default in place for this property; don't overwrite it with null"); + return true; + } + return false; + } + + public SmesMessageSpecification setDestinationAddressNumberingPlanIndicator(NumberingPlanIndicator destAddrNpi) { + if (!nullHeaderWillOverwriteDefault(destAddrNpi)) + this.destinationAddressNumberingPlanIndicator = destAddrNpi; + return this; + } + + public SmesMessageSpecification setEsmClass(ESMClass esmClass) { + if (!nullHeaderWillOverwriteDefault(esmClass)) + this.esmClass = esmClass; + return this; + } + + public SmesMessageSpecification setProtocolId(byte protocolId) { + if (!nullHeaderWillOverwriteDefault(protocolId)) + this.protocolId = protocolId; + return this; + } + + public SmesMessageSpecification setPriorityFlag(byte pf) { + if (!nullHeaderWillOverwriteDefault(pf)) + this.priorityFlag = pf; + return this; + } + + /** + * When you submit a message to an SMSC, it is possible to sometimes specify a + * validity period for the message. This setting is an instruction to the SMSC that stipulates that + * if the message cannot be delivered to the recipient within the next N minutes or hours or days, + * the SMSC should discard the message. This would mean that if the recipient'running mobile phone is + * turned off, or outSession of coverage for x minutes/hours/days after the message is submitted, the SMSC + * should not perform further delivery retry and should discard the message. + *

+ * Of course, there is no guarantee that the operator SMSC will respect this setting, so it needs + * to be tested with a particular operator first to determine if it can be used reliably. + *

+ * That information came from the NowSMS website.. + * + * @param v the period of validity. There are specific formats for this, however this method provides no validation. + *

+ * todo provide format validation if possible + * @return the current SmesMessageSpecification + */ + public SmesMessageSpecification setValidityPeriod(String v) { + if (!nullHeaderWillOverwriteDefault(v)) + this.validityPeriod = v; + return this; + } + + public SmesMessageSpecification setScheduleDeliveryTime(Date d) { + if (!nullHeaderWillOverwriteDefault(d)) + this.scheduleDeliveryTime = timeFormatter.format(d); + return this; + } + + public SmesMessageSpecification setRegisteredDelivery(RegisteredDelivery rd) { + if (!nullHeaderWillOverwriteDefault(rd)) + this.registeredDelivery = rd; + return this; + } + + public SmesMessageSpecification setReplaceIfPresentFlag(byte replaceIfPresentFlag) { + if (!nullHeaderWillOverwriteDefault(replaceIfPresentFlag)) + this.replaceIfPresentFlag = replaceIfPresentFlag; + return this; + } + + public SmesMessageSpecification setDataCoding(DataCoding dataCoding) { + if (!nullHeaderWillOverwriteDefault(dataCoding)) + this.dataCoding = dataCoding; + return this; + } + + public SmesMessageSpecification setSmDefaultMsgId(byte smDefaultMsgId) { + this.smDefaultMsgId = smDefaultMsgId; + return this; + } + + public SmesMessageSpecification setTimeFormatter(TimeFormatter timeFormatter) { + if (!nullHeaderWillOverwriteDefault(timeFormatter)) + this.timeFormatter = timeFormatter; + return this; + } + + /** + * todo it'running not quite true that the payload needs to be 140c. A large message can be split up into smaller messages, + * but for now it'running more useful to have this validation in place than not. + * + * @param s the text message body + * @return the SmesMessageSpecification + */ + public SmesMessageSpecification setShortTextMessage(String s) { + Assert.notNull(s, "the SMS message payload must not be null"); + Assert.isTrue(s.length() <= this.maxLengthSmsMessages, "the SMS message payload must be 140 characters or less."); + this.shortMessage = s.getBytes(); + return this; + } + + /** + * this is a good value, but not strictly speaking universal. This is intended only for exceptional configuration cases + *

+ * See: http://www.nowsms.com/long-sms-text-messages-and-the-160-character-limit + * + * @param maxLengthSmsMessages the length of sms messages + * @see #setShortTextMessage(String) + */ + @SuppressWarnings("unused") + public void setMaxLengthSmsMessages(int maxLengthSmsMessages) { + this.maxLengthSmsMessages = maxLengthSmsMessages; + } + + /** + * Resets the thread local, pooled objects to a known state before reuse. + *

+ * Resetting the variables is trivially cheap compared to proxying a new one each time. + * + * @return the cleaned up {@link SmesMessageSpecification} + */ + protected SmesMessageSpecification reset() { + + // configuration params - should they be reset? + maxLengthSmsMessages = 140; + timeFormatter = new AbsoluteTimeFormatter(); + + sourceAddress = null; + destinationAddress = null; + serviceType = "CMT"; + sourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + sourceAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN; + destinationAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + destinationAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN; + esmClass = new ESMClass(); + protocolId = 0; + priorityFlag = 1; + scheduleDeliveryTime = null; + validityPeriod = null; + registeredDelivery = new RegisteredDelivery(SMSCDeliveryReceipt.DEFAULT); + replaceIfPresentFlag = 0; + dataCoding = new GeneralDataCoding(false, true, MessageClass.CLASS1, Alphabet.ALPHA_DEFAULT); + smDefaultMsgId = 0; + shortMessage = null; // the bytes to the 140 character text message + smppSession = null; + return this; + } + + public SmesMessageSpecification setSourceAddressIfRequired(String defaultSourceAddress) { + if (!StringUtils.hasText(this.sourceAddress)) + this.sourceAddress = defaultSourceAddress; + return this; + } +} + +/* private static String fromPropertyToHeaderConstant(String n) { + + StringBuffer stringBuffer = new StringBuffer(); + + for (char c : n.toCharArray()) { + if (Character.isUpperCase(c)) { + stringBuffer.append("_"); + } + stringBuffer.append(c); + } + + String nn = stringBuffer.toString().toUpperCase(); + + String is = "IS_", + get = "GET_"; + if (nn.startsWith(is)) nn = nn.substring(is.length()); + if (nn.startsWith(get)) nn = nn.substring(get.length()); + + return nn; + } + + static public void main(String[] args) throws Throwable { + String m = "mb.setHeader( SmppConstants.%s, dsm.%s() );"; + String h = "public static final String %s = \"%s\";"; + Set marshalling = new HashSet(); + Set headers = new HashSet(); + PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(DeliverSm.class); + for (PropertyDescriptor propertyDescriptor : pds) { + Method reader = propertyDescriptor.getReadMethod(); + String readerName = reader.getName(); + + String header = fromPropertyToHeaderConstant(readerName); + headers.add(header); + marshalling.add(readerName + ":" + header); + } + + for (String s : headers) System.outSession.println(String.format(h, s, s)); + + for (String s : marshalling) { + + String[] tuple = s.split(":"); + + System.outSession.println(String.format(m, tuple[1], tuple[0])); + } + } +*/ diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java new file mode 100644 index 0000000..4c427c1 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java @@ -0,0 +1,52 @@ +package org.springframework.integration.smpp.core; + +/** + * + * + * @author Josh Long + */ +public abstract class SmppConstants { + + public static final String SMS="SMS", SRC_ADDR = "SRC_ADDRESS", DST_ADDR = "DEST_ADDRESS", SMS_MSG = "SMS_SHORT_MESSAGE"; + + public static final String REGISTERED_DELIVERY_MODE = "REGISTERED_DELIVERY_MODE", REPLACE_IF_PRESENT_FLAG = "REPLACE_IF_PRESENT_FLAG"; + public static final String SRC_TON = "SRC_ADDR_TON", DST_TON = "DST_ADDR_TON", DST_NPI = "DST_ADDR_NPI", SRC_NPI = "SRC_ADDR_NPI"; + public static final String SCHEDULED_DELIVERY_TIME = "SCHEDULED_DELIVERY_TIME"; + public static final String SOURCE_ADDR_NPI = "SOURCE_ADDR_NPI"; + public static final String PRIORITY_FLAG = "PRIORITY_FLAG"; + public static final String COMMAND_LENGTH = "COMMAND_LENGTH"; + public static final String UDHI_AND_REPLY_PATH = "UDHI_AND_REPLY_PATH"; + public static final String SEQUENCE_NUMBER = "SEQUENCE_NUMBER"; + public static final String SME_MANUAL_ACK_REQUESTED = "SME_MANUAL_ACK_REQUESTED"; + public static final String DEST_ADDR_TON = "DEST_ADDR_TON"; + public static final String COMMAND_ID = "COMMAND_ID"; + public static final String SME_DELIVERY_AND_MANUAL_ACK_REQUESTED = "SME_DELIVERY_AND_MANUAL_ACK_REQUESTED"; + public static final String VALIDITY_PERIOD = "VALIDITY_PERIOD"; + public static final String SOURCE_ADDR = SRC_ADDR; + public static final String ESM_CLASS = "ESM_CLASS"; + public static final String PROTOCOL_ID = "PROTOCOL_ID"; + public static final String SERVICE_TYPE = "SERVICE_TYPE"; + public static final String COMMAND_ID_AS_HEX = "COMMAND_ID_AS_HEX"; + public static final String CONVERSATION_ABORT = "CONVERSATION_ABORT"; + public static final String SME_ACK_NOT_REQUESTED = "SME_ACK_NOT_REQUESTED"; + public static final String DEST_ADDR_NPI = "DEST_ADDR_NPI"; + public static final String REPLACE_IF_PRESENT = "REPLACE_IF_PRESENT"; + public static final String SMSC_DELIVERY_RECEIPT = "SMSC_DELIVERY_RECEIPT"; + public static final String INTERMEDIATE_DELIVERY_NOTIFICATION = "INTERMEDIATE_DELIVERY_NOTIFICATION"; + public static final String REGISTERED_DELIVERY = "REGISTERED_DELIVERY"; +// public static final String SHORT_MESSAGE_AS_DELIVERY_RECEIPT = "SHORT_MESSAGE_AS_DELIVERY_RECEIPT"; + public static final String SCHEDULE_DELIVERY_TIME = "SCHEDULE_DELIVERY_TIME"; + public static final String COMMAND_STATUS = "COMMAND_STATUS"; + public static final String SHORT_MESSAGE = "SHORT_MESSAGE"; + public static final String SME_MANUAL_ACKNOWLEDGMENT = "SME_MANUAL_ACKNOWLEDGMENT"; + public static final String COMMAND_STATUS_AS_HEX = "COMMAND_STATUS_AS_HEX"; + public static final String UDHI = "UDHI"; + public static final String SME_DELIVERY_ACK_REQUESTED = "SME_DELIVERY_ACK_REQUESTED"; + public static final String DATA_CODING = "DATA_CODING"; + public static final String SOURCE_ADDR_TON = "SOURCE_ADDR_TON"; + public static final String DEFAULT_MESSAGE_TYPE = "DEFAULT_MESSAGE_TYPE"; + public static final String SM_DEFAULT_MSG_ID = "SM_DEFAULT_MSG_ID"; + public static final String REPLY_PATH = "REPLY_PATH"; + public static final String DEST_ADDRESS = DST_ADDR; + public static final String OPTIONAL_PARAMETES = "OPTIONAL_PARAMETES"; +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java new file mode 100644 index 0000000..6d3267e --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppHeaders.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smpp.core; + +/** + * Smpp adapter specific message headers. + * + * @author Johanes Soetanto + * @since 2.2 + */ +public class SmppHeaders { + + private static final String PREFIX = "smpp_"; + + /** Non instantiable utility class */ + private SmppHeaders() { + throw new AssertionError(); + } + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java new file mode 100644 index 0000000..f5f05f9 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides core classes of the Smpp module. + */ +package org.springframework.integration.smpp.core; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java new file mode 100644 index 0000000..e9bf475 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundChannelAdapter.java @@ -0,0 +1,87 @@ +package org.springframework.integration.smpp.inbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.bean.DeliveryReceipt; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.smpp.core.AbstractReceivingMessageListener; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; + +/** + * Supports receiving messages of a payload specified by the SMPP protocol from a short message service center (SMSC). + * + * @author Josh Long + * @since 2.1 + *

+ * todo find some way to configure the {@link java.util.concurrent.Executor}running for the JSMPP library + */ +public class SmppInboundChannelAdapter extends AbstractEndpoint { + + private MessagingTemplate messagingTemplate; + private MessageChannel channel; + private ExtendedSmppSession smppSession; + + /** + * the channel on which inbound SMS messages should be delivered to Spring Integration components. + * + * @param channel the channel + */ + public void setChannel(MessageChannel channel) { + this.channel = channel; + this.messagingTemplate = new MessagingTemplate(this.channel); + } + + @Override + protected void onInit() throws Exception { + Assert.notNull(this.channel, "the 'channel' property must be set"); + Assert.notNull(this.smppSession, "the 'smppSession' property must be set"); + Assert.isTrue(this.smppSession.getBindType().isReceiveable() || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "this session's bind type should support " + + "receiving messages or both sending *and* receiving messages!"); + } + + /** + * Set smpp session + * @param s smpp session + */ + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + private AbstractReceivingMessageListener abstractReceivingMessageListener = + new AbstractReceivingMessageListener() { + @Override + protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception { + // noop don't care + } + + @Override + protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception { + Message msg = SmesMessageSpecification.toMessageFromSms(deliverSm, txtMessage); + messagingTemplate.send(msg); + } + }; + + @Override + protected void doStart() { + this.smppSession.addMessageReceiverListener(this.abstractReceivingMessageListener); + this.smppSession.start(); + } + + @Override + protected void doStop() { + this.smppSession.stop(); + } + + + @Override + public String getComponentType() { + return "smpp:inbound-channel-adapter"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java new file mode 100644 index 0000000..7e991c5 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/SmppInboundGateway.java @@ -0,0 +1,134 @@ +package org.springframework.integration.smpp.inbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.bean.DeliveryReceipt; +import org.jsmpp.bean.TypeOfNumber; +import org.springframework.integration.Message; +import org.springframework.integration.gateway.MessagingGatewaySupport; +import org.springframework.integration.smpp.core.AbstractReceivingMessageListener; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * what'running an inbound gateway in this case? Receive a text message and then automatically send a response. + * + * @author Josh Long + * @since 2.1 + */ +public class SmppInboundGateway extends MessagingGatewaySupport { + + private ExtendedSmppSession smppSession; + private TypeOfNumber defaultSourceAddressTypeOfNumber; + private String defaultSourceAddress; + + /** + * Set default source address type of number. + * @param defaultSourceAddressTypeOfNumber default address type of number. + */ + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + /** + * Set default source address. + * @param defaultSourceAddress default source address + */ + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + /** + * for configuration purposes. + * + * @param s the session to use + */ + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + protected void onInit() throws Exception { + Assert.notNull(this.smppSession, "the 'smppSession' property must be set"); + Assert.isTrue(this.smppSession.getBindType().isReceiveable() || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "this session's bind type should support " + + "receiving messages or both sending *and* receiving messages!"); + } + + private AbstractReceivingMessageListener abstractReceivingMessageListener = + new AbstractReceivingMessageListener() { + @Override + protected void onDeliveryReceipt(DeliverSm deliverSm, String ogMessageId, DeliveryReceipt deliveryReceipt) throws Exception { + // noop don't care + } + + @Override + protected void onTextMessage(DeliverSm deliverSm, String txtMessage) throws Exception { + // we receive sms + logger.debug("received an SMS in " + getClass() + ". Processing it."); + Message msg = SmesMessageSpecification.toMessageFromSms(deliverSm, txtMessage); + + // send it INTO SI, where it can be processed. The reply message is sent BACK to this, which we then send BACK outSession through SMS + logger.debug("sending the SMS inbound to be processed; awaiting a reply."); + + Message response = sendAndReceiveMessage(msg); + logger.debug("received a reply message; will handle as in outbound adapter"); + + // todo copy all the code from the outbound adapter related to defaults + /// todo also make sure that we simply flip the inbound to outbound + applyDefaults(msg, response, SmesMessageSpecification.fromMessage(smppSession, response)).send(); + logger.debug("the reply SMS message has been sent."); + } + }; + + /** + * among other things this method simply 'flips' the src/dst + * + * @param request req + * @param response res + * @param smesMessageSpecification spec + * @return same spec reflecting new switches + */ + SmesMessageSpecification applyDefaults(Message request, Message response, SmesMessageSpecification smesMessageSpecification) { + + String from = null, to = null; + if (request.getHeaders().containsKey(SmppConstants.SRC_ADDR)) { + to = (String) request.getHeaders().get(SmppConstants.SRC_ADDR); + if (StringUtils.hasText(to)) + smesMessageSpecification.setDestinationAddress(to); + } + if (request.getHeaders().containsKey(SmppConstants.DEST_ADDRESS)) { + from = (String) request.getHeaders().get(SmppConstants.DEST_ADDRESS); + if (StringUtils.hasText(from)) + smesMessageSpecification.setSourceAddressIfRequired(from); + } + if (defaultSourceAddressTypeOfNumber != null) + smesMessageSpecification.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smesMessageSpecification.setSourceAddressIfRequired(this.defaultSourceAddress); + return smesMessageSpecification; + } + + @Override + protected void doStart() { + super.doStart(); + this.smppSession.addMessageReceiverListener(this.abstractReceivingMessageListener); + this.smppSession.start(); + } + + @Override + protected void doStop() { + super.doStop(); + this.smppSession.stop(); + } + + @Override + public String getComponentType() { + return "smpp:inbound-gateway"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java new file mode 100644 index 0000000..aed4690 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides inbound Spring Integration Smpp components. + */ +package org.springframework.integration.smpp.inbound; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java new file mode 100644 index 0000000..ebad814 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java @@ -0,0 +1,106 @@ +package org.springframework.integration.smpp.outbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Sends messages to an SMS gateway using SMPP. Most of the work in terms of converting inbound message headers + * (whose keys, by the way, live in {@link org.springframework.integration.smpp.core.SmppConstants}) is done by {@link org.springframework.integration.smpp.core.SmesMessageSpecification}, which + * handles all the tedium of converting and validating the configuration. + *

+ * This adapter supports mobile terminated (MT) messaging, where the recipient is a directory phone number. + * + * @author Josh Long + * @since 2.1 + */ +public class SmppOutboundChannelAdapter extends IntegrationObjectSupport implements MessageHandler { + + private String defaultSourceAddress; + + private TypeOfNumber defaultSourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private ExtendedSmppSession smppSession; + + @SuppressWarnings("unused") + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + @SuppressWarnings("unused") + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + @SuppressWarnings("unused") + public void setTimeFormatter(TimeFormatter timeFormatter) { + this.timeFormatter = timeFormatter; + } + + @Override + protected void onInit() throws Exception { + if (this.timeFormatter == null) { + this.timeFormatter = new AbsoluteTimeFormatter(); + } + + Assert.notNull(this.smppSession, "the smppSession must not be null"); + Assert.isTrue(!this.smppSession.getBindType().equals(BindType.BIND_RX), + "the BindType must support message production: BindType.TX or BindType.TRX only supported"); + + this.smppSession.start(); + + } + + private SmesMessageSpecification applyDefaultsIfNecessary(SmesMessageSpecification smsSpec) { + + if (defaultSourceAddressTypeOfNumber != null) + smsSpec.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smsSpec.setSourceAddressIfRequired(this.defaultSourceAddress); + + return smsSpec; + } + + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + public void handleMessage(Message message) throws MessagingException { + + try { + // todo support a gateway and have that gateway also handle message delivery receipt notifications + // that will correlate this smsMessageId with the ID that comes back asynchronously from the SMSC indicating that + // the message has been delivered. + // this could require that we keep a correlation map since its possible upstream SMSC + // unused return value -- see gateway + + SmesMessageSpecification specification = applyDefaultsIfNecessary( + SmesMessageSpecification.fromMessage(this.smppSession, message) + .setTimeFormatter(this.timeFormatter)); + + String smsMessageId = specification.send(); + logger.debug( "sent message : "+message.getPayload()); + logger.debug("message ID for the sent message is: " + smsMessageId); + } catch (Exception e) { + throw new RuntimeException("Exception in trying to process the inbound SMPP message", e); + } + } + + @Override + public String getComponentType() { + return "smpp:outbound-channel-adapter"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java new file mode 100644 index 0000000..1f4a3d1 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java @@ -0,0 +1,102 @@ +package org.springframework.integration.smpp.outbound; + +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.jsmpp.util.TimeFormatter; +import org.springframework.integration.Message; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Support for request/reply exchanges over SMPP to a SMSC. + *

+ * The request is an outbound SMS message, as in the {@link org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter}, + * and the reply can either be the messageId of the outbound message which can ultimately be used to track the confirmation, + * or the confirmation of the receipt of the outbound message itself. In the latter case, this class simply does the work + * of waiting for the reply and correlating it to the outbound request. + *

+ * By default this component assumes one {@link org.jsmpp.session.SMPPSession} in "transceiver" mode - it can both request and reply. + * Conceptually it should be possible to support two {@link org.jsmpp.session.SMPPSession}running, one in "sender" mode, and another in + * "receiver" mode and handle the duplexing manually. The correlation logic is the same, in any event. + *

+ * + * @author Josh Long + * @since 2.1 + */ +public class SmppOutboundGateway extends AbstractReplyProducingMessageHandler { + @Override + protected void onInit() { + Assert.isTrue( + this.smppSession.getBindType().equals(BindType.BIND_TX) || + this.smppSession.getBindType().equals(BindType.BIND_TRX), + "the smppSession's bindType must be BindType.BIND_TX or BindType.BIND_TRX"); + + this.smppSession.start(); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + try { + + SmesMessageSpecification specification = applyDefaultsIfNecessary( + SmesMessageSpecification.fromMessage(this.smppSession, requestMessage) + .setTimeFormatter(this.timeFormatter)); + + String smsMessageId = specification.send(); + + logger.debug("message ID for the sent message is: " + smsMessageId); + + return MessageBuilder.withPayload(smsMessageId).build(); + } catch (Exception e) { + throw new RuntimeException("Exception in trying to process the inbound SMPP message", e); + } + } + + private String defaultSourceAddress; + + private TypeOfNumber defaultSourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN; + + private TimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private ExtendedSmppSession smppSession; + + @SuppressWarnings("unused") + public void setDefaultSourceAddress(String defaultSourceAddress) { + this.defaultSourceAddress = defaultSourceAddress; + } + + @SuppressWarnings("unused") + public void setDefaultSourceAddressTypeOfNumber(TypeOfNumber defaultSourceAddressTypeOfNumber) { + this.defaultSourceAddressTypeOfNumber = defaultSourceAddressTypeOfNumber; + } + + @SuppressWarnings("unused") + public void setTimeFormatter(TimeFormatter timeFormatter) { + this.timeFormatter = timeFormatter; + } + + private SmesMessageSpecification applyDefaultsIfNecessary(SmesMessageSpecification smsSpec) { + + if (defaultSourceAddressTypeOfNumber != null) + smsSpec.setSourceAddressTypeOfNumberIfRequired(this.defaultSourceAddressTypeOfNumber); + + if (StringUtils.hasText(this.defaultSourceAddress)) + smsSpec.setSourceAddressIfRequired(this.defaultSourceAddress); + + return smsSpec; + } + + public void setSmppSession(ExtendedSmppSession s) { + this.smppSession = s; + } + + @Override + public String getComponentType() { + return "smpp:outbound-gateway"; + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java new file mode 100644 index 0000000..0388b21 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides Spring Integration components for doing outbound operations. + */ +package org.springframework.integration.smpp.outbound; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java new file mode 100644 index 0000000..78663da --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the Smpp Module. + */ +package org.springframework.integration.smpp; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java new file mode 100644 index 0000000..6fdf8f4 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/DelegatingMessageReceiverListener.java @@ -0,0 +1,52 @@ +package org.springframework.integration.smpp.session; + +import org.jsmpp.bean.AlertNotification; +import org.jsmpp.bean.DataSm; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.Session; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * We're normally allowed to register only one {@link MessageReceiverListener} instance. + * Additionally, that instance must be registered before connection. + *

+ * This class delegates all calls to as many {@link MessageReceiverListener}s as you'd like, regardless of when the registered listener was added. + * + * @author Josh Long + * @since 2.1 + */ +public class DelegatingMessageReceiverListener implements MessageReceiverListener { + + private volatile Set messageReceiverListenerSet = + new CopyOnWriteArraySet(); + + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + for (MessageReceiverListener l : this.messageReceiverListenerSet) + l.onAcceptDeliverSm(deliverSm); + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + for (MessageReceiverListener l : this.messageReceiverListenerSet) + l.onAcceptAlertNotification(alertNotification); + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + DataSmResult dataSmResult = null; + for (MessageReceiverListener l : this.messageReceiverListenerSet) { + DataSmResult tmpV = l.onAcceptDataSm(dataSm, source); + if (tmpV != null) { + dataSmResult = tmpV; + } + } + return dataSmResult; // could still be null + } + + public void addMessageReceiverListener(MessageReceiverListener messageReceiverListener) { + this.messageReceiverListenerSet.add(messageReceiverListener); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java new file mode 100644 index 0000000..f3c38da --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSession.java @@ -0,0 +1,37 @@ +package org.springframework.integration.smpp.session; + +import org.jsmpp.bean.BindType; +import org.jsmpp.session.ClientSession; +import org.jsmpp.session.MessageReceiverListener; + +/** + * Represents an {@link org.jsmpp.session.SMPPSession} that has a few extra capabilities: + *

+ *

  1. supports registration of multiple {@link org.jsmpp.session.MessageReceiverListener}s
+ * + * @author Josh Long + * @since 2.1 + */ +public interface ExtendedSmppSession extends ClientSession { + /** + * a {@link MessageReceiverListener} implementation to be added to the set of existing listeners. + *

+ * NB: the contract for each of these is the same as for a single instance: don't take too long when doing your processing. This is even more + * important now that multiple implementations need to share the same callback slice time. + * + * @param messageReceiverListener the message receiver listener + */ + void addMessageReceiverListener(MessageReceiverListener messageReceiverListener); + + /** + * We need to know this to determine whether or not this session can handle the requirements we need. + * + * @return the {@link BindType} + */ + BindType getBindType(); + + void start() ; + + void stop() ; + +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java new file mode 100644 index 0000000..131d491 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/ExtendedSmppSessionAdaptingDelegate.java @@ -0,0 +1,170 @@ +package org.springframework.integration.smpp.session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.InvalidResponseException; +import org.jsmpp.PDUException; +import org.jsmpp.bean.*; +import org.jsmpp.extra.NegativeResponseException; +import org.jsmpp.extra.ResponseTimeoutException; +import org.jsmpp.extra.SessionState; +import org.jsmpp.session.*; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.Lifecycle; + +import java.io.IOException; + +/** + * Adapts to the {@link ClientSession} API, while also providing the callbacks for the Spring container + * + * @author Josh Long + * @since 2.1 + */ +public class ExtendedSmppSessionAdaptingDelegate implements /*Lifecycle,*/ ExtendedSmppSession, InitializingBean { + + /** + * callback for custom lifecycle events + */ + private Lifecycle lifecycle; + private Log log = LogFactory.getLog(getClass()); + private final DelegatingMessageReceiverListener delegatingMessageReceiverListener = new DelegatingMessageReceiverListener(); + private volatile boolean running; + private BindType bindType; + private SMPPSession session; + + public void setBindType(BindType bindType) { + this.bindType = bindType; + } + + public SMPPSession getTargetClientSession() { + return this.session; + } + + public void start() { + + if( this.running) + return; + + lifecycle.start(); + this.running = true; + } + + public void stop() { + lifecycle.stop(); + this.running = false; + } + + public boolean isRunning() { + return this.running; + } + + public BindType getBindType() { + return this.bindType; + } + + /** + * noops for the {@link Lifecycle} arg in {@link ExtendedSmppSessionAdaptingDelegate#ExtendedSmppSessionAdaptingDelegate(org.jsmpp.session.SMPPSession, org.springframework.context.Lifecycle)} + * + * @param session the session + */ + public ExtendedSmppSessionAdaptingDelegate(SMPPSession session) { + this(session, new Lifecycle() { + public void start() { + } + + public void stop() { + } + + public boolean isRunning() { + return true; + } + }); + } + + public ExtendedSmppSessionAdaptingDelegate(SMPPSession session, Lifecycle lifecycle) { + this.lifecycle = lifecycle; + this.session = session; + this.session.setMessageReceiverListener(this.delegatingMessageReceiverListener); + } + + public void addMessageReceiverListener(MessageReceiverListener messageReceiverListener) { + this.delegatingMessageReceiverListener.addMessageReceiverListener(messageReceiverListener); + } + + public String submitShortMessage(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, + String sourceAddr, TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, + String destinationAddr, ESMClass esmClass, byte protocolId, byte priorityFlag, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, byte replaceIfPresentFlag, DataCoding dataCoding, byte smDefaultMsgId, byte[] shortMessage, OptionalParameter... optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.submitShortMessage(serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddr, esmClass, protocolId, priorityFlag, scheduleDeliveryTime, validityPeriod, registeredDelivery, replaceIfPresentFlag, dataCoding, smDefaultMsgId, shortMessage, optionalParameters); + } + + public SubmitMultiResult submitMultiple(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, Address[] destinationAddresses, ESMClass esmClass, byte protocolId, byte priorityFlag, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, ReplaceIfPresentFlag replaceIfPresentFlag, DataCoding dataCoding, byte smDefaultMsgId, byte[] shortMessage, OptionalParameter[] optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.submitMultiple( + serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destinationAddresses, esmClass, protocolId, priorityFlag, scheduleDeliveryTime, validityPeriod, registeredDelivery, replaceIfPresentFlag, dataCoding, smDefaultMsgId, shortMessage, optionalParameters + ); + } + + public QuerySmResult queryShortMessage(String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.queryShortMessage(messageId, sourceAddrTon, sourceAddrNpi, sourceAddr); + } + + public void cancelShortMessage(String serviceType, String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, + TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, String destinationAddress) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + session.cancelShortMessage(serviceType, messageId, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddress); + } + + public void replaceShortMessage(String messageId, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, String scheduleDeliveryTime, String validityPeriod, RegisteredDelivery registeredDelivery, byte smDefaultMsgId, byte[] shortMessage) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + session.replaceShortMessage(messageId, sourceAddrTon, sourceAddrNpi, sourceAddr, scheduleDeliveryTime, validityPeriod, registeredDelivery, smDefaultMsgId, shortMessage); + } + + public DataSmResult dataShortMessage(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, String destinationAddr, ESMClass esmClass, RegisteredDelivery registeredDelivery, DataCoding dataCoding, OptionalParameter... optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { + return session.dataShortMessage(serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddr, esmClass, registeredDelivery, dataCoding, optionalParameters); + } + + public String getSessionId() { + return session.getSessionId(); + } + + public void setEnquireLinkTimer(int enquireLinkTimer) { + session.setEnquireLinkTimer(enquireLinkTimer); + } + + public int getEnquireLinkTimer() { + return session.getEnquireLinkTimer(); + } + + public void setTransactionTimer(long transactionTimer) { + session.setTransactionTimer(transactionTimer); + } + + public long getTransactionTimer() { + return session.getTransactionTimer(); + } + + public SessionState getSessionState() { + return session.getSessionState(); + } + + public void addSessionStateListener(SessionStateListener l) { + session.addSessionStateListener(l); + } + + public void removeSessionStateListener(SessionStateListener l) { + session.removeSessionStateListener(l); + } + + public long getLastActivityTimestamp() { + return session.getLastActivityTimestamp(); + } + + public void close() { + session.close(); + } + + public void unbindAndClose() { + session.unbindAndClose(); + } + + public void afterPropertiesSet() throws Exception { + log.debug( "afterPropertiesSet!"); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java new file mode 100644 index 0000000..080715b --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/SmppSessionFactoryBean.java @@ -0,0 +1,356 @@ +package org.springframework.integration.smpp.session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.DefaultPDUReader; +import org.jsmpp.DefaultPDUSender; +import org.jsmpp.SynchronizedPDUSender; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.NumberingPlanIndicator; +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.session.SessionStateListener; +import org.jsmpp.session.connection.Connection; +import org.jsmpp.session.connection.ConnectionFactory; +import org.jsmpp.session.connection.socket.SocketConnection; +import org.jsmpp.util.DefaultComposer; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.Lifecycle; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Factory bean to create a {@link SMPPSession}. Usually, you need little more than the {@link #host}, + * the {@link #port}, perhaps a {@link #password}, and a {@link #systemId}. + *

+ * The {@link SMPPSession } represents a connection to a SMSC, through which SMS messages are sent and received. + *

+ * Here is a breakdown of the supported parameters on this factory bean: + *

+ * host the SMSC host to which the session is bound (think of this as the host of your email server) + * port the SMSC port to which the session is bound (think of this as a port on your email server) + * bindType values of type {@link org.jsmpp.bean.BindType}. the bind type specifies whether this {@link SMPPSession} can send ({@link org.jsmpp.bean.BindType#BIND_TX}), receive ({@link org.jsmpp.bean.BindType#BIND_RX}), or both send and receive ({@link org.jsmpp.bean.BindType#BIND_TRX}). + * systemId the system ID for the server being bound to + * password the password for the server being bound to + * systemType the SMSC system type + * addrTon a value from the {@link org.jsmpp.bean.TypeOfNumber} enumeration. default is {@link org.jsmpp.bean.TypeOfNumber#UNKNOWN} + * addrNpi a value from the {@link org.jsmpp.bean.NumberingPlanIndicator} enumeration. Default is {@link org.jsmpp.bean.NumberingPlanIndicator#UNKNOWN} + * addressRange can be null. Specifies the address range. + * timeout a good default value is 60000 (1 minute) + * + * @author Josh Long + *

+ * todo support a proxied SMPPSession that automatically recovers from disconnects a la the examples {@link org.jsmpp.examples.gateway.AutoReconnectGateway} + * @see org.jsmpp.session.SMPPSession#SMPPSession() + * @see org.jsmpp.session.SMPPSession#connectAndBind(String, int, org.jsmpp.session.BindParameter) + * @see org.jsmpp.session.SMPPSession#connectAndBind(String, int, org.jsmpp.bean.BindType, String, String, String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, long) + * @since 2.1 + */ +public class SmppSessionFactoryBean implements FactoryBean, SmartLifecycle, InitializingBean { + + /** + * impl of {@link Lifecycle} that connects and disconnects respectively in + * {@link org.springframework.context.Lifecycle#start()} and {@link org.springframework.context.Lifecycle#stop()} + * + * @author Josh Long + */ + private Set messageReceiverListeners = new HashSet(); + private boolean autoStartup; + private volatile boolean running; + private Log log = LogFactory.getLog(getClass()); + private SessionStateListener sessionStateListener; + private boolean ssl = false; + private String host = "127.0.0.1"; + private String addressRange; + private long timeout = 60 * 1000;// 1 minute + private int port = 2775; // good default though this has been known to change + private BindType bindType = BindType.BIND_TRX; // bind as a 'transceiver' - only 3.4 of the spec requires support for this + private String systemId = getClass().getSimpleName().toLowerCase(); // what would typically be called 'user' in a user/pw scheme + private String password; + private String systemType = "cp"; + private TypeOfNumber addrTon = TypeOfNumber.UNKNOWN; + private NumberingPlanIndicator addrNpi = NumberingPlanIndicator.UNKNOWN; + + private ExtendedSmppSessionAdaptingDelegate product; + + public void setSsl(boolean ssl) { + this.ssl = ssl; + } + + public void setHost(String host) { + this.host = host; + } + + public void setPort(int port) { + this.port = port; + } + + public void setBindType(BindType bindType) { + this.bindType = bindType; + } + + public void setSystemId(String systemId) { + this.systemId = systemId; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setSystemType(String systemType) { + this.systemType = systemType; + } + + public void setAddrTon(TypeOfNumber addrTon) { + this.addrTon = addrTon; + } + + public void setAddrNpi(NumberingPlanIndicator addrNpi) { + this.addrNpi = addrNpi; + } + + /** + * this specifies the range of numbers we want to listen to - as a consumer. If you + * specify '1234' as a destination address, and want to listen / receive all messages sent + * to that number, then specify '1234' as the {@link #addressRange}. + * + * @param addressRange the range of phone numbers to receive from. + */ + public void setAddressRange(String addressRange) { + this.addressRange = addressRange; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public void setSessionStateListener(SessionStateListener sessionStateListener) { + this.sessionStateListener = sessionStateListener; + } + + public void setMessageReceiverListeners(MessageReceiverListener... listeners) { + setMessageReceiverListeners(new HashSet(Arrays.asList(listeners))); + } + + public void setMessageReceiverListeners(Set messageReceiverListeners) { + this.messageReceiverListeners = messageReceiverListeners; + } + + /** + * @return the configured SMPPSession + * @throws Exception should anything go wrong + */ + private ExtendedSmppSessionAdaptingDelegate buildSmppSession() throws Exception { + SMPPSession smppSession = null; + if (!ssl) { + smppSession = new SMPPSession(); + } else { + smppSession = new SMPPSession(new SynchronizedPDUSender(new DefaultPDUSender(new DefaultComposer())), new DefaultPDUReader(), sslConnectionFactory); + } + + ExtendedSmppSessionAdaptingDelegate extendedSmppSessionAdaptingDelegate = new ExtendedSmppSessionAdaptingDelegate(smppSession, new ConnectingLifecycle(smppSession)); + + for (MessageReceiverListener mrl : this.messageReceiverListeners) + extendedSmppSessionAdaptingDelegate.addMessageReceiverListener(mrl); + + extendedSmppSessionAdaptingDelegate.setBindType(this.bindType); + return extendedSmppSessionAdaptingDelegate; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * {@inheritDoc} + */ + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * {@inheritDoc} + */ + public void stop(Runnable callback) { + try { + log.debug("shutting down in " + getClass().getName() + "#stop(Runnable)."); + callback.run(); + } catch (Throwable throwable) { + log.warn("error when trying to shutdown " + getClass().getName() + ", could not invoke the callback's Runnable#run method"); + } + this.stop(); + } + + /** + * {@inheritDoc} + */ + public void start() { + log.debug("starting up in " + getClass().getName() + "#start()."); + ( product).start(); + this.running = true; + } + + /** + * {@inheritDoc} + */ + public void stop() { + log.debug("shutting down in " + getClass().getName() + "#stop()."); + ( product).stop(); + this.running = false; + } + + /** + * {@inheritDoc} + */ + public boolean isRunning() { + return this.running; + } + + /** + * {@inheritDoc} + */ + public int getPhase() { + return Ordered.LOWEST_PRECEDENCE; + } + + /** + * {@inheritDoc} + *

+ * delegates to {@link #buildSmppSession()} + */ + public ExtendedSmppSession getObject() throws Exception { + return product; + } + + /** + * {@inheritDoc} + */ + public Class getObjectType() { + return ExtendedSmppSessionAdaptingDelegate.class; + } + + /** + * {@inheritDoc} + */ + public boolean isSingleton() { + return true; + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + + // NB, the reference handed back by {@link org.springframework.beans.factory.FactoryBean#getObject()} isn't itself + // managed, only the factory, so we cache it and then delegate through the factory's lifecycle methods. + + Assert.notNull(this.systemId, "the systemId can't be null"); + Assert.notNull(this.host, "the host can't be null"); + Assert.notNull(this.port, "the port can't be null"); + + this.product = buildSmppSession(); + } + + /** + * singleton {@link ConnectionFactory} that handles SSL + */ + final private static ConnectionFactory sslConnectionFactory = new ConnectionFactory() { + + public Connection createConnection(String host, int port) throws IOException { + SocketFactory socketFactory = SSLSocketFactory.getDefault(); + Socket socket = socketFactory.createSocket(host, port); + return new SocketConnection(socket); + } + }; + + /** + * lifecycle implementation that simply {@link SMPPSession#connectAndBind(String, int, org.jsmpp.session.BindParameter)} and + * {@link org.jsmpp.session.SMPPSession#unbindAndClose()}. + */ + private class ConnectingLifecycle implements Lifecycle { + + private volatile boolean running; + + private SMPPSession session; + + private ConnectingLifecycle(SMPPSession smppSession) { + this.session = smppSession; + } + + public boolean isRunning() { + return this.running; + } + + public void stop() { + if (session != null) { + if (session.getSessionState().isBound()) { + try { + session.unbindAndClose(); + } catch (Throwable t) { + log.warn("couldn't close and unbind the session", t); + } + } + } else { + log.warn("the smppSession given to close is null"); + } + } + + public void start() { + try { + session.connectAndBind(host, port, bindType, systemId, password, systemType, addrTon, addrNpi, addressRange, timeout); + this.running = true; + } catch (IOException e) { + log.error("something happened when trying to connect", e); + } + } + } +} + +/* private void reconnectAfter(final long timeInMillis) { + new Thread() { + @Override + public void run() { + logger.info("Schedule reconnect after " + timeInMillis + " millis"); + try { + Thread.sleep(timeInMillis); + } catch (InterruptedException e) { + } + + int attempt = 0; + while (session == null || session.getSessionState().equals(SessionState.CLOSED)) { + try { + logger.info("Reconnecting attempt #" + (++attempt) + "..."); + session = newSession(); + } catch (IOException e) { + logger.error("Failed opening connection and bind to " + remoteIpAddress + ":" + remotePort, e); + // wait for a second + try { Thread.sleep(1000); } catch (InterruptedException ee) {} + } + } + } + }.start(); + } + + + private class SessionStateListenerImpl implements SessionStateListener { + public void onStateChange(SessionState newState, SessionState oldState, + Object source) { + if (newState.equals(SessionState.CLOSED)) { + logger.info("Session closed"); + reconnectAfter(reconnectInterval); + } + } + } + + */ diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java new file mode 100644 index 0000000..5968eac --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/session/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various classes used for Spring Integration Smpp session. + */ +package org.springframework.integration.smpp.session; \ No newline at end of file diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java new file mode 100644 index 0000000..c66f633 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentExecutingMethodHolder.java @@ -0,0 +1,44 @@ +package org.springframework.integration.smpp.util; + +import org.springframework.core.NamedThreadLocal; + +import java.lang.reflect.Method; + +/** + * A place to stash the currently executing method for advised classes. This provides the equivalent of JavaScript's arity property, + * and it's probably cheaper to implement than throwing an {@link Exception} and parsing its stack trace for the method whence the + * exception was thrown. + * + * @author Josh Long + * @since 2.1 + */ +abstract public class CurrentExecutingMethodHolder { + + public static ThreadLocal methodThreadLocal = new NamedThreadLocal("methodThreadLocal"); + + /** + * returns the currently executing thread local-bound method + * + * @return the currently executing method + */ + public static Method getCurrentlyExecutingMethod() { + return methodThreadLocal.get(); + } + + /** + * stash the currently execution method + * + * @param m the method in the throes of execution + */ + public static void setCurrentlyExecutingMethod(Method m) { + removeMethod(); + methodThreadLocal.set(m); + } + + /** + * the thread local method. + */ + public static void removeMethod() { + methodThreadLocal.remove(); + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java new file mode 100644 index 0000000..063e9f8 --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/CurrentMethodExposingMethodInterceptor.java @@ -0,0 +1,27 @@ +package org.springframework.integration.smpp.util; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * Simple method interceptor that does nothing but store the currently executing method and make it available for the duration + * of the invoked method, so that any class may introspect the currently running method + * without setting up a custom {@link MethodInterceptor} like this one. + *

+ * In a sense, this is like JavaScript's method arity feature. + * + * @author Josh Long + * @since 2.1 + */ +public class CurrentMethodExposingMethodInterceptor implements MethodInterceptor { + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + try { + CurrentExecutingMethodHolder.setCurrentlyExecutingMethod(methodInvocation.getMethod()); + return methodInvocation.proceed(); + } finally { + CurrentExecutingMethodHolder.removeMethod(); + } + } +} diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java new file mode 100644 index 0000000..2bb079f --- /dev/null +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various util classes used across Spring Integration Smpp Components. + */ +package org.springframework.integration.smpp.util; \ No newline at end of file diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.handlers b/spring-integration-smpp/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000..456793c --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/smpp=org.springframework.integration.smpp.config.xml.SmppNamespaceHandler diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.schemas b/spring-integration-smpp/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000..160c4f2 --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/smpp/spring-integration-smpp-2.2.xsd=org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd +http\://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd=org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd diff --git a/spring-integration-smpp/src/main/resources/META-INF/spring.tooling b/spring-integration-smpp/src/main/resources/META-INF/spring.tooling new file mode 100644 index 0000000..435cc7f --- /dev/null +++ b/spring-integration-smpp/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration Smpp namespace +http\://www.springframework.org/schema/integration/smpp@name=integration Smpp Namespace +http\://www.springframework.org/schema/integration/smpp@prefix=int-smpp +http\://www.springframework.org/schema/integration/smpp@icon=org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif diff --git a/spring-integration-smpp/src/main/resources/log4j.properties b/spring-integration-smpp/src/main/resources/log4j.properties new file mode 100644 index 0000000..2d4371d --- /dev/null +++ b/spring-integration-smpp/src/main/resources/log4j.properties @@ -0,0 +1,9 @@ + +log4j.rootCategory=DEBUG, S +log4j.logger.org.w3c.tidy=FATAL +log4j.logger.org.springframework.integration.smpp=DEBUG +log4j.logger.org.jsmpp=DEBUG + +log4j.appender.S = org.apache.log4j.ConsoleAppender +log4j.appender.S.layout = org.apache.log4j.PatternLayout +log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n diff --git a/spring-integration-smpp/src/main/resources/logback.xml b/spring-integration-smpp/src/main/resources/logback.xml new file mode 100644 index 0000000..93c9638 --- /dev/null +++ b/spring-integration-smpp/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss,SSS} %-5level [%logger] %msg%n + + + + + + + \ No newline at end of file diff --git a/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd new file mode 100644 index 0000000..1b3766d --- /dev/null +++ b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp-2.2.xsd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + The definition for the Spring Integration Smpp + Inbound Channel Adapter. + + + + + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + Channel which the sms will be put in, whey they come from the SMSC. + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Inbound Gateway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines an Outbound Channel Adapter. + + + + + + + + + + + + Channel from which messages will be output. + When a message is sent to this channel it will + cause the query + to be executed. + + + + + + + + + + + Source address will be used as sender for SMPP + + + + + + + The default source address Type of Number. Default is UNKNOWN. + + + + + + + + + + Reference to jsmpp time formatter + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Outbound Gateway + + + + + + + + + Reference to jsmpp time formatter + + + + + + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to a SubscribableChannel. + + + + + + + + + + + + Defines reference to smpp session + + + + + + + + + + + + + + + + Reference to extended smpp session + + + + + + + + + + + + + + + Defines the Spring Integration Smpp Session + + + + + + + + + + + Type of SMPP connection bind. + + + + + + + + Set enquire link timer (in milliseconds). + + + + + Set transaction timer (in milliseconds). + + + + + + Host to connect (default 127.0.0.1) + + + + + Address range we are listening to + + + + + Connection timeout (default 60000ms / 1 minute) + + + + + Port to connect (default 2775) + + + + + + + + The address Type of Number. Default is UNKNOWN. + + + + + + + + The address Numbering Plan Indicator. Default is UNKNOWN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bind as Transmitter (Sending Only) + + + + + + + Bind as Receiver (Receive Only) + + + + + + + Bind as Transceiver (Sending and Receive) + + + + + + + + + + + Identifies the underlying Spring bean definition, which is an + instance of either 'EventDrivenConsumer' or 'PollingConsumer', + depending on whether the component's input channel is a + 'SubscribableChannel' or 'PollableChannel'. + + + + + + + Reference to extended smpp session + + + + + + + + + + + + + + Defines common configuration for gateway adapters. + + + + + + + + + + + + + + + + + + + + + + + + + The receiving Message Channel of this endpoint. + + + + + + + + + + + + Source address will be used as sender for SMPP + + + + + + + The default source address Type of Number. Default is UNKNOWN. + + + + + + + + + diff --git a/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif b/spring-integration-smpp/src/main/resources/org/springframework/integration/smpp/config/xml/spring-integration-smpp.gif new file mode 100644 index 0000000000000000000000000000000000000000..41b369fece0e576ecd920400b221fd33abbaf5c4 GIT binary patch literal 572 zcmZ?wbhEHb6krfwc*Xz%|NsBj^POrMHnn5wI!WKA$M#t>l?H4BPzO?b&{cRWSFFSB< z^|`x}P0QyVy|U`SiF40CoO%A?(2mZdyE;$q?mn}(`^J&Ji~DRU^KH0`_S=*f zu`(xWd1~172%niTfzv{QrUeE70}32q)Fc#tvM@3*q%i1!3doJ z$QZroAYKk;o)!}g_c$BZP}??kHkSD;%JZ2d6u9}BT8+&ujM`ePV%-d#6T`zi);F^_ zIR!9@O3I6^oV(z-tdN`(pX{#l&0dZgror~-%^VCYeSN$`0@iCL`334n#7Fs`w{uBO x(B)aWd{Qfm5=#n`xT2uIN}aG328D}QI)&6(d>(9QbkfS;6w=UeP!nLV1^^EL*)#wE literal 0 HcmV?d00001 diff --git a/spring-integration-smpp/src/main/resources/todo.txt b/spring-integration-smpp/src/main/resources/todo.txt new file mode 100644 index 0000000..0d04ca0 --- /dev/null +++ b/spring-integration-smpp/src/main/resources/todo.txt @@ -0,0 +1 @@ +- figure out a clean way to furnish our own Executor implementation diff --git a/spring-integration-smpp/src/reference/docbook/history.xml b/spring-integration-smpp/src/reference/docbook/history.xml new file mode 100644 index 0000000..88f1eb4 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/history.xml @@ -0,0 +1,19 @@ + + + Change History + + + + + + + + + + + +
ReleaseDateChanges
1.0.02012.12.14Initial release migrated from Spring sandbox
+ +
diff --git a/spring-integration-smpp/src/reference/docbook/images/logo.png b/spring-integration-smpp/src/reference/docbook/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8c71c13a0e5606e9d87f75e37fe2874cb44beb51 GIT binary patch literal 17393 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 + + + Spring Integration Smpp Adapter + Smpp Adapter ${version} + Spring Integration + ${version} + + + + + + + + + + + + + + Josh Long + Johanes Soetanto + + + © SpringSource Inc., 2012 + + + + + + + What's new? + + + If you are interested in the changes and features, that were introduced in + earlier versions, please take a look at chapter: + + + + + + + + + + Integration Adapters + + + Spring Integration adapter for SMPP (Short Message Peer-to-Peer) includes + inbound channel adapter to receive sms from SMSC (Short Message Service Center), + outbound channel adapter to send sms through SMSC, and gateways to + send/receive from SMSC. + + + + + + Appendices + + Advanced Topics and Additional Resources + + + + diff --git a/spring-integration-smpp/src/reference/docbook/resources.xml b/spring-integration-smpp/src/reference/docbook/resources.xml new file mode 100644 index 0000000..678ca1b --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/resources.xml @@ -0,0 +1,32 @@ + + + Additional Resources + +

+ Spring Integration Home + + The definitive source of information about Spring Integration is the + Spring Integration Home at + http://www.springsource.org. That site serves as a hub of + information and is the best place to find up-to-date announcements about the project as well as links to + articles, blogs, and new sample applications. + +
+ +
+ SMPP Home + + You can get more information on SMPP from + + + This adapter uses jsmpp as the underlying library to communicate with SMPP. Jsmpp is Java implementation of + SMPP protocol v3.4 and can be retrieved from + + + You can get SMSC simulator from + + +
+ + diff --git a/spring-integration-smpp/src/reference/docbook/smpp.xml b/spring-integration-smpp/src/reference/docbook/smpp.xml new file mode 100644 index 0000000..dbecb94 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/smpp.xml @@ -0,0 +1,85 @@ + + + Smpp Adapter + + The Spring Integration Smpp Adapter provides... + + + + Outbound Channel adapter + + + Outbound Gateway + + + Inbound Channel Adapter + + + Inbound Gateway + + + + + To use Spring Integration adapter for SMPP, you have to import the XML namespace. For example, you can + have following XML: + + + + + ]]> + + + + Meanwhile, you have to define your SMSC server information. For example you can define + server as following: + + + + + + //TODO: more documentation +
+ Outbound Channel Adapter + + Outbound channel adapter is to send sms into SMSC from channels in Spring Integration. + +
+ +
+ Inbound Channel Adapter + + Inbound channel adapter is used to receive sms from SMSC into Spring Integration channel. + +
+ +
+ Outbound Gateway + + Outbound gateway is similar to outbound channel adapter except that it can also be used to get + a result on the reply channel after sending. + +
+ +
+ Inbound Gateway + + Inbound gateway is similar to inbound channel adapter except that it can also be used to return + a result on the reply channel after receiving. + +
+ +
diff --git a/spring-integration-smpp/src/reference/docbook/whats-new.xml b/spring-integration-smpp/src/reference/docbook/whats-new.xml new file mode 100644 index 0000000..90a7765 --- /dev/null +++ b/spring-integration-smpp/src/reference/docbook/whats-new.xml @@ -0,0 +1,24 @@ + + + What's new? + + The Spring Integration adapter for SMPP includes two adapters and two gateways: + + + + Inbound Channel Adapter to receive sms from SMSC. + + + Outbound Channel Adapter to send sms through SMSC. + + + Inbound Gateway to receive sms from SMSC. + + + Outbound Gateway to send through SMSC. + + + + diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java new file mode 100644 index 0000000..1e318a9 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java @@ -0,0 +1,76 @@ +package org.springframework.integration.smpp; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessagingException; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; +/** + * Simple test, more of the SMPP API than anything, at the moment. + *

+ * Demonstrates that the {@link org.springframework.integration.smpp.session.SmppSessionFactoryBean} works, too. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppConnection-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppConnection { + + @Value("${smpp.systemId}") + String destination; + + @Autowired SubscribableChannel inboundChannel; + @Autowired MessageChannel outboundChannel; + @Autowired SubscribableChannel receiptChannel; + + Message messageOut; + + AtomicInteger count = new AtomicInteger(); + + @Before + public void setUp() { + messageOut = MessageBuilder.withPayload("This is the message") + .setHeader(SmppConstants.DST_ADDR, destination) + .setHeader(SmppConstants.SRC_ADDR, destination) + .build(); + } + + + @Test + public void testSmppConnection() throws Throwable { + MessageHandler standardInboundHandler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + System.out.println("Standard Inbound channel receive: " + message); + count.incrementAndGet(); + } + }; + MessageHandler receiptHandler = new MessageHandler() { + @Override + public void handleMessage(Message message) throws MessagingException { + String received = message.getPayload().toString(); + System.out.println("Outbound channel output receive receipt: " + received); + } + }; + inboundChannel.subscribe(standardInboundHandler); + receiptChannel.subscribe(receiptHandler); + + outboundChannel.send(messageOut); + + Thread.sleep(60000); + } + + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java new file mode 100644 index 0000000..8f8bfc4 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundChannelAdapter.java @@ -0,0 +1,75 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +@ContextConfiguration("classpath:TestSmppInboundChannelAdapter-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppInboundChannelAdapter { + + private String smsMessageToSend = "test SMPP message being sent from this time:"+ System.currentTimeMillis()+"."; + + @Value("${test.dst.number}") String number; + + private Log log = LogFactory.getLog(getClass()); + + private AtomicInteger atomicInteger = new AtomicInteger(); + + @Value("#{outbound}") SubscribableChannel out; + + @Value("#{inbound}") SubscribableChannel in; + + @Autowired + private ApplicationContext context; + + @Before + public void before () throws Throwable { + Assert.assertNotNull(this.number); + } + + String lastReceivedSms = null ; + + @Test + public void testReceiving() throws Throwable { + + + in.subscribe(new MessageHandler() { + public void handleMessage(Message message) throws MessagingException { + lastReceivedSms =(message.getPayload().toString()); + atomicInteger.incrementAndGet(); + } + }); + + // lets send something + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, this.number) + .setHeader(SmppConstants.DST_ADDR, this.number) + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + out.send(smsMsg); + + Thread.sleep(1000 * 10); + + Assert.assertTrue(atomicInteger.intValue()>0); + Assert.assertEquals(atomicInteger.intValue() ,1); + Assert.assertEquals(this.smsMessageToSend, lastReceivedSms); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java new file mode 100644 index 0000000..99f55fd --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java @@ -0,0 +1,90 @@ +package org.springframework.integration.smpp; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.SubscribableChannel; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.smpp.core.SmesMessageSpecification; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +@ContextConfiguration("classpath:TestSmppInboundGateway-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppInboundGateway { + + @Value("#{out1}") SubscribableChannel out1; + @Value("#{out2}") SubscribableChannel out2; + @Value("#{in1}") SubscribableChannel in1; + @Value("#{in2}") SubscribableChannel in2; + + // test data + String toPhone = "33333"; // todo make sure the gateway automatically 'flips' with the to/from on the reply SMS + String fromPhone = "1111"; + long now = System.currentTimeMillis(); + String smsRequest = "this is a request created at " + now; + String smsResponse = "this is a response created at " + now; + + @Value("#{outboundSession}") + ExtendedSmppSession outSession; + + @Before public void before(){ + outSession.start(); + } + + AtomicInteger count = new AtomicInteger(); + + @Test + public void testSendingAndReceivingAnSms() throws Throwable { + + // the gateway *receives* SMS messages, and then expects a reply. + // So we need to both *send* an SMS for the gateway to receive, and then *receive* the reply + // from the gateway to confirm it was sent... + + // two sends, two receives, one pair taken care of the by the gateway + // it would be ideal if we could in essence wrap this inbound gateway with an outbound gateway so that.. + + // outbound-gw: send + //// inbound-gw: receive, produces reply + //// inbound-gw: send + // outbound-gw: receive + // however atm thats not supported by the outbound-gw, it only 'replies' with the message ID of the outbound send + + // anyway.... + //1) lets send an outbound message so that our gateway has something to listen for + + SmesMessageSpecification.newSmesMessageSpecification(outSession, this.fromPhone, this.toPhone, this.smsRequest).send(); + + MessageHandler inboundMessageHandler = new AbstractReplyProducingMessageHandler() { + @Override + protected Object handleRequestMessage(Message requestMessage) { + Assert.assertEquals(requestMessage.getPayload(), smsRequest); + count.incrementAndGet(); + return MessageBuilder.withPayload( + smsResponse).copyHeadersIfAbsent( requestMessage.getHeaders()).build(); + + } + }; + this.in1.subscribe(inboundMessageHandler); + + // launch the whole thing + + + + Thread.sleep(1000 * 10); + + Assert.assertEquals(this.count.intValue(),1); + + + } +} + + diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java new file mode 100644 index 0000000..ab8a29e --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundChannelAdapter.java @@ -0,0 +1,60 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * exercises the outbound adapter. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppOutboundChannelAdapter-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppOutboundChannelAdapter { + + + private Log log = LogFactory.getLog(getClass()); + + @Value("#{session}") + private ExtendedSmppSession smppSession; + + @Value("#{outboundSms}") + private MessageChannel messageChannel; + + private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " + + "on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")"; + + + @Before + public void start (){ + + + } + + @Test + public void testSendingAndReceivingASmppMessageUsingRawApi() throws Throwable { + + + log.debug( "sending a message."); + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, "1616") + .setHeader(SmppConstants.DST_ADDR, "628176504657") + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + + this.messageChannel.send(smsMsg); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java new file mode 100644 index 0000000..d2516e7 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java @@ -0,0 +1,59 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.SMSCDeliveryReceipt; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/*** + * Simple tests for the gateway which differs from the outbound adapter only in that it supports + * sending the message ID back + * + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppOutboundGateway-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppOutboundGateway { + + + private MessagingTemplate messagingTemplate = new MessagingTemplate(); + private Log log = LogFactory.getLog(getClass()); + + @Value("#{session}") + private ExtendedSmppSession smppSession; + + @Value("#{outboundSms}") + private MessageChannel messageChannel; + + private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " + + "on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")"; + + @Test + public void testSendingAndReceivingASmppMessageUsingRawApi() throws Throwable { + + Message smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + .setHeader(SmppConstants.SRC_ADDR, "1616") + .setHeader(SmppConstants.DST_ADDR, "628176504657") + .setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS) + .build(); + + Message response = this.messagingTemplate.sendAndReceive(this.messageChannel,smsMsg); + + Assert.assertNotNull(response); + Assert.assertTrue(response.getPayload() instanceof String); + log.info("received the SMS Message ID: " + response.getPayload()); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java new file mode 100644 index 0000000..13f1357 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppSessionFactoryBean.java @@ -0,0 +1,128 @@ +package org.springframework.integration.smpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jsmpp.bean.AlertNotification; +import org.jsmpp.bean.BindType; +import org.jsmpp.bean.DataSm; +import org.jsmpp.bean.DeliverSm; +import org.jsmpp.extra.ProcessRequestException; +import org.jsmpp.session.DataSmResult; +import org.jsmpp.session.MessageReceiverListener; +import org.jsmpp.session.SMPPSession; +import org.jsmpp.session.Session; +import org.jsmpp.util.AbsoluteTimeFormatter; +import org.junit.*; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.smpp.session.DelegatingMessageReceiverListener; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.smpp.session.ExtendedSmppSessionAdaptingDelegate; +import org.springframework.integration.smpp.session.SmppSessionFactoryBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.Set; + +/** + * Simple test, more of the SMPP API than anything, at the moment. + *

+ * Demonstrates that the {@link org.springframework.integration.smpp.session.SmppSessionFactoryBean} works, too. + * + * @author Josh Long + * @since 2.1 + */ +@ContextConfiguration("classpath:TestSmppSessionFactoryBean-context.xml") +@RunWith(SpringJUnit4ClassRunner.class) +public class TestSmppSessionFactoryBean { + + private Log logger = LogFactory.getLog(getClass()); + + @Autowired + @Qualifier("session") + private ExtendedSmppSessionAdaptingDelegate smppSession; + + private AbsoluteTimeFormatter timeFormatter = new AbsoluteTimeFormatter(); + + private String host = "127.0.0.1"; + + private int port = 2775; + + private String systemId = "smppclient1"; + + private String password = "password"; + + @Test + public void testSmppSessionFactory() throws Throwable { + + SmppSessionFactoryBean smppSessionFactoryBean = new SmppSessionFactoryBean(); + smppSessionFactoryBean.setSystemId(this.systemId); + smppSessionFactoryBean.setPort(this.port); + smppSessionFactoryBean.setPassword(this.password); + smppSessionFactoryBean.setHost(this.host); + smppSessionFactoryBean.afterPropertiesSet(); + + ExtendedSmppSession extendedSmppSession = smppSessionFactoryBean.getObject(); + Assert.assertTrue(extendedSmppSession instanceof ExtendedSmppSessionAdaptingDelegate); + + ExtendedSmppSessionAdaptingDelegate es = (ExtendedSmppSessionAdaptingDelegate) extendedSmppSession; + Assert.assertNotNull("the factoried object should not be null", extendedSmppSession); + es.addMessageReceiverListener(new MessageReceiverListener() { + public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { + logger.debug("in onAcceptDeliverSm"); + } + + public void onAcceptAlertNotification(AlertNotification alertNotification) { + logger.debug("in onAcceptAlertNotification"); + } + + public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { + logger.debug("in onAcceptDataSm"); + return null; + } + }); + Assert.assertEquals(extendedSmppSession.getClass(), ExtendedSmppSessionAdaptingDelegate.class); + Assert.assertNotNull(es.getTargetClientSession()); + Assert.assertTrue(es.getTargetClientSession() != null); + final SMPPSession s = es.getTargetClientSession(); + + ReflectionUtils.doWithFields(ExtendedSmppSessionAdaptingDelegate.class, new ReflectionUtils.FieldCallback() { + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.getName().equalsIgnoreCase("messageReceiverListener")) { + field.setAccessible(true); + MessageReceiverListener messageReceiverListener = (MessageReceiverListener) field.get(s); + Assert.assertNotNull(messageReceiverListener); + Assert.assertTrue(messageReceiverListener instanceof DelegatingMessageReceiverListener); + final DelegatingMessageReceiverListener delegatingMessageReceiverListener = (DelegatingMessageReceiverListener) messageReceiverListener; + ReflectionUtils.doWithFields(DelegatingMessageReceiverListener.class, new ReflectionUtils.FieldCallback() { + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.getName().equals("messageReceiverListenerSet")) { + field.setAccessible(true); + @SuppressWarnings("unchecked") + Set l = (Set) field.get(delegatingMessageReceiverListener); + Assert.assertEquals(l.size(), 1); + } + } + }); + } + } + }); + } + + @Test + public void testWhetherTheBeansAlreadyStarted() throws Throwable { + + Assert.assertNotNull("session shouldn't be null", this.smppSession); + + Assert.assertTrue("the " + ExtendedSmppSession.class.getName() + " should be started if the " + + "container supports Lifecycle, otherwise, it must be manually #start'd", + (smppSession).isRunning()); + + BindType bindType = smppSession.getBindType(); + + Assert.assertNotNull("the bind type should not be null", bindType); + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java new file mode 100644 index 0000000..859f471 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/MockSmppSessionFactory.java @@ -0,0 +1,30 @@ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.BindType; +import org.springframework.integration.smpp.session.ExtendedSmppSession; + +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +/** + * Mock smpp session factory for testing. + * @author Johanes Soetanto + * @since 2.2 + */ +public class MockSmppSessionFactory { + + public static ExtendedSmppSession getOutSmppSession() { + ExtendedSmppSession mock = createNiceMock(ExtendedSmppSession.class); + expect(mock.getBindType()).andReturn(BindType.BIND_TX).anyTimes(); + replay(mock); + return mock; + } + + public static ExtendedSmppSession getInSmppSession() { + ExtendedSmppSession mock = createNiceMock(ExtendedSmppSession.class); + expect(mock.getBindType()).andReturn(BindType.BIND_RX).anyTimes(); + replay(mock); + return mock; + } +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java new file mode 100644 index 0000000..b786f7e --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.java @@ -0,0 +1,66 @@ +/* +uytea * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package org.springframework.integration.smpp.config.xml; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.smpp.inbound.SmppInboundChannelAdapter; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.*; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundChannelAdapterParserTests { + + private ConfigurableApplicationContext context; + + private SmppInboundChannelAdapter consumer; + + @Test + public void testInboundChannelAdapterParser() throws Exception { + + setUp("SmppInboundChannelAdapterParserTests.xml", getClass(), "smppInboundChannelAdapter"); + + final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "channel", AbstractMessageChannel.class); + assertEquals("out", outputChannel.getComponentName()); + + ExtendedSmppSession session = TestUtils.getPropertyValue(consumer, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + boolean autoStartup = TestUtils.getPropertyValue(consumer, "autoStartup" , Boolean.class); + assertTrue(autoStartup); + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls, String consumerId){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean(consumerId, SmppInboundChannelAdapter.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java new file mode 100644 index 0000000..9ac2ac5 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.mapping.InboundMessageMapper; +import org.springframework.integration.mapping.OutboundMessageMapper; +import org.springframework.integration.smpp.inbound.SmppInboundGateway; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppInboundGatewayParserTests { + + private ConfigurableApplicationContext context; + + private SmppInboundGateway gateway; + + @Test + public void testRetrievingInboundGatewayParser() throws Exception { + setUp("SmppInboundGatewayParserTests.xml", getClass(), "smppInboundGateway"); + + // reply timeout + long requestTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); + assertEquals(10000, requestTimeout); + + // request timeout + long replyTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.receiveTimeout", Long.class); + assertEquals(5000, replyTimeout); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + System.out.println("Session: "+session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.INTERNATIONAL); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("123456789", sourceAddress); + + // channels + AbstractMessageChannel requestChannel = TestUtils.getPropertyValue(gateway, "requestChannel", AbstractMessageChannel.class); + assertEquals("requestChannel", requestChannel.getComponentName()); + AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(gateway, "replyChannel", AbstractMessageChannel.class); + assertEquals("replyChannel", outputChannel.getComponentName()); + AbstractMessageChannel errorChannel = TestUtils.getPropertyValue(gateway, "errorChannel", AbstractMessageChannel.class); + assertEquals("errorChannel", errorChannel.getComponentName()); + + // mappers + InboundMessageMapper inboundMessageMapper = TestUtils.getPropertyValue(gateway, "requestMapper", InboundMessageMapper.class); + assertNotNull(inboundMessageMapper); + OutboundMessageMapper outboundMessageMapper = TestUtils.getPropertyValue(gateway, "messageConverter.outboundMessageMapper", OutboundMessageMapper.class); + assertNotNull(outboundMessageMapper); + + } + + + + @After + public void tearDown() { + if (context != null) { + context.close(); + } + } + + public void setUp(String name, Class cls, String gatewayId) { + context = new ClassPathXmlApplicationContext(name, cls); + gateway = this.context.getBean(gatewayId, SmppInboundGateway.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java new file mode 100644 index 0000000..9e54245 --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.TimeFormatter; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.smpp.core.SmppConstants; +import org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundChannelAdapterParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testRetrievingOutboundChannelAdapterParser() throws Exception { + setUp("SmppOutboundChannelAdapterParserTests.xml", getClass()); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + assertEquals("target", inputChannel.getComponentName()); + + final SmppOutboundChannelAdapter gateway = TestUtils.getPropertyValue(this.consumer, "handler", SmppOutboundChannelAdapter.class); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.SUBSCRIBER_NUMBER); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("12345", sourceAddress); + + // this is not set, should be default value + TimeFormatter timeFormatter = TestUtils.getPropertyValue(gateway, "timeFormatter", TimeFormatter.class); + assertNotNull(timeFormatter); + + // I send message + Message message = MessageBuilder.withPayload("Yuhuu !!! i am connected using Spring Integration namespace") + .setHeader(SmppConstants.SRC_ADDR, "pavel") + .setHeader(SmppConstants.DST_ADDR, "pavel") + .build(); + + MessagingTemplate template = context.getBean("messagingTemplate", MessagingTemplate.class); + template.send("target", message); + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean("smppOutboundChannelAdapter", EventDrivenConsumer.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java new file mode 100644 index 0000000..980be5f --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.integration.smpp.config.xml; + +import org.jsmpp.bean.TypeOfNumber; +import org.jsmpp.util.TimeFormatter; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.smpp.outbound.SmppOutboundGateway; +import org.springframework.integration.smpp.session.ExtendedSmppSession; +import org.springframework.integration.test.util.TestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + + +/** + * @author Johanes Soetanto + * @since 2.2 + * + */ +public class SmppOutboundGatewayParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testRetrievingOutboundGatewayParser() throws Exception { + setUp("SmppOutboundGatewayParserTests.xml", getClass(), "smppOutboundGateway"); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + assertEquals("in", inputChannel.getComponentName()); + + final SmppOutboundGateway gateway = TestUtils.getPropertyValue(this.consumer, "handler", SmppOutboundGateway.class); + + long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); + assertEquals(100, sendTimeout); + + ExtendedSmppSession session = TestUtils.getPropertyValue(gateway, "smppSession", ExtendedSmppSession.class); + assertNotNull(session); + + TypeOfNumber ton = TestUtils.getPropertyValue(gateway, "defaultSourceAddressTypeOfNumber", TypeOfNumber.class); + assertEquals(ton, TypeOfNumber.NETWORK_SPECIFIC); + + String sourceAddress = TestUtils.getPropertyValue(gateway, "defaultSourceAddress", String.class); + assertEquals("123456789", sourceAddress); + + int order = TestUtils.getPropertyValue(gateway, "order", Integer.class); + assertEquals(17, order); + + AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(gateway, "outputChannel", AbstractMessageChannel.class); + assertEquals("out", outputChannel.getComponentName()); + + // this is not set, should be default value + TimeFormatter timeFormatter = TestUtils.getPropertyValue(gateway, "timeFormatter", TimeFormatter.class); + assertNotNull(timeFormatter); + } + + + + @After + public void tearDown() { + if (context != null) { + context.close(); + } + } + + public void setUp(String name, Class cls, String gatewayId) { + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean(gatewayId, EventDrivenConsumer.class); + } + +} diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java new file mode 100644 index 0000000..7b346cb --- /dev/null +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/util/TestCurrentExecutingMethodAdvice.java @@ -0,0 +1,40 @@ +package org.springframework.integration.smpp.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.aop.framework.ProxyFactoryBean; + +/** + * @author Josh Long + * @since 2.1 + */ +public class TestCurrentExecutingMethodAdvice { + static String currentMethodName() { + return CurrentExecutingMethodHolder.getCurrentlyExecutingMethod().getName(); + } + + static public class TestClassWithAMethod { + public TestClassWithAMethod() { + } + + private Log l = LogFactory.getLog(getClass()); + + public void testMe() throws Throwable { + Assert.assertEquals("testMe", currentMethodName()); + l.debug("The currently executing method name is " + currentMethodName()); + } + } + + @Test + public void testLoggingTheCurrentlyExecutingMethodName() throws Throwable { + ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); + proxyFactoryBean.setProxyTargetClass(true); + proxyFactoryBean.addAdvice(new CurrentMethodExposingMethodInterceptor()); + proxyFactoryBean.setTarget(new TestClassWithAMethod()); + + TestClassWithAMethod testClassWithAMethod = (TestClassWithAMethod) proxyFactoryBean.getObject(); + testClassWithAMethod.testMe(); + } +} diff --git a/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml new file mode 100644 index 0000000..95a5cd5 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml new file mode 100644 index 0000000..b091d5a --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml new file mode 100644 index 0000000..8495da0 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml new file mode 100644 index 0000000..59fc3e6 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml new file mode 100644 index 0000000..b4d368a --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml new file mode 100644 index 0000000..a4d4bda --- /dev/null +++ b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/log4j.properties b/spring-integration-smpp/src/test/resources/log4j.properties new file mode 100644 index 0000000..8fda257 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n + +log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.jpa=INFO diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml new file mode 100644 index 0000000..24f3ce8 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml new file mode 100644 index 0000000..39cda84 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml new file mode 100644 index 0000000..9fe4782 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml new file mode 100644 index 0000000..92b7757 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/readme.txt b/spring-integration-smpp/src/test/resources/readme.txt new file mode 100644 index 0000000..257aa28 --- /dev/null +++ b/spring-integration-smpp/src/test/resources/readme.txt @@ -0,0 +1,10 @@ +README + +you're going to need a good server to test with. I've found http://www.seleniumsoftware.com/regform.php?itemdesc=SMPPSim.tar.gz to work well. Simply download it, +cd into the folder and execute ./startsmppsim.(sh|bat). Make sure the script is executable. The coniguration for this simulator is in conf/smppsim.props + +Alternatively, the JSMPP project itself has an SMPP simulator as well. + +If you want to go the full nine yards, it's pretty easy to find full-blow SMPP servers like Kanell out there. + +http://www.techdive.in/java/send-sms-using-jsmpp diff --git a/spring-integration-smpp/src/test/resources/smpp.properties b/spring-integration-smpp/src/test/resources/smpp.properties new file mode 100644 index 0000000..60d313a --- /dev/null +++ b/spring-integration-smpp/src/test/resources/smpp.properties @@ -0,0 +1,7 @@ +smpp.host=127.0.0.1 +smpp.systemId=pavel +smpp.password=wpsd +smpp.port=2775 + + +test.dst.number=123456 From c0c5c0afcac5040bdea00bbeeacc4234e191276c Mon Sep 17 00:00:00 2001 From: Johanes Soetanto Date: Wed, 19 Dec 2012 16:33:24 +1100 Subject: [PATCH 2/4] INTEXT-30 - Add SMPP Support - update to version 2.2.0.BUILD-SNAPSHOT --- spring-integration-smpp/gradle.properties | 2 +- spring-integration-smpp/src/dist/changelog.txt | 2 +- spring-integration-smpp/src/reference/docbook/history.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-integration-smpp/gradle.properties b/spring-integration-smpp/gradle.properties index bebfcbc..f212918 100644 --- a/spring-integration-smpp/gradle.properties +++ b/spring-integration-smpp/gradle.properties @@ -1 +1 @@ -version=1.0.0.BUILD-SNAPSHOT +version=2.2.0.BUILD-SNAPSHOT diff --git a/spring-integration-smpp/src/dist/changelog.txt b/spring-integration-smpp/src/dist/changelog.txt index 7e74bd5..39bddd0 100644 --- a/spring-integration-smpp/src/dist/changelog.txt +++ b/spring-integration-smpp/src/dist/changelog.txt @@ -1,7 +1,7 @@ Spring Integration Smpp Adapter CHANGELOG ========================================= -Feature in version 1.0.0 +Feature in version 2.2.0 1. Inbound channel adapter diff --git a/spring-integration-smpp/src/reference/docbook/history.xml b/spring-integration-smpp/src/reference/docbook/history.xml index 88f1eb4..894f7f5 100644 --- a/spring-integration-smpp/src/reference/docbook/history.xml +++ b/spring-integration-smpp/src/reference/docbook/history.xml @@ -10,7 +10,7 @@ Changes - 1.0.0 + 2.2.0 2012.12.14 Initial release migrated from Spring sandbox From f2a0cb7cbad1af3eb763f5be412914c9b6a98100 Mon Sep 17 00:00:00 2001 From: Johanes Soetanto Date: Wed, 19 Dec 2012 22:17:42 +1100 Subject: [PATCH 3/4] Change group id --- spring-integration-smpp/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-integration-smpp/build.gradle b/spring-integration-smpp/build.gradle index afcbe4a..9c30378 100644 --- a/spring-integration-smpp/build.gradle +++ b/spring-integration-smpp/build.gradle @@ -14,7 +14,7 @@ apply from: "${rootProject.projectDir}/publish-maven.gradle" apply plugin: 'eclipse' apply plugin: 'idea' -group = 'org.springframework.integration.smpp' +group = 'org.springframework.integration' repositories { maven { url 'http://repo.springsource.org/libs-milestone' } From e4f2f8390921586afc1721976f84e335d217a2f4 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Wed, 9 Jan 2013 16:32:39 -0500 Subject: [PATCH 4/4] INTEXT-30 - Cleanup --- spring-integration-smpp/README.md | 29 ++- spring-integration-smpp/build.gradle | 164 ++++++---------- .../gradle/wrapper/gradle-wrapper.jar | Bin 39770 -> 46670 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- spring-integration-smpp/gradlew | 4 +- spring-integration-smpp/gradlew.bat | 180 +++++++++--------- .../smpp/core/SmesMessageSpecification.java | 11 +- .../integration/smpp/core/SmppConstants.java | 4 +- .../src/main/resources/log4j.properties | 9 - .../src/main/resources/logback.xml | 11 -- .../src/main/resources/todo.txt | 1 - .../integration/smpp/TestSmppConnection.java | 2 +- .../smpp/TestSmppInboundGateway.java | 2 - .../resources/TestSmppConnection-context.xml | 71 ++++--- .../TestSmppInboundChannelAdapter-context.xml | 97 +++++----- .../TestSmppInboundGateway-context.xml | 96 +++++----- ...TestSmppOutboundChannelAdapter-context.xml | 48 ++--- .../TestSmppOutboundGateway-context.xml | 48 ++--- .../TestSmppSessionFactoryBean-context.xml | 36 ++-- .../src/test/resources/log4j.properties | 2 +- .../SmppInboundChannelAdapterParserTests.xml | 29 ++- .../xml/SmppInboundGatewayParserTests.xml | 87 ++++----- .../SmppOutboundChannelAdapterParserTests.xml | 56 +++--- .../xml/SmppOutboundGatewayParserTests.xml | 67 ++++--- .../src/test/resources/readme.txt | 10 - .../src/test/resources/smpp.properties | 2 +- 26 files changed, 493 insertions(+), 577 deletions(-) delete mode 100644 spring-integration-smpp/src/main/resources/log4j.properties delete mode 100644 spring-integration-smpp/src/main/resources/logback.xml delete mode 100644 spring-integration-smpp/src/main/resources/todo.txt delete mode 100644 spring-integration-smpp/src/test/resources/readme.txt diff --git a/spring-integration-smpp/README.md b/spring-integration-smpp/README.md index 42f692a..1982241 100644 --- a/spring-integration-smpp/README.md +++ b/spring-integration-smpp/README.md @@ -1,8 +1,7 @@ Spring Integration Smpp Adapter ================================================= -The Spring Integration Smpp allows you to receive/send sms to SMSC using SMPP protocol. This Spring Integration -extension is migrated from Spring's sandbox spring-integration-smpp project. +The Spring Integration Smpp allows you to receive/send [Short Message Service][] (SMS) messages to a [Short message service center][] (SMSC) using the [SMPP][] protocol. # Components @@ -11,8 +10,19 @@ extension is migrated from Spring's sandbox spring-integration-smpp project. * Inbound Channel Adapter * Inbound Gateway +# Requirements -## More to come... +For running the tests you're going to need a good server to test with: + +There are 2 options: + +**SMPPSim** - http://www.seleniumsoftware.com/regform.php?itemdesc=SMPPSim.tar.gz + +Simply download it, cd into the folder and execute `./startsmppsim.(sh|bat)`. Make sure the script is executable. The configuration for this simulator is in *conf/smppsim.props* + +Another option is **smsssim** and smsctest from http://opensmpp.logica.com/CommonPart/Download/download2.html + +Alternatively, the JSMPP project itself has an SMPP simulator as well. It is also possible to use a full-blow SMPP servers like *Kanell*. # Building @@ -61,3 +71,16 @@ To generate IDEA metadata (.iml and .ipr files), do the following: For more information, please visit the Spring Integration website at: [http://www.springsource.org/spring-integration](http://www.springsource.org/spring-integration) + +# TODO + +* figure out a clean way to furnish our own Executor implementation + +# Resources + +* http://www.techdive.in/java/send-sms-using-jsmpp +* http://www.linkedin.com/answers/technology/information-technology/telecommunications/TCH_ITS_TCI/461130-44316394 + +[SMPP]: http://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer +[Short Message Service]: http://en.wikipedia.org/wiki/Short_Message_Service +[Short message service center]: http://en.wikipedia.org/wiki/Short_message_service_center diff --git a/spring-integration-smpp/build.gradle b/spring-integration-smpp/build.gradle index 9c30378..7b127e7 100644 --- a/spring-integration-smpp/build.gradle +++ b/spring-integration-smpp/build.gradle @@ -18,69 +18,27 @@ group = 'org.springframework.integration' repositories { maven { url 'http://repo.springsource.org/libs-milestone' } - maven { url 'http://repo.springsource.org/plugins-release' } // for bundlor - maven { url 'http://repo.maven.apache.org/maven2'} - maven { url 'https://source.sakaiproject.org/maven2'} + maven { url 'http://repo.springsource.org/plugins-release' } } -// ensure JDK 5 compatibility (GRADLE-18; INT-1578) sourceCompatibility=1.6 targetCompatibility=1.6 ext { - jsmppVersion = '2.1.0' - slf4jVersion = '1.6.6' - commonsLangVersion = '2.6' - commonsBeanUtilsVersion= '1.8.3' - aspectjVersion = '1.6.8' - cglibVersion = '2.2' - commonsNetVersion = '3.0.1' + jsmppVersion = '2.1.0' + slf4jVersion = '1.6.6' + commonsLangVersion = '2.6' + commonsBeanUtilsVersion= '1.8.3' easymockVersion = '2.3' - groovyVersion = '1.8.5' - jacksonVersion = '1.9.2' - javaxActivationVersion = '1.1.1' - junitVersion = '4.8.2' + junitVersion = '4.10' log4jVersion = '1.2.12' mockitoVersion = '1.9.0' - // When changing Spring Versions - don't forget to update bundlor ranges - springVersion = '3.2.0.RELEASE' + springVersion = '3.1.3.RELEASE' springIntegrationVersion = '2.2.0.RELEASE' - springAmqpVersion = '1.1.0.RELEASE' - springDataMongoVersion = '1.1.0.M1' - springDataRedisVersion = '1.0.0.RELEASE' - springGemfireVersion = '1.1.1.RELEASE' - springSecurityVersion = '3.1.0.RELEASE' - springSocialTwitterVersion = '1.0.1.RELEASE' - springWsVersion = '2.0.3.RELEASE' + + idPrefix = 'smpp' } -dependencies { - compile "com.google.code:jsmpp:$jsmppVersion" - compile "org.slf4j:slf4j-log4j12:$slf4jVersion" - compile "commons-lang:commons-lang:$commonsLangVersion" - compile "commons-beanutils:commons-beanutils:$commonsBeanUtilsVersion" - compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" - compile "org.springframework.integration:spring-integration-ws:$springIntegrationVersion" - compile "org.springframework.integration:spring-integration-event:$springIntegrationVersion" - compile "org.springframework:spring-beans:$springVersion" - compile "org.springframework:spring-context:$springVersion" - compile "org.springframework:spring-context-support:$springVersion" - compile "org.springframework:spring-aop:$springVersion" - compile "org.springframework:spring-tx:$springVersion" - compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) - compile("org.springframework.amqp:spring-rabbit:$springAmqpVersion") { - exclude group: 'org.springframework', module: 'spring-aop' - exclude group: 'org.springframework', module: 'spring-beans' - exclude group: 'org.springframework', module: 'spring-context' - exclude group: 'org.springframework', module: 'spring-core' - exclude group: 'org.springframework', module: 'spring-oxm' - exclude group: 'org.springframework', module: 'spring-tx' - } - - testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" -} - - eclipse { project { natures += 'org.springframework.ide.eclipse.core.springnature' @@ -101,16 +59,19 @@ configurations { jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo } -// dependencies that are common across all java projects dependencies { - testCompile "cglib:cglib-nodep:$cglibVersion" + compile "com.googlecode.jsmpp:jsmpp:$jsmppVersion" + compile "org.slf4j:slf4j-log4j12:$slf4jVersion" + compile "commons-lang:commons-lang:$commonsLangVersion" + compile "commons-beanutils:commons-beanutils:$commonsBeanUtilsVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + compile "org.springframework.integration:spring-integration-core:$springIntegrationVersion" + testCompile "org.springframework.integration:spring-integration-test:$springIntegrationVersion" testCompile "junit:junit-dep:$junitVersion" testCompile "log4j:log4j:$log4jVersion" - testCompile "org.easymock:easymock:$easymockVersion" - testCompile "org.easymock:easymockclassextension:$easymockVersion" - testCompile "org.hamcrest:hamcrest-all:1.1" testCompile "org.mockito:mockito-all:$mockitoVersion" testCompile "org.springframework:spring-test:$springVersion" + testCompile "org.easymock:easymockclassextension:$easymockVersion" jacoco group: "org.jacoco", name: "org.jacoco.agent", version: "0.5.6.201201232323", classifier: "runtime" } @@ -181,19 +142,16 @@ sonar { task api(type: Javadoc) { group = 'Documentation' - description = 'Generates aggregated Javadoc API documentation.' + description = 'Generates the Javadoc API documentation.' title = "${rootProject.description} ${version} API" options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED options.author = true options.header = rootProject.description options.overview = 'src/api/overview.html' - source subprojects.collect { project -> - project.sourceSets.main.allJava - } + + source = sourceSets.main.allJava + classpath = project.sourceSets.main.compileClasspath destinationDir = new File(buildDir, "api") - classpath = files(subprojects.collect { project -> - project.sourceSets.main.compileClasspath - }) } task schemaZip(type: Zip) { @@ -202,46 +160,41 @@ task schemaZip(type: Zip) { description = "Builds -${classifier} archive containing all " + "XSDs for deployment at static.springframework.org/schema." - subprojects.each { subproject -> - def Properties schemas = new Properties(); - def shortName = subproject.name.replaceFirst("${rootProject.name}-", '') - if (subproject.name.endsWith("-core")) { - shortName = '' + def Properties schemas = new Properties(); + def shortName = idPrefix.replaceFirst("${idPrefix}-", '') + + project.sourceSets.main.resources.find { + it.path.endsWith('META-INF/spring.schemas') + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + File xsdFile = project.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) } - - subproject.sourceSets.main.resources.find { - it.path.endsWith('META-INF/spring.schemas') - }?.withInputStream { schemas.load(it) } - - for (def key : schemas.keySet()) { - File xsdFile = subproject.sourceSets.main.resources.find { - it.path.endsWith(schemas.get(key)) - } - assert xsdFile != null - into ("integration/${shortName}") { - from xsdFile.path - } + assert xsdFile != null + into ("integration/${shortName}") { + from xsdFile.path } } } task docsZip(type: Zip) { - group = 'Distribution' - classifier = 'docs' - description = "Builds -${classifier} archive containing api and reference " + + group = 'Distribution' + classifier = 'docs' + description = "Builds -${classifier} archive containing api and reference " + "for deployment at static.springframework.org/spring-integration/docs." - from('src/dist') { - include 'changelog.txt' - } + from('src/dist') { + include 'changelog.txt' + } - from (api) { - into 'api' - } + from (api) { + into 'api' + } - from (reference) { - into 'reference' - } + from (reference) { + into 'reference' + } } task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { @@ -267,12 +220,10 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) { into "${baseDir}/schema" } - subprojects.each { subproject -> - into ("${baseDir}/libs") { - from subproject.jar - from subproject.sourcesJar - from subproject.javadocJar - } + into ("${baseDir}/libs") { + from project.jar + from project.sourcesJar + from project.javadocJar } } @@ -288,14 +239,13 @@ task depsZip(type: Zip, dependsOn: distZip) { zipTask -> gradle.taskGraph.whenReady { taskGraph -> if (taskGraph.hasTask(":${zipTask.name}")) { - def projectNames = rootProject.subprojects*.name + def projectName = rootProject.name def artifacts = new HashSet() - subprojects.each { subproject -> - subproject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact -> - def dependency = artifact.moduleVersion.id - if (!projectNames.contains(dependency.name)) { - artifacts << artifact.file - } + + rootProject.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact -> + def dependency = artifact.moduleVersion.id + if (!projectName.equals(dependency.name)) { + artifacts << artifact.file } } @@ -319,5 +269,5 @@ task dist(dependsOn: assemble) { task wrapper(type: Wrapper) { description = 'Generates gradlew[.bat] scripts' - gradleVersion = '1.0-rc-3' + gradleVersion = '1.3' } diff --git a/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar b/spring-integration-smpp/gradle/wrapper/gradle-wrapper.jar index 81dcde627d5cfbc12c96dc5113aac79b66a15118..7b359d719bf96879170b6887c0bbb2e02b00ac34 100644 GIT binary patch literal 46670 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 literal 39770 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java index 05077b5..5d1ee2a 100644 --- a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java @@ -62,7 +62,7 @@ public class SmesMessageSpecification { mb.setHeader(SmppConstants.SMS, dsm); mb.setHeader(SmppConstants.REPLACE_IF_PRESENT, dsm.getReplaceIfPresent()); mb.setHeader(SmppConstants.SHORT_MESSAGE, dsm.getShortMessage()); - mb.setHeader(SmppConstants.OPTIONAL_PARAMETES, dsm.getOptionalParametes()); + mb.setHeader(SmppConstants.OPTIONAL_PARAMETERS, dsm.getOptionalParameters()); mb.setHeader(SmppConstants.UDHI_AND_REPLY_PATH, dsm.isUdhiAndReplyPath()); mb.setHeader(SmppConstants.VALIDITY_PERIOD, dsm.getValidityPeriod()); mb.setHeader(SmppConstants.COMMAND_LENGTH, dsm.getCommandLength()); @@ -157,8 +157,7 @@ public class SmesMessageSpecification { return (DataCoding)dc ; } if( dc instanceof Byte){ - return DataCoding.newInstance((Byte)dc); - + return DataCodings.newInstance((Byte)dc); } return null ; @@ -198,8 +197,6 @@ public class SmesMessageSpecification { * @param smppSession the SMPPSession instance against which we should work. * @see SmesMessageSpecification#SmesMessageSpecification() */ - - @SuppressWarnings("unused") SmesMessageSpecification(SMPPSession smppSession) { this.smppSession = smppSession; } @@ -244,7 +241,6 @@ public class SmesMessageSpecification { /** * use the builder API, but we need this to cleanly proxy */ - @SuppressWarnings("unused") SmesMessageSpecification() { this(null); } @@ -485,7 +481,6 @@ public class SmesMessageSpecification { * @param maxLengthSmsMessages the length of sms messages * @see #setShortTextMessage(String) */ - @SuppressWarnings("unused") public void setMaxLengthSmsMessages(int maxLengthSmsMessages) { this.maxLengthSmsMessages = maxLengthSmsMessages; } @@ -517,7 +512,7 @@ public class SmesMessageSpecification { validityPeriod = null; registeredDelivery = new RegisteredDelivery(SMSCDeliveryReceipt.DEFAULT); replaceIfPresentFlag = 0; - dataCoding = new GeneralDataCoding(false, true, MessageClass.CLASS1, Alphabet.ALPHA_DEFAULT); + dataCoding = new GeneralDataCoding(Alphabet.ALPHA_DEFAULT, MessageClass.CLASS1, false); smDefaultMsgId = 0; shortMessage = null; // the bytes to the 140 character text message smppSession = null; diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java index 4c427c1..c5bed67 100644 --- a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmppConstants.java @@ -1,7 +1,7 @@ package org.springframework.integration.smpp.core; /** - * + * * * @author Josh Long */ @@ -48,5 +48,5 @@ public abstract class SmppConstants { public static final String SM_DEFAULT_MSG_ID = "SM_DEFAULT_MSG_ID"; public static final String REPLY_PATH = "REPLY_PATH"; public static final String DEST_ADDRESS = DST_ADDR; - public static final String OPTIONAL_PARAMETES = "OPTIONAL_PARAMETES"; + public static final String OPTIONAL_PARAMETERS = "OPTIONAL_PARAMETERS"; } diff --git a/spring-integration-smpp/src/main/resources/log4j.properties b/spring-integration-smpp/src/main/resources/log4j.properties deleted file mode 100644 index 2d4371d..0000000 --- a/spring-integration-smpp/src/main/resources/log4j.properties +++ /dev/null @@ -1,9 +0,0 @@ - -log4j.rootCategory=DEBUG, S -log4j.logger.org.w3c.tidy=FATAL -log4j.logger.org.springframework.integration.smpp=DEBUG -log4j.logger.org.jsmpp=DEBUG - -log4j.appender.S = org.apache.log4j.ConsoleAppender -log4j.appender.S.layout = org.apache.log4j.PatternLayout -log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n diff --git a/spring-integration-smpp/src/main/resources/logback.xml b/spring-integration-smpp/src/main/resources/logback.xml deleted file mode 100644 index 93c9638..0000000 --- a/spring-integration-smpp/src/main/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{HH:mm:ss,SSS} %-5level [%logger] %msg%n - - - - - - - \ No newline at end of file diff --git a/spring-integration-smpp/src/main/resources/todo.txt b/spring-integration-smpp/src/main/resources/todo.txt deleted file mode 100644 index 0d04ca0..0000000 --- a/spring-integration-smpp/src/main/resources/todo.txt +++ /dev/null @@ -1 +0,0 @@ -- figure out a clean way to furnish our own Executor implementation diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java index 1e318a9..52b2ea0 100644 --- a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppConnection.java @@ -69,7 +69,7 @@ public class TestSmppConnection { outboundChannel.send(messageOut); - Thread.sleep(60000); + Thread.sleep(5000); } diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java index 99f55fd..15dd8cc 100644 --- a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppInboundGateway.java @@ -77,8 +77,6 @@ public class TestSmppInboundGateway { // launch the whole thing - - Thread.sleep(1000 * 10); Assert.assertEquals(this.count.intValue(),1); diff --git a/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml index 95a5cd5..6211653 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppConnection-context.xml @@ -1,46 +1,45 @@ - http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd"> + + + + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml index b091d5a..5e4cbac 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundChannelAdapter-context.xml @@ -1,60 +1,59 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:int="http://www.springframework.org/schema/integration" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml index 8495da0..393c146 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppInboundGateway-context.xml @@ -1,64 +1,56 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml index 59fc3e6..9377679 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundChannelAdapter-context.xml @@ -1,31 +1,33 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + - - + + + + + + + - - - - - - - + + + - - - + - - - + diff --git a/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml index b4d368a..cf52360 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppOutboundGateway-context.xml @@ -1,31 +1,33 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + - - + + + + + + + - - - - - - - + + + - - - + - - - + diff --git a/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml index a4d4bda..7672b5c 100644 --- a/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml +++ b/spring-integration-smpp/src/test/resources/TestSmppSessionFactoryBean-context.xml @@ -1,25 +1,23 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + - - - - - - - - - - - + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/log4j.properties b/spring-integration-smpp/src/test/resources/log4j.properties index 8fda257..67e6463 100644 --- a/spring-integration-smpp/src/test/resources/log4j.properties +++ b/spring-integration-smpp/src/test/resources/log4j.properties @@ -5,4 +5,4 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} %-5p [%t][%c] %m%n log4j.category.org.springframework.integration=WARN -log4j.category.org.springframework.integration.jpa=INFO +log4j.category.org.springframework.integration.smpp=INFO diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml index 24f3ce8..92e4c59 100644 --- a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundChannelAdapterParserTests.xml @@ -1,27 +1,24 @@ + http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd"> - - - - - + + + + - + auto-startup="true" channel="out"> + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml index 39cda84..4d52410 100644 --- a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppInboundGatewayParserTests.xml @@ -1,55 +1,52 @@ + http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - - + + - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml index 9fe4782..7c736f8 100644 --- a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundChannelAdapterParserTests.xml @@ -1,40 +1,38 @@ + http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - - - - + + + + - - - + + + - + - - + + - + diff --git a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml index 92b7757..1c68b0b 100644 --- a/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml +++ b/spring-integration-smpp/src/test/resources/org/springframework/integration/smpp/config/xml/SmppOutboundGatewayParserTests.xml @@ -1,45 +1,42 @@ + http://www.springframework.org/schema/integration/smpp http://www.springframework.org/schema/integration/smpp/spring-integration-smpp.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - - + + - - + + - - - + + + - - - - - - - - + source-address="123456789" source-ton="NETWORK_SPECIFIC" order="17" + request-channel="in" reply-channel="out" reply-timeout="100" + time-formatter="timeFormatter"> + + + + + + + diff --git a/spring-integration-smpp/src/test/resources/readme.txt b/spring-integration-smpp/src/test/resources/readme.txt deleted file mode 100644 index 257aa28..0000000 --- a/spring-integration-smpp/src/test/resources/readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -README - -you're going to need a good server to test with. I've found http://www.seleniumsoftware.com/regform.php?itemdesc=SMPPSim.tar.gz to work well. Simply download it, -cd into the folder and execute ./startsmppsim.(sh|bat). Make sure the script is executable. The coniguration for this simulator is in conf/smppsim.props - -Alternatively, the JSMPP project itself has an SMPP simulator as well. - -If you want to go the full nine yards, it's pretty easy to find full-blow SMPP servers like Kanell out there. - -http://www.techdive.in/java/send-sms-using-jsmpp diff --git a/spring-integration-smpp/src/test/resources/smpp.properties b/spring-integration-smpp/src/test/resources/smpp.properties index 60d313a..1628ffe 100644 --- a/spring-integration-smpp/src/test/resources/smpp.properties +++ b/spring-integration-smpp/src/test/resources/smpp.properties @@ -4,4 +4,4 @@ smpp.password=wpsd smpp.port=2775 -test.dst.number=123456 +test.dst.number=123456 \ No newline at end of file