├── .gitattributes ├── .gitignore ├── .springBeans ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── podcastpedia │ │ └── batch │ │ ├── Application.java │ │ ├── common │ │ ├── configuration │ │ │ ├── AppConfig.java │ │ │ ├── InfrastructureConfiguration.java │ │ │ └── MailConfiguration.java │ │ ├── entities │ │ │ ├── Category.java │ │ │ ├── Episode.java │ │ │ ├── EpisodeId.java │ │ │ ├── LanguageCode.java │ │ │ ├── MediaType.java │ │ │ ├── MediaTypeConverter.java │ │ │ ├── Podcast.java │ │ │ ├── Tag.java │ │ │ ├── UpdateFrequency.java │ │ │ ├── UpdateFrequencyConverter.java │ │ │ └── User.java │ │ └── listeners │ │ │ ├── LogProcessListener.java │ │ │ └── ProtocolListener.java │ │ └── jobs │ │ ├── addpodcast │ │ ├── AddPodcastJobConfiguration.java │ │ ├── ServicesConfiguration.java │ │ ├── SuggestedPodcastFieldSetMapper.java │ │ ├── SuggestedPodcastItemProcessor.java │ │ ├── Writer.java │ │ ├── dao │ │ │ ├── InsertDao.java │ │ │ ├── ReadDao.java │ │ │ └── ReadDaoImpl.java │ │ ├── model │ │ │ └── SuggestedPodcast.java │ │ └── service │ │ │ ├── EmailNotificationService.java │ │ │ ├── EmailNotificationServiceImpl.java │ │ │ ├── PodcastAndEpisodeAttributesService.java │ │ │ ├── PodcastAndEpisodeAttributesServiceImpl.java │ │ │ ├── SocialMediaService.java │ │ │ ├── SocialMediaServiceImpl.java │ │ │ ├── SyndFeedService.java │ │ │ └── SyndFeedServiceImpl.java │ │ └── notifysubscribers │ │ ├── EmailNotificationPlaceholderRowMapper.java │ │ ├── NotifySubscribersItemProcessor.java │ │ ├── NotifySubscribersJobConfiguration.java │ │ ├── NotifySubscribersServicesConfiguration.java │ │ ├── NotifySubscribersWriter.java │ │ ├── UserRowMapper.java │ │ ├── model │ │ └── EmailNotificationPlaceholder.java │ │ └── service │ │ ├── EmailNotificationService.java │ │ └── EmailNotificationServiceImpl.java └── resources │ ├── application-dev_home.properties │ ├── application-dev_work-notify-job.properties │ ├── application-dev_work.properties │ ├── application.properties │ ├── log4j.dtd │ ├── log4j.xml │ ├── persistence.xml │ ├── suggested-podcasts.in │ └── templates │ ├── new_episodes.vm │ ├── new_episodes_table.vm │ └── podcast_addition_notification.vm └── test └── java └── org └── podcastpedia └── batch └── jobs └── notifysubscribers ├── TestApp.java └── TestConfig.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | /.project 3 | /.classpath 4 | /target 5 | 6 | /src/main/resources/twitter4j.properties 7 | /src/main/resources/mail.properties 8 | /src/main/resources/application-prod.properties 9 | /.idea 10 | podcastpedia-batch.iml -------------------------------------------------------------------------------- /.springBeans: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | java:org.podcastpedia.batch.Applicationonfigs> 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "http://repo.spring.io/libs-snapshot" } 4 | mavenLocal() 5 | } 6 | dependencies { 7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.2.RELEASE") 8 | } 9 | } 10 | 11 | apply plugin: 'java' 12 | apply plugin: 'eclipse' 13 | apply plugin: 'idea' 14 | apply plugin: 'spring-boot' 15 | 16 | jar { 17 | baseName = 'gs-batch-processing' 18 | version = '0.1.0' 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | maven { url "http://repo.spring.io/libs-snapshot" } 24 | } 25 | 26 | dependencies { 27 | compile("org.springframework.boot:spring-boot-starter-batch") 28 | compile("org.hsqldb:hsqldb") 29 | testCompile("junit:junit") 30 | } 31 | 32 | task wrapper(type: Wrapper) { 33 | gradleVersion = '1.11' 34 | } 35 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodepediaOrg/podcastpedia-batch/6e70cbe3d720409e6ea496c53de3647b36e1bcf2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 12 07:26:43 CST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.podcastpedia.batch 7 | podcastpedia-batch 8 | 0.1.0 9 | 10 | 11 | 1.7 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 1.1.6.RELEASE 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-batch 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-jpa 27 | 28 | 29 | 30 | org.apache.httpcomponents 31 | httpclient 32 | 4.3.5 33 | 34 | 35 | org.apache.httpcomponents 36 | httpcore 37 | 4.3.2 38 | 39 | 40 | 41 | org.apache.velocity 42 | velocity 43 | 1.7 44 | 45 | 46 | org.apache.velocity 47 | velocity-tools 48 | 2.0 49 | 50 | 51 | org.apache.struts 52 | struts-core 53 | 54 | 55 | 56 | 57 | 58 | 59 | rome 60 | rome 61 | 1.0 62 | 63 | 64 | 65 | rome 66 | rome-fetcher 67 | 1.0 68 | 69 | 70 | org.jdom 71 | jdom 72 | 1.1 73 | 74 | 75 | 76 | xerces 77 | xercesImpl 78 | 2.9.1 79 | 80 | 81 | 82 | 83 | mysql 84 | mysql-connector-java 85 | 5.1.31 86 | 87 | 88 | org.scala-lang 89 | scala-library 90 | 2.10.3 91 | 92 | 93 | org.scala-lang 94 | scala-library 95 | 2.10.3 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-starter-freemarker 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-starter-remote-shell 104 | 105 | 106 | javax.mail 107 | mail 108 | 109 | 110 | 111 | 112 | javax.mail 113 | mail 114 | 1.4.7 115 | 116 | 117 | javax.inject 118 | javax.inject 119 | 1 120 | 121 | 122 | org.twitter4j 123 | twitter4j-core 124 | [4.0,) 125 | 126 | 127 | org.springframework.boot 128 | spring-boot-starter-test 129 | 130 | 131 | 132 | log4j 133 | log4j 134 | 1.2.17 135 | 136 | 137 | 138 | 139 | 140 | 141 | maven-compiler-plugin 142 | 143 | 144 | org.springframework.boot 145 | spring-boot-maven-plugin 146 | 147 | 148 | 149 | 150 | 166 | 167 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/Application.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.Date; 9 | 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.springframework.batch.core.*; 13 | import org.springframework.batch.core.launch.JobLauncher; 14 | import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; 15 | import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; 16 | import org.springframework.batch.core.repository.JobRestartException; 17 | import org.springframework.beans.BeansException; 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 20 | import org.springframework.context.ConfigurableApplicationContext; 21 | import org.springframework.context.annotation.ComponentScan; 22 | 23 | 24 | @ComponentScan 25 | @EnableAutoConfiguration 26 | public class Application { 27 | 28 | private static final String NEW_EPISODES_NOTIFICATION_JOB = "newEpisodesNotificationJob"; 29 | private static final String ADD_NEW_PODCAST_JOB = "addNewPodcastJob"; 30 | 31 | public static void main(String[] args) throws BeansException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException, InterruptedException, IOException { 32 | 33 | Log log = LogFactory.getLog(Application.class); 34 | 35 | SpringApplication app = new SpringApplication(Application.class); 36 | app.setWebEnvironment(false); 37 | ConfigurableApplicationContext ctx = app.run(args); 38 | JobLauncher jobLauncher = ctx.getBean(JobLauncher.class); 39 | 40 | if(ADD_NEW_PODCAST_JOB.equals(args[0])){ 41 | //addNewPodcastJob 42 | Job addNewPodcastJob = ctx.getBean(ADD_NEW_PODCAST_JOB, Job.class); 43 | JobParameters jobParameters = new JobParametersBuilder() 44 | .addDate("date", new Date()) 45 | .addString("directoryPath", args[1]) 46 | .toJobParameters(); 47 | 48 | JobExecution jobExecution = jobLauncher.run(addNewPodcastJob, jobParameters); 49 | 50 | BatchStatus batchStatus = jobExecution.getStatus(); 51 | while(batchStatus.isRunning()){ 52 | log.info("*********** Still running.... **************"); 53 | Thread.sleep(1000); 54 | } 55 | ExitStatus exitStatus = jobExecution.getExitStatus(); 56 | String exitCode = exitStatus.getExitCode(); 57 | log.info(String.format("*********** Exit status: %s", exitCode)); 58 | 59 | if(exitStatus.equals(ExitStatus.COMPLETED)){ 60 | renameFile(args[1]); 61 | } 62 | 63 | JobInstance jobInstance = jobExecution.getJobInstance(); 64 | log.info(String.format("********* Name of the job %s", jobInstance.getJobName())); 65 | 66 | log.info(String.format("*********** job instance Id: %d", jobInstance.getId())); 67 | 68 | System.exit(0); 69 | 70 | } else if(NEW_EPISODES_NOTIFICATION_JOB.equals(args[0])){ 71 | JobParameters jobParameters = new JobParametersBuilder() 72 | .addDate("date", new Date()) 73 | .addString("updateFrequency", args[1]) 74 | .toJobParameters(); 75 | 76 | jobLauncher.run(ctx.getBean(NEW_EPISODES_NOTIFICATION_JOB, Job.class), jobParameters); 77 | } else { 78 | throw new IllegalArgumentException("Please provide a valid Job name as first application parameter"); 79 | } 80 | 81 | System.exit(0); 82 | } 83 | 84 | private static void renameFile(String directoryPath) throws IOException { 85 | File fl = new File(directoryPath); 86 | 87 | File[] files = fl.listFiles(new FileFilter() { 88 | @Override 89 | public boolean accept(File file) { 90 | return file.isFile(); 91 | } 92 | }); 93 | 94 | if(files.length != 1) throw new RuntimeException("There must be only one file present in the folder to be processed"); 95 | File file = files[0]; 96 | String fileName = file.getName(); 97 | String fileNameWithoutExtension = fileName.substring(0, fileName.lastIndexOf(".")); 98 | String fileNameExtension = fileName.substring(fileName.lastIndexOf("."), fileName.length()); 99 | String newFileName = fileNameWithoutExtension + "-completed" + fileNameExtension; 100 | 101 | Path source = file.toPath(); 102 | Files.move(source, source.resolveSibling(newFileName)); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/configuration/AppConfig.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.configuration; 2 | 3 | import org.podcastpedia.batch.jobs.addpodcast.AddPodcastJobConfiguration; 4 | import org.podcastpedia.batch.jobs.notifysubscribers.NotifySubscribersJobConfiguration; 5 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 6 | import org.springframework.batch.core.configuration.support.ApplicationContextFactory; 7 | import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @EnableBatchProcessing(modular=true) 13 | public class AppConfig { 14 | 15 | @Bean 16 | public ApplicationContextFactory addNewPodcastJobs(){ 17 | return new GenericApplicationContextFactory(AddPodcastJobConfiguration.class); 18 | } 19 | 20 | @Bean 21 | public ApplicationContextFactory newEpisodesNotificationJobs(){ 22 | return new GenericApplicationContextFactory(NotifySubscribersJobConfiguration.class); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/configuration/InfrastructureConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | import javax.persistence.EntityManagerFactory; 6 | import javax.sql.DataSource; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; 14 | import org.springframework.orm.jpa.JpaTransactionManager; 15 | import org.springframework.orm.jpa.JpaVendorAdapter; 16 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 17 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 18 | import org.springframework.transaction.PlatformTransactionManager; 19 | 20 | @Configuration 21 | public class InfrastructureConfiguration { 22 | 23 | @Autowired 24 | Environment env; 25 | 26 | @Bean 27 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 28 | LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); 29 | em.setDataSource(dataSource()); 30 | em.setPackagesToScan(new String[] { "org.podcastpedia.batch.*" }); 31 | 32 | JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); 33 | em.setJpaVendorAdapter(vendorAdapter); 34 | em.setJpaProperties(additionalJpaProperties()); 35 | 36 | return em; 37 | } 38 | 39 | Properties additionalJpaProperties() { 40 | Properties properties = new Properties(); 41 | // properties.setProperty("hibernate.hbm2ddl.auto", "validate"); 42 | properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); 43 | properties.setProperty("hibernate.show_sql", "true"); 44 | 45 | return properties; 46 | } 47 | 48 | @Bean 49 | public DataSource dataSource(){ 50 | return DataSourceBuilder.create() 51 | .url(env.getProperty("db.url")) 52 | .driverClassName(env.getProperty("db.driver")) 53 | .username(env.getProperty("db.username")) 54 | .password(env.getProperty("db.password")) 55 | .build(); 56 | } 57 | 58 | @Bean 59 | public PlatformTransactionManager transactionManager(EntityManagerFactory emf){ 60 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 61 | transactionManager.setEntityManagerFactory(emf); 62 | 63 | return transactionManager; 64 | } 65 | 66 | @Bean 67 | public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){ 68 | return new PersistenceExceptionTranslationPostProcessor(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/configuration/MailConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.configuration; 2 | 3 | import java.util.Properties; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.PropertySource; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.mail.javamail.JavaMailSender; 11 | import org.springframework.mail.javamail.JavaMailSenderImpl; 12 | 13 | @Configuration 14 | @PropertySource("classpath:mail.properties") 15 | public class MailConfiguration { 16 | 17 | @Autowired 18 | private Environment environment; 19 | 20 | /** 21 | * The Java Mail sender. 22 | * It's not generally expected for mail sending to work in embedded mode. 23 | * Since this mail sender is always invoked asynchronously, this won't cause problems for the developer. 24 | */ 25 | @Bean 26 | public JavaMailSender mailSender() { 27 | 28 | JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); 29 | mailSender.setDefaultEncoding("UTF-8"); 30 | mailSender.setHost(environment.getProperty("mail.smtp.host")); 31 | mailSender.setPort(Integer.valueOf(environment.getProperty("mail.port"))); 32 | mailSender.setProtocol(environment.getProperty("mail.protocol")); 33 | mailSender.setUsername(environment.getProperty("mail.username")); 34 | mailSender.setPassword(environment.getProperty("mail.password")); 35 | 36 | Properties properties = new Properties(); 37 | properties.put("mail.smtp.auth", environment.getProperty("mail.smtp.auth", Boolean.class, true)); 38 | mailSender.setJavaMailProperties(properties); 39 | 40 | return mailSender; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/Category.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | import javax.persistence.ManyToMany; 10 | import javax.persistence.Table; 11 | 12 | @Entity 13 | @Table(name="categories") 14 | public class Category implements Serializable{ 15 | 16 | private static final long serialVersionUID = 219264453988823416L; 17 | 18 | @Id 19 | @Column(name="category_id") 20 | protected int categoryId; 21 | 22 | @Column(name="name") 23 | protected String name; 24 | 25 | @Column(name="description") 26 | protected String description; 27 | 28 | @ManyToMany(mappedBy="categories") 29 | private List podcasts; 30 | 31 | public List getPodcasts() { 32 | return podcasts; 33 | } 34 | public void setPodcasts(List podcasts) { 35 | this.podcasts = podcasts; 36 | } 37 | public int getCategoryId() { 38 | return categoryId; 39 | } 40 | public void setCategoryId(int categoryId) { 41 | this.categoryId = categoryId; 42 | } 43 | public String getName() { 44 | return name; 45 | } 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | public String getDescription() { 50 | return description; 51 | } 52 | public void setDescription(String description) { 53 | this.description = description; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/Episode.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Convert; 8 | import javax.persistence.EmbeddedId; 9 | import javax.persistence.Entity; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.ManyToOne; 12 | import javax.persistence.MapsId; 13 | import javax.persistence.Table; 14 | 15 | @Entity 16 | @Table(name="episodes") 17 | public class Episode implements Serializable{ 18 | 19 | /** 20 | * automatic generated serialVersionUID 21 | */ 22 | private static final long serialVersionUID = -1957667986801174870L; 23 | 24 | @EmbeddedId 25 | private EpisodeId id; 26 | 27 | /** description of the episode */ 28 | @Column(name="description") 29 | private String description; 30 | 31 | /** title of the episode */ 32 | @Column(name="title") 33 | private String title; 34 | 35 | /** link of the episode - this should be the link to the episode on the provider's website 36 | * Some providers set this as the url to the media file */ 37 | @Column(name="link") 38 | private String link; 39 | 40 | /** this is the url to the media (audio or video) of this episode */ 41 | @Column(name="media_url") 42 | private String mediaUrl; 43 | 44 | /** publication date of the episode */ 45 | @Column(name="publication_date") 46 | private Date publicationDate; 47 | 48 | /** media type (either audio or video) */ 49 | @Column(name="media_type") 50 | @Convert(converter=MediaTypeConverter.class) 51 | private MediaType mediaType; 52 | 53 | /** length of the episode */ 54 | @Column(name="length") 55 | private Long length; 56 | 57 | /** episode's transformed title with hyphens to be added in the URL for SEO optimization */ 58 | @Column(name="title_in_url") 59 | private String titleInUrl; 60 | 61 | /** 62 | * holds the the httpStatus for the episode's url - see org.apache.http.HttpStatus for the codes semnification 63 | * or custom exception code */ 64 | @Column(name="availability") 65 | private Integer availability; 66 | 67 | /** any value not null or zero means the episode is new */ 68 | @Column(name="is_new") 69 | private Integer isNew; 70 | 71 | /** type of the enclosure */ 72 | @Column(name="enclosure_type") 73 | private String enclosureType; 74 | 75 | /** author of the episode */ 76 | @Column(name="author") 77 | private String author; 78 | 79 | /** podcast the episode belongs to */ 80 | @MapsId("podcastId") 81 | @ManyToOne //a podcast has to have at least one episode to be considered valid for Podcastpedia... 82 | @JoinColumn(name="podcast_id") 83 | private Podcast podcast; 84 | 85 | public Episode(){} 86 | 87 | public String getDescription() { 88 | return description; 89 | } 90 | 91 | public EpisodeId getId() { 92 | return id; 93 | } 94 | 95 | public void setId(EpisodeId id) { 96 | this.id = id; 97 | } 98 | 99 | public void setDescription(String description) { 100 | this.description = description; 101 | } 102 | 103 | public String getTitle() { 104 | return title; 105 | } 106 | 107 | public void setTitle(String title) { 108 | this.title = title; 109 | } 110 | 111 | public String getLink() { 112 | return link; 113 | } 114 | 115 | public void setLink(String link) { 116 | this.link = link; 117 | } 118 | 119 | public String getMediaUrl() { 120 | return mediaUrl; 121 | } 122 | 123 | public void setMediaUrl(String mediaUrl) { 124 | this.mediaUrl = mediaUrl; 125 | } 126 | 127 | public Podcast getPodcast() { 128 | return podcast; 129 | } 130 | 131 | public void setPodcast(Podcast podcast) { 132 | this.podcast = podcast; 133 | } 134 | 135 | public Date getPublicationDate() { 136 | return publicationDate; 137 | } 138 | 139 | public void setPublicationDate(Date publicationDate) { 140 | this.publicationDate = publicationDate; 141 | } 142 | 143 | public MediaType getMediaType() { 144 | return mediaType; 145 | } 146 | 147 | public void setMediaType(MediaType mediaType) { 148 | this.mediaType = mediaType; 149 | } 150 | 151 | public Long getLength() { 152 | return length; 153 | } 154 | 155 | public void setLength(Long length) { 156 | this.length = length; 157 | } 158 | 159 | public String getTitleInUrl() { 160 | return titleInUrl; 161 | } 162 | 163 | public void setTitleInUrl(String titleInUrl) { 164 | this.titleInUrl = titleInUrl; 165 | } 166 | 167 | public Integer getAvailability() { 168 | return availability; 169 | } 170 | 171 | public void setAvailability(Integer availability) { 172 | this.availability = availability; 173 | } 174 | 175 | public Integer getIsNew() { 176 | return isNew; 177 | } 178 | 179 | public void setIsNew(Integer isNew) { 180 | this.isNew = isNew; 181 | } 182 | 183 | public String getEnclosureType() { 184 | return enclosureType; 185 | } 186 | 187 | public void setEnclosureType(String enclosureType) { 188 | this.enclosureType = enclosureType; 189 | } 190 | 191 | public String getAuthor() { 192 | return author; 193 | } 194 | 195 | public void setAuthor(String author) { 196 | this.author = author; 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/EpisodeId.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | @Embeddable 9 | public class EpisodeId implements Serializable{ 10 | 11 | private static final long serialVersionUID = 1201784239459713261L; 12 | 13 | @Column(name="podcast_id") 14 | Integer podcastId; 15 | 16 | @Column(name="episode_id") 17 | Integer episodeId; 18 | 19 | public Integer getPodcastId() { 20 | return podcastId; 21 | } 22 | public void setPodcastId(Integer podcastId) { 23 | this.podcastId = podcastId; 24 | } 25 | public Integer getEpisodeId() { 26 | return episodeId; 27 | } 28 | public void setEpisodeId(Integer episodeId) { 29 | this.episodeId = episodeId; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | final int prime = 31; 35 | int result = 1; 36 | result = prime * result 37 | + ((episodeId == null) ? 0 : episodeId.hashCode()); 38 | result = prime * result 39 | + ((podcastId == null) ? 0 : podcastId.hashCode()); 40 | return result; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) 46 | return true; 47 | if (obj == null) 48 | return false; 49 | if (getClass() != obj.getClass()) 50 | return false; 51 | EpisodeId other = (EpisodeId) obj; 52 | if (episodeId == null) { 53 | if (other.episodeId != null) 54 | return false; 55 | } else if (!episodeId.equals(other.episodeId)) 56 | return false; 57 | if (podcastId == null) { 58 | if (other.podcastId != null) 59 | return false; 60 | } else if (!podcastId.equals(other.podcastId)) 61 | return false; 62 | return true; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/LanguageCode.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Enum that lists possible types for podcasts and episodes. 9 | * @author adrian 10 | * 11 | */ 12 | public enum LanguageCode { 13 | 14 | /** English */ 15 | en("en"), 16 | 17 | /** French */ 18 | fr("fr"), 19 | 20 | /** German */ 21 | de("de"), 22 | 23 | /** Italian */ 24 | it("it"), 25 | 26 | /** Romanian */ 27 | ro("ro"), 28 | 29 | /** Spanish */ 30 | es("es"), 31 | 32 | /** Portuguese */ 33 | pt("pt"); 34 | 35 | 36 | private String code; 37 | 38 | private LanguageCode(String code) { 39 | this.code = code; 40 | } 41 | 42 | public String getCode() { 43 | return code; 44 | } 45 | 46 | private static final Map lookup 47 | = new HashMap(); 48 | 49 | static { 50 | for(LanguageCode s : EnumSet.allOf(LanguageCode.class)) 51 | lookup.put(s.getCode(), s); 52 | } 53 | 54 | public static LanguageCode get(String code) { 55 | return lookup.get(code); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/MediaType.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Enum that lists possible types for podcasts and episodes. 9 | * @author adrian 10 | * 11 | */ 12 | public enum MediaType { 13 | 14 | /** Audio */ 15 | Audio(1), 16 | 17 | /** Video */ 18 | Video(2), 19 | 20 | /** VideoHD */ 21 | VideoHD(3); 22 | 23 | private int code; 24 | 25 | private MediaType(int code) { 26 | this.code = code; 27 | } 28 | 29 | public int getCode() { 30 | return code; 31 | } 32 | 33 | private static final Map lookup 34 | = new HashMap(); 35 | 36 | static { 37 | for(MediaType s : EnumSet.allOf(MediaType.class)) 38 | lookup.put(s.getCode(), s); 39 | } 40 | 41 | public static MediaType get(int code) { 42 | return lookup.get(code); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/MediaTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import javax.persistence.AttributeConverter; 4 | 5 | public class MediaTypeConverter implements AttributeConverter { 6 | 7 | @Override 8 | public Integer convertToDatabaseColumn(MediaType mediaType) { 9 | 10 | switch(mediaType) { 11 | case Audio: 12 | return Integer.valueOf(1); 13 | case Video: 14 | return Integer.valueOf(2); 15 | case VideoHD: 16 | return Integer.valueOf(3); 17 | default: 18 | throw new IllegalArgumentException("Unknown value: " + mediaType); 19 | } 20 | 21 | } 22 | 23 | @Override 24 | public MediaType convertToEntityAttribute(Integer dbData) { 25 | switch (dbData){ 26 | case 1: 27 | return MediaType.Audio; 28 | case 2: 29 | return MediaType.Video; 30 | case 3: 31 | return MediaType.VideoHD; 32 | default: 33 | throw new IllegalArgumentException("Unknown value: " + dbData); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/Podcast.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Column; 10 | import javax.persistence.Convert; 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.GenerationType; 16 | import javax.persistence.Id; 17 | import javax.persistence.JoinColumn; 18 | import javax.persistence.JoinTable; 19 | import javax.persistence.ManyToMany; 20 | import javax.persistence.OneToMany; 21 | import javax.persistence.Table; 22 | 23 | 24 | 25 | /** 26 | * Simple JavaBean domain object representing a podcast. 27 | * 28 | * @author amasia 29 | * 30 | */ 31 | @Entity 32 | @Table(name="podcasts") 33 | public class Podcast implements Serializable { 34 | 35 | private static final long serialVersionUID = 1L; 36 | 37 | /** id of the podcast - primary key in db */ 38 | @Id 39 | @Column(name="podcast_id") 40 | @GeneratedValue(strategy=GenerationType.IDENTITY) 41 | protected Integer podcastId; 42 | 43 | /** e.g. http://www.podcastpedia.org/quarks */ 44 | @Column(name="identifier") 45 | protected String identifier; 46 | 47 | /** stores the date when new episodes were added to the podcast */ 48 | @Column(name="last_update") 49 | protected Date lastUpdate; 50 | 51 | /** feed url of the podcast - based on this with rome will get further details */ 52 | @Column(name="url") 53 | protected String url; 54 | 55 | /** date when the podcast is inserted in the database */ 56 | @Column(name="insertion_date") 57 | protected Date insertionDate; 58 | 59 | /** media type of the podcast (either audio, video or videoHD) */ 60 | @Column(name="media_type") 61 | @Convert(converter=MediaTypeConverter.class) 62 | protected MediaType mediaType; 63 | 64 | /** description of the podcast */ 65 | @Column(name="description") 66 | protected String description; 67 | 68 | /** title of the podcast */ 69 | @Column(name="title") 70 | protected String title; 71 | 72 | /** link of the image that represents the podcast 73 | * - all these three last fields are for performance purposes 74 | */ 75 | @Column(name="podcast_image_url") 76 | protected String urlOfImageToDisplay; 77 | 78 | /** copyright of the podcast */ 79 | @Column(name="copyright") 80 | protected String copyright; 81 | 82 | /** podcast publication date - contains the date of the last published episode */ 83 | @Column(name="publication_date") 84 | protected Date publicationDate; 85 | 86 | /** link of the podcast */ 87 | @Column(name="podcast_link") 88 | protected String link; 89 | 90 | /** short description - it is basically the first 320 of the description if it is longer than that */ 91 | @Column(name="short_description") 92 | protected String shortDescription; 93 | 94 | /** stores the "etag" value in the HTTP header for the podcast feed */ 95 | @Column(name="etag_header_field") 96 | protected String etagHeaderField; 97 | 98 | /** stores the "last modified" in the HTTP header for the podcast feed */ 99 | @Column(name="last_modified_header_field") 100 | protected Date lastModifiedHeaderField; 101 | 102 | /** same as above just this time is stored as string TODO - in the end one of these has to disappear*/ 103 | @Column(name="last_modified_header_field_str") 104 | protected String lastModifiedHeaderFieldStr; 105 | 106 | /** holds the title to be displayed in the url "quarks & co" becomes "quarks-co"*/ 107 | @Column(name="title_in_url") 108 | protected String titleInUrl; 109 | 110 | /** holds the the httpStatus for the podcasts's url - see org.apache.http.HttpStatus for the codes semnification and extra exception 111 | * codes and modification */ 112 | @Column(name="availability") 113 | private Integer availability; 114 | 115 | /** stores in the database the language code - for example Romanian will be stored as "ro" */ 116 | @Column(name="language_code") 117 | @Enumerated(EnumType.STRING) 118 | private LanguageCode languageCode; 119 | 120 | /** author of the podcast */ 121 | @Column(name="author") 122 | private String author; 123 | 124 | /** Facebook fan page */ 125 | @Column(name="social_fb_page") 126 | private String fbPage; 127 | 128 | /** twitter fan page */ 129 | @Column(name="social_twitter_page") 130 | private String twitterPage; 131 | 132 | /** Gplus fan page */ 133 | @Column(name="social_gplus_page") 134 | private String gplusPage; 135 | 136 | @Column(name="update_frequency") 137 | @Convert(converter=UpdateFrequencyConverter.class) 138 | private UpdateFrequency updateFrequency; 139 | 140 | /** media url of the last episode */ 141 | @Column(name="last_episode_url") 142 | protected String lastEpisodeMediaUrl; 143 | 144 | @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "podcast") 145 | private List episodes; 146 | 147 | public void addEpisode(Episode episode){ 148 | if(episode != null) { 149 | if(episodes == null) { 150 | episodes = new ArrayList(); 151 | } 152 | episodes.add(episode); 153 | episode.setPodcast(this); 154 | } 155 | } 156 | 157 | @ManyToMany(cascade = CascadeType.ALL) 158 | @JoinTable( 159 | name="podcasts_tags", 160 | joinColumns={@JoinColumn(name="podcast_id")}, 161 | inverseJoinColumns={@JoinColumn(name="tag_id")} 162 | ) 163 | private List tags; 164 | 165 | @ManyToMany(cascade = CascadeType.ALL) 166 | @JoinTable( 167 | name="podcasts_categories", 168 | joinColumns={@JoinColumn(name="podcast_id")}, 169 | inverseJoinColumns={@JoinColumn(name="category_id")} 170 | ) 171 | private List categories; 172 | 173 | 174 | public List getCategories() { 175 | return categories; 176 | } 177 | 178 | public void setCategories(List categories) { 179 | this.categories = categories; 180 | } 181 | 182 | public List getEpisodes() { 183 | return episodes; 184 | } 185 | 186 | public void setEpisodes(List episodes) { 187 | this.episodes = episodes; 188 | } 189 | 190 | public List getTags() { 191 | return tags; 192 | } 193 | 194 | public void setTags(List tags) { 195 | this.tags = tags; 196 | } 197 | 198 | public Integer getPodcastId() { 199 | return podcastId; 200 | } 201 | 202 | public void setPodcastId(Integer podcastId) { 203 | this.podcastId = podcastId; 204 | } 205 | 206 | public String getIdentifier() { 207 | return identifier; 208 | } 209 | 210 | public void setIdentifier(String identifier) { 211 | this.identifier = identifier; 212 | } 213 | 214 | public Date getLastUpdate() { 215 | return lastUpdate; 216 | } 217 | 218 | public void setLastUpdate(Date lastUpdate) { 219 | this.lastUpdate = lastUpdate; 220 | } 221 | 222 | public String getUrl() { 223 | return url; 224 | } 225 | 226 | public void setUrl(String url) { 227 | this.url = url; 228 | } 229 | 230 | public Date getInsertionDate() { 231 | return insertionDate; 232 | } 233 | 234 | public void setInsertionDate(Date insertionDate) { 235 | this.insertionDate = insertionDate; 236 | } 237 | 238 | public MediaType getMediaType() { 239 | return mediaType; 240 | } 241 | 242 | public void setMediaType(MediaType mediaType) { 243 | this.mediaType = mediaType; 244 | } 245 | 246 | public String getDescription() { 247 | return description; 248 | } 249 | 250 | public void setDescription(String description) { 251 | this.description = description; 252 | } 253 | 254 | public String getTitle() { 255 | return title; 256 | } 257 | 258 | public void setTitle(String title) { 259 | this.title = title; 260 | } 261 | 262 | public String getUrlOfImageToDisplay() { 263 | return urlOfImageToDisplay; 264 | } 265 | 266 | public void setUrlOfImageToDisplay(String urlOfImageToDisplay) { 267 | this.urlOfImageToDisplay = urlOfImageToDisplay; 268 | } 269 | 270 | public String getCopyright() { 271 | return copyright; 272 | } 273 | 274 | public void setCopyright(String copyright) { 275 | this.copyright = copyright; 276 | } 277 | 278 | public Date getPublicationDate() { 279 | return publicationDate; 280 | } 281 | 282 | public void setPublicationDate(Date publicationDate) { 283 | this.publicationDate = publicationDate; 284 | } 285 | 286 | public String getLink() { 287 | return link; 288 | } 289 | 290 | public void setLink(String link) { 291 | this.link = link; 292 | } 293 | 294 | public String getShortDescription() { 295 | return shortDescription; 296 | } 297 | 298 | public void setShortDescription(String shortDescription) { 299 | this.shortDescription = shortDescription; 300 | } 301 | 302 | public String getEtagHeaderField() { 303 | return etagHeaderField; 304 | } 305 | 306 | public void setEtagHeaderField(String etagHeaderField) { 307 | this.etagHeaderField = etagHeaderField; 308 | } 309 | 310 | public Date getLastModifiedHeaderField() { 311 | return lastModifiedHeaderField; 312 | } 313 | 314 | public void setLastModifiedHeaderField(Date lastModifiedHeaderField) { 315 | this.lastModifiedHeaderField = lastModifiedHeaderField; 316 | } 317 | 318 | public String getLastModifiedHeaderFieldStr() { 319 | return lastModifiedHeaderFieldStr; 320 | } 321 | 322 | public void setLastModifiedHeaderFieldStr(String lastModifiedHeaderFieldStr) { 323 | this.lastModifiedHeaderFieldStr = lastModifiedHeaderFieldStr; 324 | } 325 | 326 | public String getTitleInUrl() { 327 | return titleInUrl; 328 | } 329 | 330 | public void setTitleInUrl(String titleInUrl) { 331 | this.titleInUrl = titleInUrl; 332 | } 333 | 334 | public Integer getAvailability() { 335 | return availability; 336 | } 337 | 338 | public void setAvailability(Integer availability) { 339 | this.availability = availability; 340 | } 341 | 342 | public LanguageCode getLanguageCode() { 343 | return languageCode; 344 | } 345 | 346 | public void setLanguageCode(LanguageCode languageCode) { 347 | this.languageCode = languageCode; 348 | } 349 | 350 | public String getAuthor() { 351 | return author; 352 | } 353 | 354 | public void setAuthor(String author) { 355 | this.author = author; 356 | } 357 | 358 | public String getFbPage() { 359 | return fbPage; 360 | } 361 | 362 | public void setFbPage(String fbPage) { 363 | this.fbPage = fbPage; 364 | } 365 | 366 | public String getTwitterPage() { 367 | return twitterPage; 368 | } 369 | 370 | public void setTwitterPage(String twitterPage) { 371 | this.twitterPage = twitterPage; 372 | } 373 | 374 | public String getGplusPage() { 375 | return gplusPage; 376 | } 377 | 378 | public void setGplusPage(String gplusPage) { 379 | this.gplusPage = gplusPage; 380 | } 381 | 382 | public UpdateFrequency getUpdateFrequency() { 383 | return updateFrequency; 384 | } 385 | 386 | public void setUpdateFrequency(UpdateFrequency updateFrequency) { 387 | this.updateFrequency = updateFrequency; 388 | } 389 | 390 | public String getLastEpisodeMediaUrl() { 391 | return lastEpisodeMediaUrl; 392 | } 393 | 394 | public void setLastEpisodeMediaUrl(String lastEpisodeMediaUrl) { 395 | this.lastEpisodeMediaUrl = lastEpisodeMediaUrl; 396 | } 397 | 398 | } 399 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/Tag.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.GenerationType; 10 | import javax.persistence.Id; 11 | import javax.persistence.ManyToMany; 12 | import javax.persistence.Table; 13 | 14 | @Entity 15 | @Table(name="tags") 16 | public class Tag implements Serializable{ 17 | 18 | private static final long serialVersionUID = -2370292880165225805L; 19 | 20 | /** id of the tag - BIGINT in MySQL DB */ 21 | @Id 22 | @Column(name="tag_id") 23 | @GeneratedValue(strategy=GenerationType.AUTO) 24 | private long tagId; 25 | 26 | /** name of the tag */ 27 | @Column(name="name") 28 | private String name; 29 | 30 | @ManyToMany(mappedBy="tags") 31 | private List podcasts; 32 | 33 | public Tag(){} 34 | 35 | public Tag(String name){ 36 | this.name = name; 37 | } 38 | 39 | public List getPodcasts() { 40 | return podcasts; 41 | } 42 | 43 | public void setPodcasts(List podcasts) { 44 | this.podcasts = podcasts; 45 | } 46 | 47 | public long getTagId() { 48 | return tagId; 49 | } 50 | 51 | public void setTagId(long tagId) { 52 | this.tagId = tagId; 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public void setName(String name) { 60 | this.name = name; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/UpdateFrequency.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.util.EnumSet; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Enum with values for frequency update of the podcasts 9 | * @author adrian 10 | * 11 | */ 12 | public enum UpdateFrequency { 13 | 14 | /** podcast is updated daily*/ 15 | DAILY(1), 16 | 17 | /** podcast is updated weekly */ 18 | WEEKLY(2), 19 | 20 | /** podcast is updated monthly */ 21 | MONTHLY(3), 22 | 23 | /** podcast is updated yearyl */ 24 | YEARLY(4), 25 | 26 | /** podcast is most likely through, and it will never be updated - but contents are valuable and stored somewhere */ 27 | TERMINATED(5), 28 | 29 | /** the update frequency cannot be deducted or is unknown*/ 30 | UNKNOWN(6); 31 | 32 | private int code; 33 | 34 | private UpdateFrequency(int code) { 35 | this.code = code; 36 | } 37 | 38 | public int getCode() { 39 | return code; 40 | } 41 | 42 | private static final Map lookup 43 | = new HashMap(); 44 | 45 | static { 46 | for(UpdateFrequency s : EnumSet.allOf(UpdateFrequency.class)) 47 | lookup.put(s.getCode(), s); 48 | } 49 | 50 | public static UpdateFrequency get(int code) { 51 | return lookup.get(code); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/UpdateFrequencyConverter.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import javax.persistence.AttributeConverter; 4 | 5 | public class UpdateFrequencyConverter implements AttributeConverter{ 6 | 7 | @Override 8 | public Integer convertToDatabaseColumn(UpdateFrequency attribute) { 9 | 10 | switch(attribute) { 11 | case DAILY: 12 | return Integer.valueOf(1); 13 | case WEEKLY: 14 | return Integer.valueOf(2); 15 | case MONTHLY: 16 | return Integer.valueOf(3); 17 | case YEARLY: 18 | return Integer.valueOf(4); 19 | case TERMINATED: 20 | return Integer.valueOf(5); 21 | case UNKNOWN: 22 | return Integer.valueOf(6); 23 | default: 24 | throw new IllegalArgumentException("Unknown value: " + attribute); 25 | } 26 | 27 | } 28 | 29 | @Override 30 | public UpdateFrequency convertToEntityAttribute(Integer dbData) { 31 | switch (dbData){ 32 | case 1: 33 | return UpdateFrequency.DAILY; 34 | case 2: 35 | return UpdateFrequency.WEEKLY; 36 | case 3: 37 | return UpdateFrequency.MONTHLY; 38 | case 4: 39 | return UpdateFrequency.YEARLY; 40 | case 5: 41 | return UpdateFrequency.TERMINATED; 42 | case 6: 43 | return UpdateFrequency.UNKNOWN; 44 | default: 45 | throw new IllegalArgumentException("Unknown value: " + dbData); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/entities/User.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.entities; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.CascadeType; 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | import javax.persistence.JoinColumn; 10 | import javax.persistence.JoinTable; 11 | import javax.persistence.ManyToMany; 12 | import javax.persistence.Table; 13 | 14 | @Entity 15 | @Table(name="users") 16 | public class User { 17 | 18 | @Id 19 | @Column(name="username") 20 | private String username; 21 | 22 | @Column(name="password") 23 | private String password; 24 | 25 | @Column(name="email") 26 | private String email; 27 | 28 | @Column(name="name") 29 | private String name; 30 | 31 | @ManyToMany(cascade = CascadeType.ALL) 32 | @JoinTable( 33 | name="podcasts_email_subscribers", 34 | joinColumns={@JoinColumn(name="email")}, 35 | inverseJoinColumns={@JoinColumn(name="podcast_id")} 36 | ) 37 | private List podcasts; 38 | 39 | public String getUsername() { 40 | return username; 41 | } 42 | 43 | public void setUsername(String username) { 44 | this.username = username; 45 | } 46 | 47 | public String getPassword() { 48 | return password; 49 | } 50 | 51 | public void setPassword(String password) { 52 | this.password = password; 53 | } 54 | 55 | public String getEmail() { 56 | return email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getName() { 64 | return name; 65 | } 66 | 67 | public void setName(String name) { 68 | this.name = name; 69 | } 70 | 71 | public List getPodcasts() { 72 | return podcasts; 73 | } 74 | 75 | public void setPodcasts(List podcasts) { 76 | this.podcasts = podcasts; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/listeners/LogProcessListener.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.listeners; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.springframework.batch.core.ItemProcessListener; 6 | 7 | public class LogProcessListener implements ItemProcessListener { 8 | 9 | private static final Log log = LogFactory.getLog(LogProcessListener.class); 10 | 11 | public void afterProcess(Object item, Object result) { 12 | if(item!=null) log.info("Input to Processor: " + item.toString()); 13 | if(result!=null) log.info("Output of Processor: " + result.toString()); 14 | } 15 | 16 | public void beforeProcess(Object item) { 17 | } 18 | 19 | public void onProcessError(Object item, Exception e) { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/common/listeners/ProtocolListener.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.common.listeners; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map.Entry; 5 | 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | import org.springframework.batch.core.JobExecution; 9 | import org.springframework.batch.core.JobExecutionListener; 10 | import org.springframework.batch.core.JobParameter; 11 | import org.springframework.batch.core.JobParameters; 12 | import org.springframework.batch.core.StepExecution; 13 | 14 | public class ProtocolListener implements JobExecutionListener { 15 | private static final Log LOGGER = LogFactory.getLog(ProtocolListener.class); 16 | 17 | public void afterJob(JobExecution jobExecution) { 18 | StringBuilder protocol = new StringBuilder(); 19 | protocol.append("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); 20 | protocol.append("Protocol for " + jobExecution.getJobInstance().getJobName() + " \n"); 21 | protocol.append(" Started : "+ jobExecution.getStartTime()+"\n"); 22 | protocol.append(" Finished : "+ jobExecution.getEndTime()+"\n"); 23 | protocol.append(" Exit-Code : "+ jobExecution.getExitStatus().getExitCode()+"\n"); 24 | protocol.append(" Exit-Descr. : "+ jobExecution.getExitStatus().getExitDescription()+"\n"); 25 | protocol.append(" Status : "+ jobExecution.getStatus()+"\n"); 26 | protocol.append("+++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); 27 | 28 | protocol.append("Job-Parameter: \n"); 29 | JobParameters jp = jobExecution.getJobParameters(); 30 | for (Iterator> iter = jp.getParameters().entrySet().iterator(); iter.hasNext();) { 31 | Entry entry = iter.next(); 32 | protocol.append(" "+entry.getKey()+"="+entry.getValue()+"\n"); 33 | } 34 | protocol.append("+++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); 35 | 36 | for (StepExecution stepExecution : jobExecution.getStepExecutions()) { 37 | protocol.append("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); 38 | protocol.append("Step " + stepExecution.getStepName() + " \n"); 39 | protocol.append("WriteCount: " + stepExecution.getWriteCount() + "\n"); 40 | protocol.append("Commits: " + stepExecution.getCommitCount() + "\n"); 41 | protocol.append("SkipCount: " + stepExecution.getSkipCount() + "\n"); 42 | protocol.append("Rollbacks: " + stepExecution.getRollbackCount() + "\n"); 43 | protocol.append("Filter: " + stepExecution.getFilterCount() + "\n"); 44 | protocol.append("+++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n"); 45 | } 46 | LOGGER.info(protocol.toString()); 47 | } 48 | 49 | public void beforeJob(JobExecution arg0) { 50 | // nothing to do 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/AddPodcastJobConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast; 2 | 3 | import org.podcastpedia.batch.common.configuration.InfrastructureConfiguration; 4 | import org.podcastpedia.batch.common.listeners.LogProcessListener; 5 | import org.podcastpedia.batch.common.listeners.ProtocolListener; 6 | import org.podcastpedia.batch.jobs.addpodcast.model.SuggestedPodcast; 7 | import org.springframework.batch.core.Job; 8 | import org.springframework.batch.core.Step; 9 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 10 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 11 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 12 | import org.springframework.batch.core.configuration.annotation.StepScope; 13 | import org.springframework.batch.item.ItemProcessor; 14 | import org.springframework.batch.item.ItemWriter; 15 | import org.springframework.batch.item.file.FlatFileItemReader; 16 | import org.springframework.batch.item.file.LineMapper; 17 | import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; 18 | import org.springframework.batch.item.file.mapping.DefaultLineMapper; 19 | import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.beans.factory.annotation.Value; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.context.annotation.Import; 25 | import org.springframework.core.io.FileSystemResource; 26 | import org.springframework.core.io.Resource; 27 | 28 | import java.io.File; 29 | import java.io.FileFilter; 30 | 31 | @Configuration 32 | @EnableBatchProcessing 33 | @Import({InfrastructureConfiguration.class, ServicesConfiguration.class}) 34 | public class AddPodcastJobConfiguration { 35 | 36 | public static final String OVERRIDEN_BY_EXPRESSION_VALUE = "overriden by expression value"; 37 | @Autowired 38 | private JobBuilderFactory jobs; 39 | 40 | @Autowired 41 | private StepBuilderFactory stepBuilderFactory; 42 | 43 | @Bean 44 | public Job addNewPodcastJob(){ 45 | return jobs.get("addNewPodcastJob") 46 | .listener(protocolListener()) 47 | .start(step()) 48 | .build(); 49 | } 50 | 51 | @Bean 52 | public Step step(){ 53 | return stepBuilderFactory.get("step") 54 | .chunk(1) //important to be one in this case to commit after every line read 55 | .reader(reader(OVERRIDEN_BY_EXPRESSION_VALUE)) 56 | .processor(processor()) 57 | .writer(writer()) 58 | .listener(logProcessListener()) 59 | .faultTolerant() 60 | .skipLimit(10) //default is set to 0 61 | .skip(Exception.class) 62 | .build(); 63 | } 64 | 65 | @Bean 66 | @StepScope 67 | public FlatFileItemReader reader(@Value("#{jobParameters[directoryPath]}") String directoryPath){ 68 | FlatFileItemReader reader = new FlatFileItemReader(); 69 | reader.setLinesToSkip(1);//first line is title definition 70 | reader.setResource(getFileFromDirectory(directoryPath)); 71 | reader.setLineMapper(lineMapper()); 72 | 73 | return reader; 74 | } 75 | 76 | private Resource getFileFromDirectory(String directoryPath) { 77 | 78 | File fl = new File(directoryPath); 79 | 80 | File[] files = fl.listFiles(new FileFilter() { 81 | @Override 82 | public boolean accept(File file) { 83 | return file.isFile(); 84 | } 85 | }); 86 | 87 | if(files.length != 1) throw new RuntimeException("There must be only one file present in the folder to be processed"); 88 | 89 | return new FileSystemResource(files[0]); 90 | } 91 | @Bean 92 | public LineMapper lineMapper() { 93 | DefaultLineMapper lineMapper = new DefaultLineMapper(); 94 | 95 | DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer(); 96 | lineTokenizer.setDelimiter(";"); 97 | lineTokenizer.setStrict(false); 98 | lineTokenizer.setNames(new String[]{"FEED_URL", "IDENTIFIER_ON_PODCASTPEDIA", "CATEGORIES", "LANGUAGE", "MEDIA_TYPE", "UPDATE_FREQUENCY", "KEYWORDS", "FB_PAGE", "TWITTER_PAGE", "GPLUS_PAGE", "NAME_SUBMITTER", "EMAIL_SUBMITTER"}); 99 | 100 | BeanWrapperFieldSetMapper fieldSetMapper = new BeanWrapperFieldSetMapper(); 101 | fieldSetMapper.setTargetType(SuggestedPodcast.class); 102 | 103 | lineMapper.setLineTokenizer(lineTokenizer); 104 | lineMapper.setFieldSetMapper(suggestedPodcastFieldSetMapper()); 105 | 106 | return lineMapper; 107 | } 108 | 109 | @Bean 110 | public SuggestedPodcastFieldSetMapper suggestedPodcastFieldSetMapper() { 111 | return new SuggestedPodcastFieldSetMapper(); 112 | } 113 | 114 | /** configure the processor related stuff */ 115 | @Bean 116 | public ItemProcessor processor() { 117 | return new SuggestedPodcastItemProcessor(); 118 | } 119 | 120 | @Bean 121 | public ItemWriter writer() { 122 | return new Writer(); 123 | } 124 | 125 | @Bean 126 | public ProtocolListener protocolListener(){ 127 | return new ProtocolListener(); 128 | } 129 | 130 | @Bean 131 | public LogProcessListener logProcessListener(){ 132 | return new LogProcessListener(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/ServicesConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast; 2 | 3 | import java.io.IOException; 4 | import java.util.Properties; 5 | 6 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 7 | import org.apache.velocity.app.VelocityEngine; 8 | import org.apache.velocity.exception.VelocityException; 9 | import org.podcastpedia.batch.common.configuration.MailConfiguration; 10 | import org.podcastpedia.batch.jobs.addpodcast.dao.ReadDao; 11 | import org.podcastpedia.batch.jobs.addpodcast.dao.ReadDaoImpl; 12 | import org.podcastpedia.batch.jobs.addpodcast.service.EmailNotificationService; 13 | import org.podcastpedia.batch.jobs.addpodcast.service.EmailNotificationServiceImpl; 14 | import org.podcastpedia.batch.jobs.addpodcast.service.PodcastAndEpisodeAttributesService; 15 | import org.podcastpedia.batch.jobs.addpodcast.service.PodcastAndEpisodeAttributesServiceImpl; 16 | import org.podcastpedia.batch.jobs.addpodcast.service.SocialMediaService; 17 | import org.podcastpedia.batch.jobs.addpodcast.service.SocialMediaServiceImpl; 18 | import org.podcastpedia.batch.jobs.addpodcast.service.SyndFeedService; 19 | import org.podcastpedia.batch.jobs.addpodcast.service.SyndFeedServiceImpl; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Import; 23 | import org.springframework.ui.velocity.VelocityEngineFactoryBean; 24 | 25 | @Configuration 26 | @Import(MailConfiguration.class) 27 | public class ServicesConfiguration { 28 | 29 | @Bean 30 | public ReadDao readDao(){ 31 | return new ReadDaoImpl(); 32 | } 33 | 34 | @Bean 35 | public SyndFeedService syndFeedService(){ 36 | return new SyndFeedServiceImpl(); 37 | } 38 | 39 | @Bean 40 | public PodcastAndEpisodeAttributesService podcastAndEpisodeAttributesService(){ 41 | return new PodcastAndEpisodeAttributesServiceImpl(); 42 | } 43 | 44 | @Bean 45 | public EmailNotificationService emailNotificationService1(){ 46 | return new EmailNotificationServiceImpl(); 47 | } 48 | 49 | @Bean 50 | public SocialMediaService socialMediaService(){ 51 | return new SocialMediaServiceImpl(); 52 | } 53 | 54 | @Bean 55 | public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager(){ 56 | PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(); 57 | poolingHttpClientConnectionManager.setMaxTotal(100); 58 | 59 | return poolingHttpClientConnectionManager; 60 | } 61 | 62 | @Bean 63 | public VelocityEngine velocityEngine() throws VelocityException, IOException{ 64 | VelocityEngineFactoryBean factory = new VelocityEngineFactoryBean(); 65 | Properties props = new Properties(); 66 | props.put("resource.loader", "class"); 67 | props.put("class.resource.loader.class", 68 | "org.apache.velocity.runtime.resource.loader." + 69 | "ClasspathResourceLoader"); 70 | factory.setVelocityProperties(props); 71 | 72 | return factory.createVelocityEngine(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/SuggestedPodcastFieldSetMapper.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast; 2 | 3 | import org.podcastpedia.batch.common.entities.LanguageCode; 4 | import org.podcastpedia.batch.common.entities.MediaType; 5 | import org.podcastpedia.batch.common.entities.Podcast; 6 | import org.podcastpedia.batch.common.entities.UpdateFrequency; 7 | import org.podcastpedia.batch.jobs.addpodcast.model.SuggestedPodcast; 8 | import org.springframework.batch.item.file.mapping.FieldSetMapper; 9 | import org.springframework.batch.item.file.transform.FieldSet; 10 | import org.springframework.validation.BindException; 11 | 12 | public class SuggestedPodcastFieldSetMapper implements FieldSetMapper { 13 | 14 | @Override 15 | public SuggestedPodcast mapFieldSet(FieldSet fieldSet) throws BindException { 16 | 17 | SuggestedPodcast suggestedPodcast = new SuggestedPodcast(); 18 | 19 | suggestedPodcast.setCategories(fieldSet.readString("CATEGORIES")); 20 | suggestedPodcast.setEmail(fieldSet.readString("EMAIL_SUBMITTER")); 21 | suggestedPodcast.setName(fieldSet.readString("NAME_SUBMITTER")); 22 | suggestedPodcast.setTags(fieldSet.readString("KEYWORDS")); 23 | 24 | //some of the attributes we can map directly into the Podcast entity that we'll insert later into the database 25 | Podcast podcast = new Podcast(); 26 | podcast.setUrl(fieldSet.readString("FEED_URL")); 27 | podcast.setIdentifier(fieldSet.readString("IDENTIFIER_ON_PODCASTPEDIA")); 28 | podcast.setLanguageCode(LanguageCode.valueOf(fieldSet.readString("LANGUAGE"))); 29 | podcast.setMediaType(MediaType.valueOf(fieldSet.readString("MEDIA_TYPE"))); 30 | podcast.setUpdateFrequency(UpdateFrequency.valueOf(fieldSet.readString("UPDATE_FREQUENCY"))); 31 | podcast.setFbPage(fieldSet.readString("FB_PAGE")); 32 | podcast.setTwitterPage(fieldSet.readString("TWITTER_PAGE")); 33 | podcast.setGplusPage(fieldSet.readString("GPLUS_PAGE")); 34 | 35 | suggestedPodcast.setPodcast(podcast); 36 | 37 | return suggestedPodcast; 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/SuggestedPodcastItemProcessor.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast; 2 | 3 | import java.io.IOException; 4 | import java.net.MalformedURLException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.apache.commons.httpclient.util.DateParseException; 9 | import org.apache.commons.httpclient.util.DateUtil; 10 | import org.apache.http.Header; 11 | import org.apache.http.HttpResponse; 12 | import org.apache.http.HttpStatus; 13 | import org.apache.http.client.ClientProtocolException; 14 | import org.apache.http.client.HttpClient; 15 | import org.apache.http.client.config.RequestConfig; 16 | import org.apache.http.client.methods.HttpHead; 17 | import org.apache.http.impl.client.HttpClientBuilder; 18 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 19 | import org.podcastpedia.batch.common.entities.Category; 20 | import org.podcastpedia.batch.common.entities.Episode; 21 | import org.podcastpedia.batch.common.entities.EpisodeId; 22 | import org.podcastpedia.batch.common.entities.Podcast; 23 | import org.podcastpedia.batch.common.entities.Tag; 24 | import org.podcastpedia.batch.jobs.addpodcast.dao.ReadDao; 25 | import org.podcastpedia.batch.jobs.addpodcast.model.SuggestedPodcast; 26 | import org.podcastpedia.batch.jobs.addpodcast.service.PodcastAndEpisodeAttributesService; 27 | import org.podcastpedia.batch.jobs.addpodcast.service.SyndFeedService; 28 | import org.springframework.batch.item.ItemProcessor; 29 | import org.springframework.beans.factory.annotation.Autowired; 30 | 31 | import com.sun.syndication.feed.synd.SyndEntryImpl; 32 | import com.sun.syndication.feed.synd.SyndFeed; 33 | import com.sun.syndication.io.FeedException; 34 | 35 | public class SuggestedPodcastItemProcessor implements ItemProcessor { 36 | 37 | private static final int TIMEOUT = 10; 38 | 39 | @Autowired 40 | ReadDao readDao; 41 | 42 | @Autowired 43 | PodcastAndEpisodeAttributesService podcastAndEpisodeAttributesService; 44 | 45 | @Autowired 46 | private PoolingHttpClientConnectionManager poolingHttpClientConnectionManager; 47 | 48 | @Autowired 49 | private SyndFeedService syndFeedService; 50 | 51 | /** 52 | * Method used to build the categories, tags and episodes of the podcast 53 | */ 54 | @Override 55 | public SuggestedPodcast process(SuggestedPodcast item) throws Exception { 56 | 57 | if(isPodcastAlreadyInTheDirectory(item.getPodcast().getUrl())) { 58 | return null; 59 | } 60 | 61 | String[] categories = item.getCategories().trim().split("\\s*,\\s*"); 62 | 63 | item.getPodcast().setAvailability(org.apache.http.HttpStatus.SC_OK); 64 | 65 | //set etag and last modified attributes for the podcast 66 | setHeaderFieldAttributes(item.getPodcast()); 67 | 68 | //set the other attributes of the podcast from the feed 69 | podcastAndEpisodeAttributesService.setPodcastFeedAttributes(item.getPodcast()); 70 | 71 | //set the categories 72 | List categoriesByNames = readDao.findCategoriesByNames(categories); 73 | item.getPodcast().setCategories(categoriesByNames); 74 | 75 | //set the tags 76 | setTagsForPodcast(item); 77 | 78 | //build the episodes 79 | setEpisodesForPodcast(item.getPodcast()); 80 | 81 | return item; 82 | } 83 | 84 | private void setTagsForPodcast(SuggestedPodcast item) { 85 | String[] tags = item.getTags().trim().split("\\s*,\\s*"); 86 | List tagsByName = readDao.getTagsByNames(tags); 87 | 88 | List podcastTags = new ArrayList(); 89 | for(int i=0; i episodes = new ArrayList(); 170 | 171 | SyndFeed syndFeedForUrl = syndFeedService.getSyndFeedForUrl(podcast.getUrl()); 172 | if(null != syndFeedForUrl){ 173 | for(SyndEntryImpl entry: (List)syndFeedForUrl.getEntries()){ 174 | 175 | Episode episode = new Episode(); 176 | // episode.setPodcastId(podcast.getPodcastId()); 177 | 178 | //because there is an insertion operation we set them in order they come from the entry list 179 | EpisodeId episodeId = new EpisodeId(); 180 | episodeId.setEpisodeId(i); 181 | episode.setId(episodeId); 182 | 183 | //set attribues of the episode 184 | podcastAndEpisodeAttributesService.setEpisodeAttributes(episode, podcast, entry); 185 | i++; 186 | 187 | //at the beginning all the episodes are marked as available (200) 188 | episode.setAvailability(org.apache.http.HttpStatus.SC_OK); 189 | 190 | //insert the episode in the database 191 | try { 192 | //TODO when I move to InnoDB for table and lucene search, rollback the transaction with log message when updating the podcast 193 | //we add only the episodes that have a media file attached to them 194 | boolean episodeMediaCouldBeSet = !episode.getMediaUrl().equals("noMediaUrl"); 195 | if(episodeMediaCouldBeSet){ 196 | // LOG.info("PodId[" + podcast.getPodcastId().toString() + "] - " + "INSERT EPISODE epId[" + episode.getEpisodeId() 197 | // + "] - epURL " + episode.getMediaUrl()); 198 | // episodes.add(episode); 199 | podcast.addEpisode(episode); 200 | } 201 | 202 | } catch (Exception e) { 203 | // LOG.error("ERROR inserting episode " + episode.getMediaUrl() + " for podcastId[" + episode.getPodcastId() + "]" + e.getMessage()); 204 | continue; //do not mark it as new episode 205 | } 206 | 207 | } 208 | } 209 | 210 | // podcast.setEpisodes(episodes); 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/Writer.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import javax.inject.Inject; 7 | import javax.persistence.EntityManager; 8 | 9 | import org.podcastpedia.batch.common.entities.Podcast; 10 | import org.podcastpedia.batch.jobs.addpodcast.model.SuggestedPodcast; 11 | import org.podcastpedia.batch.jobs.addpodcast.service.EmailNotificationService; 12 | import org.podcastpedia.batch.jobs.addpodcast.service.SocialMediaService; 13 | import org.springframework.batch.item.ItemWriter; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | public class Writer implements ItemWriter{ 17 | 18 | @Autowired 19 | private EntityManager entityManager; 20 | 21 | @Inject 22 | private EmailNotificationService emailNotificationService; 23 | 24 | @Inject 25 | private SocialMediaService socialMediaService; 26 | 27 | @Override 28 | public void write(List items) throws Exception { 29 | 30 | if(items.get(0) != null){ 31 | SuggestedPodcast suggestedPodcast = items.get(0); 32 | 33 | //first insert the data in the database 34 | Podcast podcast = suggestedPodcast.getPodcast(); 35 | 36 | podcast.setInsertionDate(new Date()); 37 | entityManager.persist(podcast); 38 | entityManager.flush(); 39 | 40 | //notify submitter about the insertion and post a twitt about it 41 | String url = buildUrlOnPodcastpedia(podcast); 42 | 43 | emailNotificationService.sendPodcastAdditionConfirmation( 44 | suggestedPodcast.getName(), suggestedPodcast.getEmail(), 45 | url); 46 | if(podcast.getTwitterPage() != null){ 47 | socialMediaService.postOnTwitterAboutNewPodcast(podcast, 48 | url); 49 | } 50 | } 51 | 52 | } 53 | 54 | private String buildUrlOnPodcastpedia(Podcast podcast) { 55 | StringBuffer urlOnPodcastpedia = new StringBuffer( 56 | "http://www.podcastpedia.org"); 57 | if (podcast.getIdentifier() != null) { 58 | urlOnPodcastpedia.append("/" + podcast.getIdentifier()); 59 | } else { 60 | urlOnPodcastpedia.append("/podcasts/"); 61 | urlOnPodcastpedia.append(String.valueOf(podcast.getPodcastId())); 62 | urlOnPodcastpedia.append("/" + podcast.getTitleInUrl()); 63 | } 64 | String url = urlOnPodcastpedia.toString(); 65 | return url; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/dao/InsertDao.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.dao; 2 | 3 | import org.podcastpedia.batch.common.entities.Episode; 4 | import org.podcastpedia.batch.common.entities.Podcast; 5 | 6 | 7 | public interface InsertDao { 8 | 9 | /** 10 | * Inserts episode in the database 11 | * @param episode 12 | */ 13 | public void insertEpisode(Episode episode); 14 | 15 | /** 16 | * Adds a new podcast to the database 17 | * 18 | * @param podcast 19 | */ 20 | public void addPodcast(Podcast podcast); 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/dao/ReadDao.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.dao; 2 | 3 | import java.util.List; 4 | 5 | import org.podcastpedia.batch.common.entities.Category; 6 | import org.podcastpedia.batch.common.entities.Podcast; 7 | import org.podcastpedia.batch.common.entities.Tag; 8 | import org.podcastpedia.batch.common.entities.User; 9 | 10 | public interface ReadDao { 11 | 12 | public List findCategoriesByNames(String[] categoryNames); 13 | 14 | public List getTagsByNames(String[] tags); 15 | 16 | public Podcast getPodcastByFeedUrl(String url); 17 | 18 | public User getMeUser(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/dao/ReadDaoImpl.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.dao; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import javax.persistence.EntityManager; 7 | import javax.persistence.TypedQuery; 8 | 9 | import org.podcastpedia.batch.common.entities.Category; 10 | import org.podcastpedia.batch.common.entities.Episode; 11 | import org.podcastpedia.batch.common.entities.Podcast; 12 | import org.podcastpedia.batch.common.entities.Tag; 13 | import org.podcastpedia.batch.common.entities.User; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | public class ReadDaoImpl implements ReadDao { 17 | 18 | // @PersistenceContext(unitName = "batchPersistenceUnit") 19 | @Autowired 20 | private EntityManager entityManager; 21 | 22 | @Override 23 | public List findCategoriesByNames(String[] categoryNames) { 24 | 25 | String jpql = "SELECT c FROM Category c WHERE c.name IN :categoryNames"; 26 | TypedQuery query = entityManager.createQuery(jpql, Category.class); 27 | query.setParameter("categoryNames", Arrays.asList(categoryNames)); 28 | 29 | return query.getResultList(); 30 | } 31 | 32 | @Override 33 | public List getTagsByNames(String[] tags) { 34 | 35 | String jpql = "SELECT t FROM Tag t WHERE t.name IN :tags"; 36 | TypedQuery query = entityManager.createQuery(jpql, Tag.class); 37 | query.setParameter("tags", Arrays.asList(tags)); 38 | 39 | return query.getResultList(); 40 | } 41 | 42 | @Override 43 | public Podcast getPodcastByFeedUrl(String feedUrl) { 44 | String jpql = "SELECT p FROM Podcast p WHERE p.url = :url"; 45 | TypedQuery query = entityManager.createQuery(jpql, Podcast.class); 46 | query.setParameter("url", feedUrl); 47 | 48 | List resultList = query.getResultList(); 49 | if(resultList !=null && resultList.size() > 0) 50 | return resultList.get(0); 51 | else 52 | return null; 53 | } 54 | 55 | @Override 56 | public User getMeUser() { 57 | 58 | String sql = "SELECT u FROM User u WHERE u.email=?1 "; 59 | TypedQuery query = entityManager.createQuery(sql, User.class); 60 | query.setParameter(1, "adrianmatei@gmail.com"); 61 | 62 | User meTheUser = query.getSingleResult(); 63 | List episodes = meTheUser.getPodcasts().get(0).getEpisodes(); 64 | 65 | System.out.println("Episodes size of the first subscribed pod " + episodes.size()); 66 | 67 | return meTheUser; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/model/SuggestedPodcast.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.model; 2 | 3 | import org.podcastpedia.batch.common.entities.Podcast; 4 | 5 | 6 | public class SuggestedPodcast { 7 | 8 | private Podcast podcast; 9 | 10 | /** contains the categories submitted, comma separated as string */ 11 | protected String categories; 12 | 13 | /** contains the tags submitted, comma separated as string */ 14 | protected String tags; 15 | 16 | /** Name of the person suggesting the podcast */ 17 | protected String name; 18 | 19 | /** Email of the person suggesting the podcast */ 20 | protected String email; 21 | 22 | public String getTags() { 23 | return tags; 24 | } 25 | 26 | public void setTags(String tags) { 27 | this.tags = tags; 28 | } 29 | 30 | public String getCategories() { 31 | return categories; 32 | } 33 | 34 | public void setCategories(String categories) { 35 | this.categories = categories; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getEmail() { 47 | return email; 48 | } 49 | 50 | public void setEmail(String email) { 51 | this.email = email; 52 | } 53 | 54 | public Podcast getPodcast() { 55 | return podcast; 56 | } 57 | 58 | public void setPodcast(Podcast podcast) { 59 | this.podcast = podcast; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/EmailNotificationService.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | 4 | 5 | /** 6 | * Email notification service for user interaction with podcastpedia.org 7 | * 8 | * @author adrian 9 | * 10 | */ 11 | public interface EmailNotificationService { 12 | 13 | /** 14 | * Sends back a confirmation of podcast addition to the podcast submitter 15 | * 16 | * @param name 17 | * @param email 18 | * @param podcastUrl 19 | */ 20 | public void sendPodcastAdditionConfirmation(String name, String email, String podcastUrl); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/EmailNotificationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import javax.inject.Inject; 8 | import javax.mail.internet.MimeMessage; 9 | 10 | import org.apache.velocity.app.VelocityEngine; 11 | import org.springframework.mail.javamail.JavaMailSender; 12 | import org.springframework.mail.javamail.MimeMessageHelper; 13 | import org.springframework.mail.javamail.MimeMessagePreparator; 14 | import org.springframework.ui.velocity.VelocityEngineUtils; 15 | 16 | public class EmailNotificationServiceImpl implements EmailNotificationService { 17 | 18 | @Inject 19 | private JavaMailSender mailSender; 20 | 21 | @Inject 22 | private VelocityEngine velocityEngine; 23 | 24 | public void sendPodcastAdditionConfirmation(final String name, final String email, final String podcastUrl) { 25 | MimeMessagePreparator preparator = new MimeMessagePreparator() { 26 | 27 | 28 | @SuppressWarnings({ "rawtypes", "unchecked" }) 29 | @Override 30 | public void prepare(MimeMessage mimeMessage) throws Exception { 31 | MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8"); 32 | message.setTo("adrianmatei@gmail.com"); 33 | message.setBcc("adrian.matei@yahoo.com"); 34 | message.setFrom("contact@podcastpedia.org"); 35 | message.setSubject("Your podcast has been added to Podcastpedia.org"); 36 | message.setSentDate(new Date()); 37 | 38 | Map model = new HashMap(); 39 | model.put("name", name); 40 | model.put("podcastUrl", podcastUrl); 41 | 42 | String text = VelocityEngineUtils.mergeTemplateIntoString( 43 | velocityEngine, "templates/podcast_addition_notification.vm", "UTF-8", model); 44 | 45 | message.setText(text, true); 46 | //IMPORTANT - see documentation for setText 47 | // message.addInline("fb_logo", new ClassPathResource("img/fb_51.png")); 48 | // message.addInline("twitter_logo", new ClassPathResource("img/twitter_51.png")); 49 | // message.addInline("gplus_logo", new ClassPathResource("img/gplus_51.png")); 50 | 51 | } 52 | }; 53 | this.mailSender.send(preparator); 54 | } 55 | 56 | public JavaMailSender getMailSender() { 57 | return mailSender; 58 | } 59 | 60 | public void setMailSender(JavaMailSender mailSender) { 61 | this.mailSender = mailSender; 62 | } 63 | 64 | public VelocityEngine getVelocityEngine() { 65 | return velocityEngine; 66 | } 67 | 68 | public void setVelocityEngine(VelocityEngine velocityEngine) { 69 | this.velocityEngine = velocityEngine; 70 | } 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/PodcastAndEpisodeAttributesService.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | import java.io.IOException; 4 | 5 | import org.podcastpedia.batch.common.entities.Episode; 6 | import org.podcastpedia.batch.common.entities.Podcast; 7 | 8 | import com.sun.syndication.feed.synd.SyndEntryImpl; 9 | import com.sun.syndication.io.FeedException; 10 | 11 | public interface PodcastAndEpisodeAttributesService { 12 | 13 | /** 14 | * Given a podcast's feed url different attributes like title, description, 15 | * image url etc. are set for the podcast The second parameter 16 | * specifies whether the SyndFeed property of the podcast has already been 17 | * set 18 | * 19 | * @param p 20 | */ 21 | public void setPodcastFeedAttributes(Podcast podcast) throws IllegalArgumentException, 22 | FeedException, IOException; 23 | 24 | /** 25 | * Given a podcast's feed url different attributes like title, description, 26 | * image url etc. are set for the podcast The second parameter 27 | * specifies whether the SyndFeed property of the podcast has already been 28 | * set 29 | * 30 | * @param p 31 | */ 32 | public boolean setPodcastFeedAttributesWithWarnings(Podcast podcast) 33 | throws IllegalArgumentException, FeedException, IOException; 34 | 35 | /** 36 | * Given an entry in the attributes of the episode are set 37 | * 38 | * @param episode 39 | * @param entry 40 | */ 41 | public void setEpisodeAttributes(Episode episode, Podcast podcast, 42 | SyndEntryImpl entry); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/PodcastAndEpisodeAttributesServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.apache.log4j.Logger; 9 | import org.podcastpedia.batch.common.entities.Episode; 10 | import org.podcastpedia.batch.common.entities.MediaType; 11 | import org.podcastpedia.batch.common.entities.Podcast; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import com.sun.syndication.feed.synd.SyndEnclosureImpl; 15 | import com.sun.syndication.feed.synd.SyndEntryImpl; 16 | import com.sun.syndication.feed.synd.SyndFeed; 17 | import com.sun.syndication.feed.synd.SyndImage; 18 | import com.sun.syndication.io.FeedException; 19 | 20 | public class PodcastAndEpisodeAttributesServiceImpl implements PodcastAndEpisodeAttributesService { 21 | 22 | private static Logger LOG = Logger.getLogger(PodcastAndEpisodeAttributesServiceImpl.class); 23 | 24 | private static final int MAX_LENGTH_DESCRIPTION = 1000; 25 | 26 | private static final int TITLE_IN_URL_MAX_LENGTH = 100; 27 | 28 | private static final int MAX_PERMITTED_TITLE_LENGTH = 500; 29 | 30 | private static final String NO_IMAGE_LOCAL_URL = "http://www.podcastpedia.org/static/images/podcast-tm.jpg"; 31 | 32 | 33 | @Autowired 34 | SyndFeedService syndFeedService; 35 | 36 | @SuppressWarnings("unchecked") 37 | public void setPodcastFeedAttributes(Podcast podcast) throws IllegalArgumentException, 38 | FeedException, IOException { 39 | 40 | SyndFeed syndFeed = syndFeedService.getSyndFeedForUrl(podcast.getUrl()); 41 | 42 | 43 | if (syndFeed != null) { 44 | // set DESCRIPTION for podcast - used in search 45 | if (syndFeed.getDescription() != null 46 | && !syndFeed.getDescription().equals("")) { 47 | String description = syndFeed.getDescription(); 48 | // out of description remove tags if any exist and store also 49 | // short description 50 | String descWithoutTabs = description 51 | .replaceAll("\\<[^>]*>", ""); 52 | if (descWithoutTabs.length() > MAX_LENGTH_DESCRIPTION) { 53 | podcast.setDescription(descWithoutTabs.substring(0, 54 | MAX_LENGTH_DESCRIPTION)); 55 | } else { 56 | podcast.setDescription(descWithoutTabs); 57 | } 58 | 59 | } 60 | 61 | // set TITLE - used in search 62 | String podcastTitle = syndFeed.getTitle(); 63 | podcast.setTitle(podcastTitle); 64 | 65 | // build the title that will appear in the URL when accessing a 66 | // podcast from the main application 67 | String titleInUrl = podcastTitle.trim().replaceAll( 68 | "[^a-zA-Z0-9\\-\\s\\.]", ""); 69 | titleInUrl = titleInUrl.replaceAll("[\\-| |\\.]+", "-"); 70 | if (titleInUrl.length() > TITLE_IN_URL_MAX_LENGTH) { 71 | podcast.setTitleInUrl(titleInUrl.substring(0, 72 | TITLE_IN_URL_MAX_LENGTH)); 73 | } else { 74 | podcast.setTitleInUrl(titleInUrl); 75 | } 76 | 77 | // set author 78 | podcast.setAuthor(syndFeed.getAuthor()); 79 | 80 | // set COPYRIGHT 81 | podcast.setCopyright(syndFeed.getCopyright()); 82 | 83 | // set LINK 84 | podcast.setLink(syndFeed.getLink()); 85 | 86 | // set url link of the podcast's image when selecting the podcast in 87 | // the main application - mostly used through ) syndFeed 108 | .getEntries()) { 109 | // get the list of enclosures 110 | List enclosures = (List) entry 111 | .getEnclosures(); 112 | 113 | if (null != enclosures) { 114 | // if in the enclosure list is a media type (either audio or 115 | // video), this will set as the link of the episode 116 | for (SyndEnclosureImpl enclosure : enclosures) { 117 | if (null != enclosure) { 118 | podcast.setLastEpisodeMediaUrl(enclosure.getUrl()); 119 | break; 120 | } 121 | } 122 | } 123 | 124 | if (entry.getPublishedDate() == null) { 125 | LOG.warn("PodURL[" 126 | + podcast.getUrl() 127 | + "] - " 128 | + "COULD NOT SET publication date for podcast, default date 08.01.1983 will be used "); 129 | } else { 130 | podcast.setPublicationDate(entry.getPublishedDate()); 131 | } 132 | // first episode in the list is last episode - normally (are 133 | // there any exceptions?? TODO -investigate) 134 | break; 135 | } 136 | } 137 | 138 | } 139 | 140 | public void setEpisodeAttributes(Episode episode, Podcast podcast, 141 | SyndEntryImpl entry) { 142 | // set DESCRIPTION for episode - used in search 143 | if (null != entry.getDescription()) { 144 | 145 | String episodeDesc = entry.getDescription().getValue(); 146 | // tags are removed from description 147 | String descWithoutTabs = episodeDesc.replaceAll("\\<[^>]*>", ""); 148 | // carriage returns are removed from description - for player 149 | String descWithoutEndOfLine = descWithoutTabs.replaceAll("\\n", ""); 150 | if (descWithoutEndOfLine.length() > MAX_LENGTH_DESCRIPTION) { 151 | episode.setDescription(descWithoutEndOfLine.substring(0, 152 | MAX_LENGTH_DESCRIPTION)); 153 | } else { 154 | episode.setDescription(descWithoutEndOfLine); 155 | } 156 | 157 | } 158 | 159 | // set author 160 | episode.setAuthor(entry.getAuthor()); 161 | 162 | // set title for episode - used in search 163 | String episodeTitle = entry.getTitle(); 164 | if (episodeTitle != null) { 165 | // removes quotes to display properly in player 166 | episodeTitle = episodeTitle.replaceAll("\"", ""); 167 | if (episodeTitle.length() > MAX_PERMITTED_TITLE_LENGTH) { 168 | episodeTitle = episodeTitle.substring(0, 169 | MAX_PERMITTED_TITLE_LENGTH); 170 | } 171 | episode.setTitle(episodeTitle); 172 | String titleInUrl = episodeTitle.trim().replaceAll( 173 | "[^a-zA-Z0-9\\-\\s\\.]", ""); 174 | titleInUrl = titleInUrl.replaceAll("[\\-| |\\.]+", "-"); 175 | if (titleInUrl.length() > TITLE_IN_URL_MAX_LENGTH) { 176 | episode.setTitleInUrl(titleInUrl.substring(0, 177 | TITLE_IN_URL_MAX_LENGTH)); 178 | } else { 179 | episode.setTitleInUrl(titleInUrl); 180 | } 181 | } 182 | 183 | episode.setLink(entry.getLink()); 184 | 185 | // in the beginning inherit the media type from the podcast 186 | episode.setMediaType(podcast.getMediaType()); 187 | 188 | // get the list of enclosures 189 | @SuppressWarnings("unchecked") 190 | List enclosures = (List) entry 191 | .getEnclosures(); 192 | 193 | List audioMimeTypesList = Arrays.asList(audioMimeTypesArray); 194 | List videoMimeTypesList = Arrays.asList(videoMimeTypesArray); 195 | 196 | // set media url for the episode - this will be played in the player 197 | if (null != enclosures) { 198 | // if in the enclosure list is a media type (either audio or video), 199 | // this will set as the link of the episode 200 | for (SyndEnclosureImpl enclosure : enclosures) { 201 | if (null != enclosure) { 202 | episode.setMediaUrl(enclosure.getUrl()); 203 | if (enclosure.getLength() >= 0) 204 | episode.setLength(enclosure.getLength()); 205 | // when adding a new podcast media type is selected for the 206 | // podcast based on an initial view, but it can be that is a 207 | // mixed podcast so both audio 208 | // and video should be considered and in that case PRIORITY 209 | // has the type of the episode if any... 210 | if (null != enclosure.getType()) { 211 | episode.setEnclosureType(enclosure.getType().trim()); 212 | if (audioMimeTypesList.contains(enclosure.getType() 213 | .trim())) { 214 | episode.setMediaType(MediaType.Audio); 215 | break; 216 | } 217 | if (videoMimeTypesList.contains(enclosure.getType() 218 | .trim())) { 219 | episode.setMediaType(MediaType.Video); 220 | break; 221 | } 222 | } 223 | } 224 | } 225 | } else { 226 | episode.setMediaUrl("noMediaUrl"); 227 | } 228 | 229 | if (episode.getMediaUrl() == null) { 230 | episode.setMediaUrl("noMediaUrl"); 231 | } 232 | 233 | if (episode.getMediaUrl() == null 234 | || episode.getMediaUrl().equals("noMediaUrl")) { 235 | LOG.warn("PodcastId[" + podcast.getPodcastId() + "] - " 236 | + "COULD NOT SET MEDIA URL - " + "epTitle[" 237 | + entry.getTitle() + "]" + "feed[" + podcast.getUrl() + "]"); 238 | } 239 | 240 | // set link attribute 241 | episode.setLink(entry.getLink()); 242 | 243 | episode.setPublicationDate(entry.getPublishedDate()); 244 | updatePodcastPublicationDateAndLastMediaUrl(episode, podcast); 245 | 246 | if (episode.getPublicationDate() == null) { 247 | LOG.warn("PodcastId[" + podcast.getPodcastId() + "] - " 248 | + "COULD NOT SET publication date " + "epTitle[" 249 | + entry.getTitle() + "]" + "feed[" + podcast.getUrl() + "]"); 250 | } 251 | } 252 | 253 | /** 254 | * Set the podcast's publication date to the episode's if it is more recent 255 | * 256 | * @param episode 257 | * @param podcast 258 | * @param episodePublicationDate 259 | */ 260 | private void updatePodcastPublicationDateAndLastMediaUrl(Episode episode, 261 | Podcast podcast) { 262 | Date podcastPublicationDate = podcast.getPublicationDate(); 263 | boolean episodePubDateIsMoreRecent = episode.getPublicationDate() != null 264 | && (podcastPublicationDate == null || (podcastPublicationDate != null && podcastPublicationDate 265 | .before(episode.getPublicationDate()))); 266 | 267 | if (episodePubDateIsMoreRecent) { 268 | podcast.setPublicationDate(episode.getPublicationDate()); 269 | podcast.setLastEpisodeMediaUrl(episode.getMediaUrl()); 270 | } 271 | } 272 | 273 | private static String[] audioMimeTypesArray = { 274 | /* most common at the beginning */ 275 | "audio/basic", "audio/L24", "audio/mp3", "audio/mp4", "audio/ogg", 276 | "audio/mpg", "audio/mpeg", "audio/mpeg-url", "audio/mpeg3", 277 | "audio/vorbis", "audio/vnd.wave", "audio/webm", 278 | 279 | /* the rest in alphabetical order */ 280 | "audio/3gpp", "audio/aas", "audio/ac3", "audio/aiff", "audio/AMR", 281 | "audio/AMR-WB", "audio/annodex", "audio/asf", "audio/au", 282 | "audio/audible", "audio/avi", "audio/flac", "audio/gsm", 283 | "audio/iklax", "audio/it", "audio/m", "audio/m-mpeg", "audio/mad", 284 | "audio/make", "audio/mdz", "audio/med", "audio/mgp", "audio/mid", 285 | "audio/midi", "audio/mobile-xmf", "audio/mod", "audio/module-xm", 286 | "audio/mp4a-latm", "audio/nspaudio", "audio/pat", "audio/prs.sid", 287 | "audio/psid", "audio/rmf", "audio/s3m", "audio/scpls", "audio/sds", 288 | "audio/sfr", "audio/sidtune", "audio/snd", "audio/songsafe", 289 | "audio/soundtrack", "audio/tsp-audio", "audio/tsplayer", 290 | "audio/tta", "audio/vnd.lucent.voice ", "audio/vnd.nortel.vbk", 291 | "audio/vnd.qcelp", "audio/vnd.rn-realaudio", 292 | "audio/vnd.rn-realaudio-secure", "audio/vnd.rn-realvideo", 293 | "audio/vnd.rrn-realvideo", "audio/vnd.sealedmedia.softseal.mpeg", 294 | "audio/voc", "audio/x-voc", "audio/voxware", "audio/wav", 295 | "audio/wave", "audio/x-ahx", "audio/x-aifc", "audio/x-aiff", 296 | "audio/x-au", "audio/x-basic", "audio/x-dspeech", "audio/x-gsm", 297 | "audio/x-la-lqt", "audio/x-laplayer-reg", "audio/x-liquid", 298 | "audio/x-liquid-file", "audio/x-liquid-secure", 299 | "audio/x-liveaudio", "audio/x-mad", "audio/x-mid", "audio/x-midi", 300 | "audio/x-mod", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpeg3", 301 | "audio/x-mpeg-3", "audio/x-mpegaudio", "audio/x-mpegurl", 302 | "audio/x-mpg", "audio/x-ms-wax", "audio/x-ms-wma", 303 | "audio/x-nspaudio", "audio/x-ntx", "audio/x-pat", 304 | "audio/x-pm-realaudio-plugin", "audio/x-pn-aiff", "audio/x-pn-au", 305 | "audio/x-pn-audibleaudio", "audio/x-pn-realaudio", 306 | "audio/x-pn-realvideo", "audio/x-pn-wav", 307 | "audio/x-pn-realaudio-plugin", "audio/x-realaudio", 308 | "audio/x-realaudio-secure", "audio/x-rmf", "audio/x-s3m", 309 | "audio/x-scpls", "audio/x-sd2", "audio/x-sidtune", "audio/x-stx", 310 | "audio/x-tfmx", "audio/x-tta", "audio/x-twinvq", 311 | "audio/x-twinvq-plugin", "audio/x-ulaw", "audio/x-wav", 312 | "audio/x-xm", "audio/x-zipped-mod", "audio/xm" }; 313 | 314 | private static String[] videoMimeTypesArray = { 315 | /* most common at the beginning */ 316 | "video/mpeg", "video/mp4", "video/ogg", "video/quicktime", "video/webm", 317 | "video/x-matroska", "video/x-ms-wmv", "video/x-flv", 318 | 319 | /* the rest in alphabetical order */ 320 | "video/3gpp", "video/animaflex", "video/annodex", "video/avi", 321 | "video/avs-video", "video/dl", "video/dvd", "video/flc", 322 | "video/fli", "video/gl", "video/mng", "video/mpeg2", "video/mpg", 323 | "video/msvideo", "video/sgi-movie", "video/theora", 324 | "video/video cd", "video/vdo", "video/vivo", 325 | "video/vnd-rn-realvideo", "video/vnd.rn-realvideo", 326 | "video/vnd.rn-realvideo-secure", "video/vnd.sealed.mpeg1", 327 | "video/vnd.sealed.mpeg4", "video/vnd.sealed.swf", 328 | "video/vnd.sealedmedia.softseal.mov", "video/vnd.vivo", 329 | "video/vosaic", "video/x-acad-anim", "video/x-amt-demorun", 330 | "video/x-amt-showrun", "video/x-atomic3d-feature", "video/x-dl", 331 | "video/x-dv", "video/x-flc", "video/x-fli", "video/x-gl", 332 | "video/x-isvideo", "video/x-ivf", "video/x-m4v", 333 | "video/x-matroska", "video/x-la-asf", "video/x-mng", 334 | "video/x-motion-jpeg", "video/x-mpeg", "video/x-mpeg2-system", 335 | "video/x-mpeq2a", "video/x-mpegurl", "video/x-mpg", 336 | "video/x-ms-asf", "video/x-ms-asf-plugin", "video/x-ms-wm", 337 | "video/x-ms-wmp", "video/x-ms-wmx", "video/x-ms-wvv", 338 | "video/x-msvideo", "video/x-pn-realvideo", 339 | "video/x-pn-realvideo-plugin", "video/vnd.rn-realvideo", 340 | "video/x-qtc", "video/x-quicktime", "video/x-quicktimeplayer", 341 | "video/x-screencam", "video/x-sgi-movie", "video/xmpg2", 342 | "video/x-scm", "video/x-sgi-movie" }; 343 | 344 | 345 | public boolean setPodcastFeedAttributesWithWarnings(Podcast podcast) 346 | throws IllegalArgumentException, FeedException, IOException { 347 | 348 | boolean feedAttributesModified = false; 349 | 350 | // the syndFeed is newly created or it might have been loaded from a 351 | // local file 352 | SyndFeed syndFeed = syndFeedService.getSyndFeedForUrl(podcast.getUrl()); 353 | 354 | if (syndFeed != null) { 355 | 356 | // set DESCRIPTION for podcast - used in search 357 | if (syndFeed.getDescription() != null 358 | && !syndFeed.getDescription().equals("")) { 359 | String description = syndFeed.getDescription(); 360 | // out of description remove tags if any exist and store also 361 | // short description 362 | String descWithoutTabs = description 363 | .replaceAll("\\<[^>]*>", ""); 364 | if (descWithoutTabs.length() > MAX_LENGTH_DESCRIPTION) { 365 | boolean descriptionModified = !(podcast.getDescription() != null && podcast 366 | .getDescription().equals(descWithoutTabs)); 367 | if (descriptionModified) { 368 | LOG.warn("Podcast description has been modified " 369 | + "\n OLD " + podcast.getDescription() 370 | + "\n NEW " + descWithoutTabs); 371 | feedAttributesModified = true; 372 | } 373 | podcast.setDescription(descWithoutTabs.substring(0, 374 | MAX_LENGTH_DESCRIPTION)); 375 | } else { 376 | boolean descriptionModified = !(podcast.getDescription() != null && podcast 377 | .getDescription().equals(descWithoutTabs)); 378 | if (descriptionModified) { 379 | LOG.warn("Podcast description has been modified " 380 | + "\n OLD " + podcast.getDescription() 381 | + "\n NEW " + descWithoutTabs); 382 | feedAttributesModified = true; 383 | } 384 | podcast.setDescription(descWithoutTabs); 385 | } 386 | } 387 | 388 | // set TITLE - used in search 389 | String podcastTitle = syndFeed.getTitle(); 390 | if (!podcast.getTitle().equals(podcastTitle)) { 391 | LOG.warn("PodcastTitle has been modified " + "\n OLD " 392 | + podcast.getTitle() + "\n NEW " + podcastTitle); 393 | feedAttributesModified = true; 394 | } 395 | if (podcastTitle != null) { 396 | podcast.setTitle(podcastTitle); 397 | } 398 | 399 | // build the title that will appear in the URL when accessing a 400 | // podcast from the main application 401 | String titleInUrl = podcastTitle.trim().replaceAll( 402 | "[^a-zA-Z0-9\\-\\s\\.]", ""); 403 | titleInUrl = titleInUrl.replaceAll("[\\-| |\\.]+", "-"); 404 | if (titleInUrl.length() > TITLE_IN_URL_MAX_LENGTH) { 405 | podcast.setTitleInUrl(titleInUrl.substring(0, 406 | TITLE_IN_URL_MAX_LENGTH)); 407 | } else { 408 | podcast.setTitleInUrl(titleInUrl); 409 | } 410 | 411 | // set author 412 | boolean authorModified = syndFeed.getAuthor() != null 413 | && (podcast.getAuthor() == null || !podcast.getAuthor() 414 | .equals(syndFeed.getAuthor())); 415 | if (authorModified) { 416 | LOG.warn("PodcastTitle has been modified " + "\n OLD " 417 | + podcast.getTitle() + "\n NEW " + podcastTitle); 418 | feedAttributesModified = true; 419 | } 420 | if (syndFeed.getAuthor() != null) { 421 | podcast.setAuthor(syndFeed.getAuthor()); 422 | } 423 | 424 | // set COPYRIGHT 425 | boolean copyrightModified = syndFeed.getCopyright() != null 426 | && (podcast.getCopyright() == null || !podcast 427 | .getCopyright().equals(syndFeed.getCopyright())); 428 | if (copyrightModified) { 429 | LOG.warn("Copyright has been modified " + "\n OLD " 430 | + podcast.getCopyright() + "\n NEW" 431 | + syndFeed.getCopyright()); 432 | feedAttributesModified = true; 433 | } 434 | if (syndFeed.getCopyright() != null) { 435 | podcast.setCopyright(syndFeed.getCopyright()); 436 | } 437 | 438 | // set LINK 439 | boolean linkModified = syndFeed.getLink() != null 440 | && (podcast.getLink() == null || !podcast.getLink().equals( 441 | syndFeed.getLink())); 442 | if (linkModified) { 443 | LOG.warn("Copyright has been modified " + "\n OLD " 444 | + podcast.getLink() + "\n NEW" + syndFeed.getLink()); 445 | feedAttributesModified = true; 446 | } 447 | if (syndFeed.getLink() != null) { 448 | podcast.setLink(syndFeed.getLink()); 449 | } 450 | 451 | // set url link of the podcast's image when selecting the podcast in 452 | // the main application - mostly used through 0){ 22 | StringBuffer tweet = new StringBuffer(); 23 | 24 | tweet.append("\"" + podcast.getTitle() + "\""); //append podcast title to the tweet 25 | tweet.length(); 26 | 27 | String twitterAccount = podcast.getTwitterPage().substring(podcast.getTwitterPage().lastIndexOf("/") + 1); 28 | tweet.append(" by @" + twitterAccount); 29 | 30 | if(tweet.length() + COUNTED_URL_LENGTH <= MAX_TWEET_LENGTH) { 31 | //it gets tweeted only if the title plus author and link is not too big 32 | int tweetLengthBeforeUrlAddition = tweet.length(); 33 | tweetLengthBeforeUrlAddition += COUNTED_URL_LENGTH; 34 | 35 | tweet.append("\n"+ urlOnPodcastpedia + "\n\n"); 36 | 37 | //now we are adding the tags as long as the tweet's size doesn't jump over the MAX limit 38 | for(Tag tag : podcast.getTags()){ 39 | String trimmedTag = tag.getName().replaceAll("\\s+", ""); 40 | if(tweetLengthBeforeUrlAddition + " #".length() + trimmedTag.length() <= MAX_TWEET_LENGTH){ 41 | tweet.append("#" + trimmedTag + " ");//replace 42 | tweetLengthBeforeUrlAddition += ("#" + trimmedTag + " ").length(); 43 | } else { 44 | break; 45 | } 46 | } 47 | 48 | LOG.info(tweet.toString()); 49 | postOnTwiter(tweet); 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | private void postOnTwiter(StringBuffer tweet) { 57 | try { 58 | Twitter twitter = TwitterFactory.getSingleton(); 59 | Status status = twitter.updateStatus(tweet.toString()); 60 | LOG.debug("Successfully updated status to " + status.getText()); 61 | } catch (TwitterException e) { 62 | LOG.error("ERROR when trying to post on Twitter ", e); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/SyndFeedService.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | import java.io.IOException; 4 | import java.net.MalformedURLException; 5 | 6 | import com.sun.syndication.feed.synd.SyndFeed; 7 | import com.sun.syndication.io.FeedException; 8 | 9 | public interface SyndFeedService { 10 | 11 | /** 12 | * Given the url it returns the SyndFeed built with rome api 13 | * 14 | * @param url 15 | * @return 16 | */ 17 | public SyndFeed getSyndFeedForUrl(String url) throws MalformedURLException, 18 | IOException, IllegalArgumentException, FeedException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/addpodcast/service/SyndFeedServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.addpodcast.service; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | import java.util.zip.GZIPInputStream; 9 | 10 | import org.apache.log4j.Logger; 11 | import org.apache.xerces.impl.io.MalformedByteSequenceException; 12 | import org.xml.sax.InputSource; 13 | 14 | import com.sun.syndication.feed.synd.SyndFeed; 15 | import com.sun.syndication.io.FeedException; 16 | import com.sun.syndication.io.ParsingFeedException; 17 | import com.sun.syndication.io.SyndFeedInput; 18 | 19 | public class SyndFeedServiceImpl implements SyndFeedService { 20 | 21 | private static Logger LOG = Logger.getLogger(SyndFeedServiceImpl.class); 22 | 23 | private static final String SERVER_RETURNED_HTTP_RESPONSE_CODE_403_FOR_URL = "Server returned HTTP response code: 403 for URL"; 24 | 25 | @Override 26 | public SyndFeed getSyndFeedForUrl(String url) throws MalformedURLException, 27 | IOException, IllegalArgumentException, FeedException { 28 | 29 | SyndFeed feed = null; 30 | InputStream is = null; 31 | 32 | try { 33 | 34 | URLConnection openConnection = new URL(url).openConnection(); 35 | openConnection 36 | .addRequestProperty("User-Agent", 37 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"); 38 | is = openConnection.getInputStream(); 39 | if ("gzip".equals(openConnection.getContentEncoding())) { 40 | is = new GZIPInputStream(is); 41 | } 42 | InputSource source = new InputSource(is); 43 | SyndFeedInput input = new SyndFeedInput(); 44 | feed = input.build(source); 45 | 46 | } catch (ParsingFeedException e) { 47 | LOG.error("************* ParsingFeedException *************\n ", e); 48 | throw e; 49 | } catch (MalformedByteSequenceException e) { 50 | LOG.error( 51 | "************* MalformedByteSequenceException *************\n ", 52 | e); 53 | throw e; 54 | } catch (IOException e) { 55 | if (e.getMessage().contains( 56 | SERVER_RETURNED_HTTP_RESPONSE_CODE_403_FOR_URL)) { 57 | 58 | URLConnection openConnection = new URL(url).openConnection(); 59 | openConnection 60 | .addRequestProperty("User-Agent", 61 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"); 62 | is = openConnection.getInputStream(); 63 | if ("gzip".equals(openConnection.getContentEncoding())) { 64 | is = new GZIPInputStream(is); 65 | } 66 | InputSource source = new InputSource(is); 67 | 68 | SyndFeedInput input = new SyndFeedInput(); 69 | feed = input.build(source); 70 | } else { 71 | LOG.error("************* IOException *************\n ", e); 72 | throw e; 73 | } 74 | } finally { 75 | if (is != null) 76 | is.close(); 77 | } 78 | 79 | return feed; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/EmailNotificationPlaceholderRowMapper.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import org.podcastpedia.batch.jobs.notifysubscribers.model.EmailNotificationPlaceholder; 7 | import org.springframework.jdbc.core.RowMapper; 8 | 9 | public class EmailNotificationPlaceholderRowMapper implements RowMapper { 10 | 11 | @Override 12 | public EmailNotificationPlaceholder mapRow(ResultSet rs, int rowNum) 13 | throws SQLException { 14 | EmailNotificationPlaceholder emailNotificationPlaceholder = new EmailNotificationPlaceholder(); 15 | 16 | emailNotificationPlaceholder.setEmail(rs.getString("email")); 17 | emailNotificationPlaceholder.setPodcastId(rs.getInt("podcast_id")); 18 | emailNotificationPlaceholder.setPodcastTitle(rs.getString("title")); 19 | emailNotificationPlaceholder.setPodcastTitleInUrl(rs.getString("title_in_url")); 20 | emailNotificationPlaceholder.setEpisodeId(rs.getInt("episode_id")); 21 | emailNotificationPlaceholder.setEpisodeTitle(rs.getString("episode_title")); 22 | emailNotificationPlaceholder.setEpisodeTitleInUrl(rs.getString("episode_title_in_url")); 23 | emailNotificationPlaceholder.setEpisodePublicationDate(rs.getDate("publication_date")); 24 | 25 | return emailNotificationPlaceholder; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/NotifySubscribersItemProcessor.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.TypedQuery; 9 | 10 | import org.podcastpedia.batch.common.entities.Episode; 11 | import org.podcastpedia.batch.common.entities.Podcast; 12 | import org.podcastpedia.batch.common.entities.UpdateFrequency; 13 | import org.podcastpedia.batch.common.entities.User; 14 | import org.springframework.batch.item.ItemProcessor; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.context.annotation.Scope; 18 | 19 | @Scope("step") 20 | public class NotifySubscribersItemProcessor implements ItemProcessor { 21 | 22 | @Autowired 23 | EntityManager em; 24 | 25 | @Value("#{jobParameters[updateFrequency]}") 26 | String updateFrequency; 27 | 28 | @Override 29 | public User process(User item) throws Exception { 30 | 31 | String sqlInnerJoinEpisodes = "select e from User u JOIN u.podcasts p JOIN p.episodes e WHERE u.email=?1 AND p.updateFrequency=?2 AND" 32 | + " e.isNew IS NOT NULL AND e.availability=200 ORDER BY e.podcast.podcastId ASC, e.publicationDate ASC"; 33 | TypedQuery queryInnerJoinepisodes = em.createQuery(sqlInnerJoinEpisodes, Episode.class); 34 | queryInnerJoinepisodes.setParameter(1, item.getEmail()); 35 | queryInnerJoinepisodes.setParameter(2, UpdateFrequency.valueOf(updateFrequency)); 36 | 37 | List newEpisodes = queryInnerJoinepisodes.getResultList(); 38 | 39 | return regroupPodcastsWithEpisodes(item, newEpisodes); 40 | 41 | } 42 | 43 | private User regroupPodcastsWithEpisodes(User item, 44 | List newEpisodes) { 45 | if(newEpisodes.isEmpty()){ 46 | return null; //there are no new episodes for this user, wo we go to the next 47 | } else { 48 | List podcastsToNotify = new ArrayList(); 49 | 50 | Iterator epIterator = newEpisodes.iterator(); 51 | Episode firstEpisode = epIterator.next(); 52 | Podcast podcast = firstEpisode.getPodcast(); 53 | List episodesForPodcast = new ArrayList(); 54 | episodesForPodcast.add(firstEpisode); 55 | Episode nextEpisode = null; 56 | 57 | while(epIterator.hasNext()){ 58 | nextEpisode = epIterator.next(); 59 | if(nextEpisode.getPodcast().getPodcastId() == podcast.getPodcastId()){ 60 | episodesForPodcast.add(nextEpisode); 61 | } else { 62 | podcast.setEpisodes(episodesForPodcast); 63 | podcastsToNotify.add(podcast); 64 | 65 | podcast = nextEpisode.getPodcast(); 66 | episodesForPodcast = new ArrayList<>(); 67 | episodesForPodcast.add(nextEpisode); 68 | } 69 | } 70 | 71 | podcast.setEpisodes(episodesForPodcast); 72 | podcastsToNotify.add(podcast); 73 | 74 | item.setPodcasts(podcastsToNotify); 75 | 76 | } 77 | 78 | return item; 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/NotifySubscribersJobConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.podcastpedia.batch.common.configuration.InfrastructureConfiguration; 6 | import org.podcastpedia.batch.common.entities.User; 7 | import org.podcastpedia.batch.common.listeners.LogProcessListener; 8 | import org.podcastpedia.batch.common.listeners.ProtocolListener; 9 | import org.springframework.batch.core.Job; 10 | import org.springframework.batch.core.Step; 11 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 12 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 13 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 14 | import org.springframework.batch.core.configuration.annotation.StepScope; 15 | import org.springframework.batch.item.ItemProcessor; 16 | import org.springframework.batch.item.ItemReader; 17 | import org.springframework.batch.item.ItemWriter; 18 | import org.springframework.batch.item.database.JdbcCursorItemReader; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Import; 23 | 24 | import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException; 25 | 26 | @Configuration 27 | @EnableBatchProcessing 28 | @Import({InfrastructureConfiguration.class, NotifySubscribersServicesConfiguration.class}) 29 | public class NotifySubscribersJobConfiguration { 30 | 31 | @Autowired 32 | private JobBuilderFactory jobBuilders; 33 | 34 | @Autowired 35 | private StepBuilderFactory stepBuilders; 36 | 37 | @Autowired 38 | private DataSource dataSource; 39 | 40 | @Bean 41 | public Job newEpisodesNotificationJob(){ 42 | return jobBuilders.get("newEpisodesNotificationJob") 43 | .listener(protocolListener()) 44 | .start(notifySubscribersStep()) 45 | .build(); 46 | } 47 | 48 | @Bean 49 | public Step notifySubscribersStep(){ 50 | return stepBuilders.get("notifySubscribersStep") 51 | .chunk(1) //important to be one in this case to commit after every line read 52 | .reader(notifySubscribersReader()) 53 | .processor(notifySubscribersProcessor()) 54 | .writer(notifySubscribersWriter()) 55 | .listener(logProcessListener()) 56 | .faultTolerant() 57 | .skipLimit(10) //default is set to 0 58 | .skip(MySQLIntegrityConstraintViolationException.class) 59 | .build(); 60 | } 61 | 62 | @Bean 63 | public ItemReader notifySubscribersReader(){ 64 | 65 | JdbcCursorItemReader reader = new JdbcCursorItemReader(); 66 | String sql = "select * from users where is_email_subscriber is not null"; 67 | 68 | reader.setSql(sql); 69 | reader.setDataSource(dataSource); 70 | reader.setRowMapper(rowMapper()); 71 | 72 | return reader; 73 | } 74 | 75 | @Bean 76 | public UserRowMapper rowMapper(){ 77 | return new UserRowMapper(); 78 | } 79 | 80 | /** configure the processor related stuff */ 81 | @Bean 82 | @StepScope 83 | public ItemProcessor notifySubscribersProcessor() { 84 | return new NotifySubscribersItemProcessor(); 85 | } 86 | 87 | @Bean 88 | public ItemWriter notifySubscribersWriter() { 89 | return new NotifySubscribersWriter(); 90 | } 91 | 92 | @Bean 93 | public ProtocolListener protocolListener(){ 94 | return new ProtocolListener(); 95 | } 96 | 97 | @Bean 98 | public LogProcessListener logProcessListener(){ 99 | return new LogProcessListener(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/NotifySubscribersServicesConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.io.IOException; 4 | import java.util.Properties; 5 | 6 | import org.apache.velocity.app.VelocityEngine; 7 | import org.apache.velocity.exception.VelocityException; 8 | import org.podcastpedia.batch.common.configuration.MailConfiguration; 9 | import org.podcastpedia.batch.jobs.notifysubscribers.service.EmailNotificationService; 10 | import org.podcastpedia.batch.jobs.notifysubscribers.service.EmailNotificationServiceImpl; 11 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Import; 15 | import org.springframework.ui.velocity.VelocityEngineFactoryBean; 16 | 17 | @Configuration 18 | @Import(MailConfiguration.class) 19 | @EnableBatchProcessing(modular=true) 20 | public class NotifySubscribersServicesConfiguration { 21 | 22 | @Bean 23 | public EmailNotificationService emailNotificationService(){ 24 | return new EmailNotificationServiceImpl(); 25 | } 26 | 27 | @Bean 28 | public VelocityEngine velocityEngine() throws VelocityException, IOException{ 29 | VelocityEngineFactoryBean factory = new VelocityEngineFactoryBean(); 30 | Properties props = new Properties(); 31 | props.put("resource.loader", "class"); 32 | props.put("class.resource.loader.class", 33 | "org.apache.velocity.runtime.resource.loader." + 34 | "ClasspathResourceLoader"); 35 | factory.setVelocityProperties(props); 36 | 37 | return factory.createVelocityEngine(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/NotifySubscribersWriter.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | 7 | import org.podcastpedia.batch.common.entities.User; 8 | import org.podcastpedia.batch.jobs.notifysubscribers.service.EmailNotificationService; 9 | import org.springframework.batch.item.ItemWriter; 10 | 11 | public class NotifySubscribersWriter implements ItemWriter { 12 | 13 | @Inject 14 | private EmailNotificationService emailNotificationService; 15 | 16 | @Override 17 | public void write(List items) throws Exception { 18 | 19 | for(User item : items){ 20 | String[] split = item.getEmail().split("@"); 21 | item.setName(Character.toUpperCase(split[0].charAt(0)) + split[0].substring(1)); 22 | System.out.println("************* WRITER **************"); 23 | emailNotificationService.sendNewEpisodesNotification(item); 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/UserRowMapper.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import org.podcastpedia.batch.common.entities.User; 7 | import org.springframework.jdbc.core.RowMapper; 8 | 9 | public class UserRowMapper implements RowMapper { 10 | 11 | @Override 12 | public User mapRow(ResultSet rs, int rowNum) throws SQLException { 13 | User user = new User(); 14 | user.setEmail(rs.getString("email")); 15 | 16 | return user; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/model/EmailNotificationPlaceholder.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers.model; 2 | 3 | import java.util.Date; 4 | 5 | public class EmailNotificationPlaceholder { 6 | 7 | /** name of the email receiver */ 8 | String name; 9 | 10 | String email; 11 | 12 | Integer podcastId; 13 | 14 | String podcastTitle; 15 | 16 | String podcastTitleInUrl; 17 | 18 | Integer episodeId; 19 | 20 | String episodeTitle; 21 | 22 | String episodeTitleInUrl; 23 | 24 | Date episodePublicationDate; 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getEmail() { 35 | return email; 36 | } 37 | 38 | public void setEmail(String email) { 39 | this.email = email; 40 | } 41 | 42 | public Integer getPodcastId() { 43 | return podcastId; 44 | } 45 | 46 | public void setPodcastId(Integer podcastId) { 47 | this.podcastId = podcastId; 48 | } 49 | 50 | public String getPodcastTitle() { 51 | return podcastTitle; 52 | } 53 | 54 | public void setPodcastTitle(String podcastTitle) { 55 | this.podcastTitle = podcastTitle; 56 | } 57 | 58 | public String getPodcastTitleInUrl() { 59 | return podcastTitleInUrl; 60 | } 61 | 62 | public void setPodcastTitleInUrl(String podcastTitleInUrl) { 63 | this.podcastTitleInUrl = podcastTitleInUrl; 64 | } 65 | 66 | public Integer getEpisodeId() { 67 | return episodeId; 68 | } 69 | 70 | public void setEpisodeId(Integer episodeId) { 71 | this.episodeId = episodeId; 72 | } 73 | 74 | public String getEpisodeTitle() { 75 | return episodeTitle; 76 | } 77 | 78 | public void setEpisodeTitle(String episodeTitle) { 79 | this.episodeTitle = episodeTitle; 80 | } 81 | 82 | public String getEpisodeTitleInUrl() { 83 | return episodeTitleInUrl; 84 | } 85 | 86 | public void setEpisodeTitleInUrl(String episodeTitleInUrl) { 87 | this.episodeTitleInUrl = episodeTitleInUrl; 88 | } 89 | 90 | public Date getEpisodePublicationDate() { 91 | return episodePublicationDate; 92 | } 93 | 94 | public void setEpisodePublicationDate(Date episodePublicationDate) { 95 | this.episodePublicationDate = episodePublicationDate; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/service/EmailNotificationService.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers.service; 2 | 3 | import org.podcastpedia.batch.common.entities.User; 4 | 5 | 6 | 7 | /** 8 | * Email notification service for new episodes on the subscribed podcasts 9 | * 10 | * 11 | */ 12 | public interface EmailNotificationService { 13 | 14 | public void sendNewEpisodesNotification(User emailSubscriber); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/podcastpedia/batch/jobs/notifysubscribers/service/EmailNotificationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers.service; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Locale; 7 | import java.util.Map; 8 | 9 | import javax.inject.Inject; 10 | import javax.mail.internet.MimeMessage; 11 | 12 | import org.apache.velocity.app.VelocityEngine; 13 | import org.podcastpedia.batch.common.entities.User; 14 | import org.springframework.mail.javamail.JavaMailSender; 15 | import org.springframework.mail.javamail.MimeMessageHelper; 16 | import org.springframework.mail.javamail.MimeMessagePreparator; 17 | import org.springframework.ui.velocity.VelocityEngineUtils; 18 | 19 | public class EmailNotificationServiceImpl implements EmailNotificationService { 20 | 21 | @Inject 22 | private JavaMailSender mailSender; 23 | 24 | @Inject 25 | private VelocityEngine velocityEngine; 26 | 27 | public void sendNewEpisodesNotification(final User emailSubscriber) { 28 | MimeMessagePreparator preparator = new MimeMessagePreparator() { 29 | 30 | @SuppressWarnings({ "rawtypes", "unchecked" }) 31 | @Override 32 | public void prepare(MimeMessage mimeMessage) throws Exception { 33 | 34 | MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8"); 35 | message.setTo("adrianmatei@gmail.com"); 36 | message.setBcc("adrian.matei@yahoo.com"); 37 | message.setFrom("contact@podcastpedia.org"); 38 | 39 | message.setSubject("Latest episodes from your podcast subscriptions");//or maybe name some + "and more", like you tube 40 | message.setSentDate(new Date()); 41 | 42 | Map model = new HashMap(); 43 | model.put("user", emailSubscriber); 44 | 45 | //set today's date 46 | Date now = new Date(); 47 | SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.US); 48 | model.put("today", dateFormat.format(now)); 49 | 50 | String text = VelocityEngineUtils.mergeTemplateIntoString( 51 | velocityEngine, "templates/new_episodes_table.vm", "UTF-8", model); 52 | 53 | message.setText(text, true); 54 | System.out.println(text); 55 | } 56 | }; 57 | this.mailSender.send(preparator); 58 | } 59 | 60 | public JavaMailSender getMailSender() { 61 | return mailSender; 62 | } 63 | 64 | public void setMailSender(JavaMailSender mailSender) { 65 | this.mailSender = mailSender; 66 | } 67 | 68 | public VelocityEngine getVelocityEngine() { 69 | return velocityEngine; 70 | } 71 | 72 | public void setVelocityEngine(VelocityEngine velocityEngine) { 73 | this.velocityEngine = velocityEngine; 74 | } 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/main/resources/application-dev_home.properties: -------------------------------------------------------------------------------- 1 | db.url=jdbc:mysql://localhost:3307/pcmDB?allowMultiQueries=true 2 | db.driver=com.mysql.jdbc.Driver 3 | db.username=pcm 4 | db.password=pcm_pw 5 | 6 | spring.batch.job.names= newEpisodesNotificationJob, addNewPodcastJob 7 | spring.batch.job.enabled=true -------------------------------------------------------------------------------- /src/main/resources/application-dev_work-notify-job.properties: -------------------------------------------------------------------------------- 1 | db.url=jdbc:mysql://localhost:3306/pcmDB?allowMultiQueries=true 2 | db.driver=com.mysql.jdbc.Driver 3 | db.username=pcm 4 | db.password=pcm_pw 5 | 6 | spring.batch.job.names= newEpisodesNotificationJob 7 | spring.batch.job.enabled=true -------------------------------------------------------------------------------- /src/main/resources/application-dev_work.properties: -------------------------------------------------------------------------------- 1 | db.url=jdbc:mysql://localhost:3306/pcmDB?allowMultiQueries=true 2 | db.driver=com.mysql.jdbc.Driver 3 | db.username=pcm 4 | db.password=pcm_pw 5 | 6 | spring.batch.job.names= addNewPodcastJob 7 | spring.batch.job.enabled=true -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.jpa.hibernate.ddl-auto=create 2 | #spring.jpa.show-sql=true 3 | #spring.batch.job.names=addNewPodcastJob, emailNotificationAboutNewEpisodesJob 4 | db.url=jdbc:mysql://localhost:3307/pcmDB?allowMultiQueries=true 5 | db.driver=com.mysql.jdbc.Driver 6 | db.username=pcm 7 | db.password=pcm_pw 8 | 9 | batchdb.url=jdbc:mysql://localhost:3307/batchdb?allowMultiQueries=true 10 | batchdb.driver=com.mysql.jdbc.Driver 11 | batchdb.username=batch 12 | batchdb.password=batch 13 | 14 | spring.batch.job.enabled=false 15 | #spring.batch.job.names= newEpisodesNotificationJob, addNewPodcastJob -------------------------------------------------------------------------------- /src/main/resources/log4j.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 82 | 83 | 84 | 85 | 86 | 90 | 91 | 92 | 93 | 97 | 98 | 99 | 100 | 101 | 102 | 107 | 108 | 109 | 110 | 111 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 141 | 142 | 143 | 144 | 146 | 147 | 148 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 167 | -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/resources/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.hibernate.ejb.HibernatePersistence 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/suggested-podcasts.in: -------------------------------------------------------------------------------- 1 | FEED_URL; IDENTIFIER_ON_PODCASTPEDIA; CATEGORIES; LANGUAGE; MEDIA_TYPE; UPDATE_FREQUENCY; KEYWORDS; FB_PAGE; TWITTER_PAGE; GPLUS_PAGE; NAME_SUBMITTER; EMAIL_SUBMITTER 2 | http://www.npr.org/rss/podcast.php?id=1056; NPR-Topics-WorldStoryOfTheDay; news_politics; en; Audio; DAILY; NPR,NPR,World Story of the Day,Washington,District of Columbia,Morning Edition,All Things Considered,Fresh Air; ; ; ; Adrian; adrianmatei@gmail.com -------------------------------------------------------------------------------- /src/main/resources/templates/new_episodes.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if( $XHTML ) 4 | #set( $br = "
" ) 5 | #else 6 | #set( $br = "
" ) 7 | #end 8 | 9 | 10 |

$user.name, check out the latest episodes from your podcast subscriptions for $tools.date

11 |
24 | 25 |

26 | Thank you for sharing and connecting with us 27 | $br 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |

38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/templates/new_episodes_table.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if( $XHTML ) 4 | #set( $br = "
" ) 5 | #else 6 | #set( $br = "
" ) 7 | #end 8 | 9 | 10 | 94 | 95 |
11 | 12 | 13 | 22 | 23 | 24 | 77 | 78 | 79 | 91 | 92 |
14 | 15 | 16 | 19 | 20 |
17 |

Podcastpedia.org, knowledge to go

18 |
21 |
25 | 26 | 27 | 30 | 31 | #foreach( $podcast in $user.podcasts ) 32 | ## set the url of the podcast, to reach when clicking the picture 33 | #if( "$podcast.identifier"=="" ) 34 | #set( $podcastUrl = "http://www.podcastpedia.org/$podcast.identifier" ) 35 | #else 36 | #set( $podcastUrl = "http://www.podcastpedia.org/podcasts/$podcast.podcastId/$podcast.titleInUrl/" ) 37 | #end 38 | 39 | 58 | 59 | #end 60 | 61 | 74 | 75 |
28 |

$user.name, check out the latest episodes from your podcast subscriptions for $today

29 |
40 | 41 | 42 | 45 | 55 | 56 |
43 | 44 | 46 | $podcast.title 47 |
    48 | #foreach( $episode in $podcast.episodes ) 49 |
  • 50 | $episode.title 51 |
  • 52 | #end 53 |
54 |
57 |
62 | Thank you for sharing and connecting with us 63 | $br 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
76 |
80 | 81 | 82 | 88 | 89 |
83 | 84 | Podcastpedia sends email summaries like these so that you can keep up with the podcasts 85 | you subscribed via email. If you no longer want to receive these updates you can unsubscribe from all podcasts. 86 | 87 |
90 |
93 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/resources/templates/podcast_addition_notification.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if( $XHTML ) 4 | #set( $br = "
" ) 5 | #else 6 | #set( $br = "
" ) 7 | #end 8 | 9 | Dear ${name}, 10 |

11 | Thank you very much for your suggestion. Your podcast has been added to the directory: 12 | $br 13 | ${podcastUrl} 14 |

15 |

16 | Best regards, 17 |
18 | Podcastpedia.org 19 |

20 |

21 | Thank you for sharing and connecting with us 22 | $br 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |

33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/java/org/podcastpedia/batch/jobs/notifysubscribers/TestApp.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.PersistenceContext; 7 | import javax.persistence.TypedQuery; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.podcastpedia.batch.common.entities.Episode; 12 | import org.podcastpedia.batch.common.entities.User; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | 15 | @RunWith(SpringJUnit4ClassRunner.class) 16 | //@ContextConfiguration(classes = {InfrastructureConfiguration.class, NotifySubscribersServicesConfiguration.class}) 17 | public class TestApp { 18 | 19 | // @Autowired 20 | // ReadDao readDao; 21 | // 22 | // @Test 23 | // public void testLoadPodcasts(){ 24 | // 25 | // readDao.getMeUser(); 26 | // } 27 | 28 | @PersistenceContext 29 | private EntityManager entityManager; 30 | @Test public void testLoadPodcasts(){ String sql = "SELECT u FROM User u WHERE u.email=?1 "; TypedQuery query = entityManager.createQuery(sql, User.class); query.setParameter(1, "adrianmatei@gmail.com"); User meTheUser = query.getSingleResult(); List episodes = meTheUser.getPodcasts().get(0).getEpisodes(); System.out.println("Episodes size of the first subscribed pod " + episodes.size()); } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/org/podcastpedia/batch/jobs/notifysubscribers/TestConfig.java: -------------------------------------------------------------------------------- 1 | package org.podcastpedia.batch.jobs.notifysubscribers; 2 | 3 | public class TestConfig { 4 | 5 | } 6 | --------------------------------------------------------------------------------