├── README.md ├── consumer ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sayedbaladoh │ │ │ └── buzzdiggr │ │ │ └── consumer │ │ │ ├── ConsumerApplication.java │ │ │ ├── config │ │ │ └── KakfaConsumerConfig.java │ │ │ ├── controller │ │ │ ├── ArticleController.java │ │ │ └── TweetController.java │ │ │ ├── model │ │ │ ├── Article.java │ │ │ └── Tweet.java │ │ │ ├── repository │ │ │ ├── ArticleRepository.java │ │ │ └── TweetRepository.java │ │ │ └── service │ │ │ ├── ArticleService.java │ │ │ ├── KafkaReciever.java │ │ │ └── TweetService.java │ └── resources │ │ ├── application.properties │ │ └── application.yml │ └── test │ └── java │ └── com │ └── sayedbaladoh │ └── buzzdiggr │ └── consumer │ └── ConsumerApplicationTests.java ├── docker-compose.yml ├── front-end ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── search │ │ │ ├── search.component.css │ │ │ ├── search.component.html │ │ │ ├── search.component.spec.ts │ │ │ ├── search.component.ts │ │ │ ├── search.service.spec.ts │ │ │ └── search.service.ts │ │ └── setting │ │ │ ├── setting.component.css │ │ │ ├── setting.component.html │ │ │ ├── setting.component.spec.ts │ │ │ ├── setting.component.ts │ │ │ ├── setting.service.spec.ts │ │ │ └── setting.service.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── spinner.gif │ │ └── spinnerGrey.gif │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── images ├── Architecture.png ├── FullTextSearchUI.png ├── SearchFullTextSearchUI.png ├── SettingsFullTextSearchUI.png └── dockerComposeUpPs.png └── producer ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── sayedbaladoh │ │ └── buzzdiggr │ │ └── producer │ │ ├── ProducerApplication.java │ │ ├── config │ │ ├── KakfaProducerConfig.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ └── StreamController.java │ │ ├── model │ │ ├── Article.java │ │ └── Tweet.java │ │ ├── property │ │ └── TwitterProperties.java │ │ ├── service │ │ ├── crawler │ │ │ └── Spider.java │ │ ├── kafka │ │ │ └── Sender.java │ │ └── stream │ │ │ ├── TweetHandler.java │ │ │ └── TwitterStream.java │ │ └── util │ │ └── StringUtils.java └── resources │ ├── application.properties │ └── application.yml └── test └── java └── com └── sayedbaladoh └── buzzdiggr └── producer └── ProducerApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # Full-Text Search System to stream, collect, clean, store and filter data collected from different sources using Docker, Kafka, Elasticsearch, Kibana, Java, Spring Boot, Spring Kafka, Spring Data Elasticsearch, Twitter HBC, Jsoup and Angular 2 | 3 | Designing and implementing a system to stream, capture, clean, store data, and allows users to make full-text search and filter data collected from different sources (social media, world wide web). It has the following services: 4 | 5 | + **Producer service** to extract data from different sources (social media and web), clean, send it to Kafka producer and provide APIs to configure and launch the streaming for social media (twitter) and Crawling the web sites using Java, Spring Boot, Spring Kafka and Maven. 6 | 7 | + **Consumer service** is a Kafka Consumer to read messages from Kafka, process and index them into Elasticsearch and provide APIs for full-text searching and filtering data from Elasticsearch using Java, Spring Boot, Spring Kafka, Spring Data Elasticsearch and Maven. 8 | 9 | + **FrontEnd app** is a simple UI application using Angular to configure and launch the real-time social media (twitter) streamer and web crawler. It also provide full-text search on the collected data in a simple way. 10 | 11 | ## Table of contents 12 | * [Architecture](#architecture) 13 | * [Technologies](#technologies) 14 | * [Getting Started](#getting-started) 15 | * [About me](#about-me) 16 | * [Acknowledgments](#acknowledgments) 17 | 18 | ## Architecture 19 | The next diagram shows the system architecture 20 | 21 | ![Architecture Diagram](images/Architecture.png) 22 | 23 | + Tha admin of the system *configure and launch* the real-time social media streaming (Twitter for this case study) and web crawling for any other web site using simple UI app. The streaming data send to *Kafka produce*. 24 | + The *Kafka consumer* consumes data and the consumer service converting it to *Elasticsearch*. 25 | + The *Elasticsearch* receives data from Kafka to index and store it. 26 | + Tha admin can use *Kibana* to visualize, monitor and manage data. 27 | + The user can use a simple *UI search* app to make *full-text search* and filter the collected data. 28 | 29 | ## Technologies 30 | This project is created using the following technologies: 31 | 32 | 1. Java 8 33 | 2. Maven Dependency Management 34 | 3. Spring Boot: 35 | 36 | + Spring Web 37 | + Spring Kafka 38 | + Spring Data Elasticsearch 39 | + Spring Actuator 40 | 41 | 4. Twitter hbc 42 | 5. Jsoup 43 | 6. Docker 44 | 7. Apache Kafka and Apache Zookeeper (required for Kafka) 45 | 8. Elasticsearch 46 | 9. Kibana 47 | 10. Angular 48 | 49 | ## Getting Started 50 | 51 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 52 | 53 | ### Prerequisites 54 | You need to install the following software: 55 | * Java JDK 1.8+ 56 | * Maven 3.0+ 57 | * Git client 58 | * Docker Compose: To [install docker-compose](https://docs.docker.com/compose/install/) 59 | * Twitter API credentials: Set up an Twitter application account and get Twitter app credentials from [https://apps.twitter.com/](https://apps.twitter.com/). 60 | For help: [How to create a Twitter application account](http://docs.inboundnow.com/guide/create-twitter-application/) 61 | * Angular 6+ 62 | 63 | ### Setup 64 | To run this project, install it locally as follow: 65 | 66 | 1. **Clone the application** 67 | 68 | ```bash 69 | git clone https://github.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular.git 70 | ``` 71 | 72 | 2. **Change twitter configuration with your API Key, API Secret Key, Access Token and Access Token Secret** 73 | 74 | + open `producer/src/main/resources/application.yml` file 75 | + change twitter `auth` properties: 76 | ```bash 77 | social: 78 | twitter: 79 | auth: 80 | apiKey: API_KEY 81 | apiSecretKey: API_SECRET_KEY 82 | accessToken: ACCESS_TOKEN 83 | accessTokenSecret: ACCESS_TOKEN_SECRET 84 | ``` 85 | 86 | 3. **Start the Kafka, Zookeper, Elasticsearch and Kibana using docker-compose** 87 | 88 | The project includes a [*docker-compose.yml*](docker-compose.yml) file so you can use Docker Compose to start up them, no installation needed. 89 | 90 | ```bash 91 | cd solution_directory 92 | docker-compose up -d 93 | ``` 94 | 95 | 4. **Check if Kafka, Zookeper, Elasticsearch and Kibana is running** 96 | 97 | From command prompt: 98 | 99 | ```bash 100 | docker ps -a 101 | ``` 102 | You should see the following result: 103 | ![Kafka, Zookeper, Elasticsearch and Kibana are UP and RUNNING](images/dockerComposeUpPs.png) 104 | 105 | You can even make other checks in order to make sure your Kibana and elasticsearch are running. Open your internet browser and use the following URLs: 106 | 107 | + http://localhost:9200/ (Elasticsearch) 108 | + http://localhost:5601/ (Kibana) 109 | 110 | 5. **Run Producer service application** 111 | 112 | You can start the producer service by typing the following command 113 | 114 | ```bash 115 | cd producer 116 | mvn spring-boot:run 117 | ``` 118 | The producer service will start on port `8081`, So you'll be able to visit it under address `http://localhost:8081`. 119 | 120 | + http://localhost:8081/producer/actuator/info (To view `info` about `producer` service) 121 | 122 | + http://localhost:8081/producer/actuator/health (To `Check Health` for `producer` service) 123 | 124 | 6. **Run the Consumer service application** 125 | 126 | You can start the consumer service by typing the following command 127 | 128 | ```bash 129 | cd consumer 130 | mvn spring-boot:run 131 | ``` 132 | 133 | The consumer service will start on port `8082`, So you'll be able to visit it under address `http://localhost:8082`. 134 | 135 | + http://localhost:8082/consumer/actuator/info (To view `info` about `consumer` service) 136 | 137 | + http://localhost:8082/consumer/actuator/health (To `Check Health` for `producer` service) 138 | 139 | 7. **Start the Frontend application** 140 | 141 | You can start the UI application by typing the following commands 142 | 143 | ```bash 144 | cd front-end 145 | npm install 146 | ng serve 147 | ``` 148 | 149 | The UI app will start on port `4200` by default, So once you have successfully started application you'll be able to visit it using http://localhost:4200 150 | 151 | 8. **Package the applications** 152 | 153 | You can also package the applications in the form of a `jar` file and then run each application like so 154 | 155 | ```bash 156 | cd service_directory 157 | mvn clean package 158 | java -jar target/service_name-0.0.1-SNAPSHOT.jar 159 | ``` 160 | 161 | + *service_directory*: the directory of the service. 162 | + *service_name*: the name of the service. 163 | 164 | ### Running 165 | 166 | * **Full-Text Search Front-end application** 167 | 168 | To access the frontend application use the following endpoins: 169 | 170 | + http://localhost:4200 171 | 172 | ![Full-Text Search UI](images/FullTextSearchUI.png) 173 | 174 | Now take a look on UI app: You will find two links (Settings | Search). 175 | 176 | * **Full-Text Search Front-end Settings Tab** 177 | 178 | Use *Settings* to configure and launch the real-time social media (twitter) streaming and web crawling to collect your required data. 179 | 180 | + http://localhost:4200/config 181 | 182 | ![Full-Text Search UI Settings tab](images/SettingsFullTextSearchUI.png) 183 | 184 | * **Full-Text Search Front-end Search Tab** 185 | 186 | The *Search* link provide full-text search on the collected data. 187 | 188 | + http://localhost:4200/search 189 | 190 | ![Full-Text Search UI Search tab](images/SearchFullTextSearchUI.png) 191 | 192 | ## References 193 | 194 | * [Social media analytics: a survey of techniques, tools and platforms](https://link.springer.com/article/10.1007/s00146-014-0549-4) 195 | * [How to effectively clean social media data for analysis](https://hub.packtpub.com/clean-social-media-data-analysis-python/) 196 | * [8 Most Popular Java Web Crawling & Scraping Libraries](https://www.datasciencecentral.com/profiles/blogs/8-most-popular-java-web-crawling-amp-scraping-libraries) 197 | * [Web Crawling: How can you extract news articles given keywords and news sources?](https://www.quora.com/Web-Crawling-How-can-you-extract-news-articles-given-keywords-and-news-sources) 198 | * [How to make a simple web crawler in Java](http://www.netinstructions.com/how-to-make-a-simple-web-crawler-in-java/) 199 | * [Get started with the Twitter developer platform](https://developer.twitter.com/en/docs/basics/getting-started) 200 | * [Streaming Real-time Twitter feeds using Apache Kafka](https://www.linkedin.com/pulse/streaming-real-time-twitter-feeds-using-apache-kafka-manisha-malhotra/) 201 | * [Write a Kafka Producer Using Twitter Stream](https://dzone.com/articles/how-to-write-a-kafka-producer-using-twitter-stream) 202 | * [Building a Full-Text Search App Using Docker and Elasticsearch](https://blog.patricktriest.com/text-search-docker-elasticsearch/) 203 | * [Elasticsearch with Spring Boot](https://piotrminkowski.wordpress.com/2019/03/29/elasticsearch-with-spring-boot/) 204 | 205 | ## About me 206 | 207 | I am Sayed Baladoh - Phd. Senior Software Engineer. I like software development. You can contact me via: 208 | 209 | * [LinkedIn+](https://www.linkedin.com/in/sayed-baladoh-227aa66b/) 210 | * [Mail](mailto:sayedbaladoh@yahoo.com) 211 | * [Phone +20 1004337924](tel:+201004337924) 212 | 213 | _**Any improvement or comment about the project is always welcome! As well as others shared their code publicly I want to share mine! Thanks!**_ 214 | 215 | ## Acknowledgments 216 | 217 | Thanks for reading. 218 | 219 | Did I help you? 220 | + Share it with someone you think it might be helpful. 221 | + Give a star to this project 222 | -------------------------------------------------------------------------------- /consumer/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /consumer/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /consumer/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/consumer/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /consumer/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /consumer/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /consumer/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /consumer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.5.RELEASE 9 | 10 | 11 | com.sayedbaladoh.buzzdiggr 12 | consumer 13 | 0.0.1-SNAPSHOT 14 | consumer 15 | Consumer service to consume data from Kafka consumer, save it to Elastic search and provid APIs for searching from Elastic search 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-actuator 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-elasticsearch 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.kafka 36 | spring-kafka 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | org.springframework.kafka 46 | spring-kafka-test 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-maven-plugin 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/ConsumerApplication.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ConsumerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ConsumerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/config/KakfaConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.kafka.clients.consumer.ConsumerConfig; 7 | import org.apache.kafka.common.serialization.StringDeserializer; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.kafka.annotation.EnableKafka; 12 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 13 | import org.springframework.kafka.core.ConsumerFactory; 14 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 15 | import org.springframework.kafka.support.serializer.JsonDeserializer; 16 | 17 | import com.sayedbaladoh.buzzdiggr.consumer.model.Article; 18 | import com.sayedbaladoh.buzzdiggr.consumer.model.Tweet; 19 | 20 | @EnableKafka 21 | @Configuration 22 | public class KakfaConsumerConfig { 23 | 24 | @Value("${kafka.boot.server}") 25 | private String kafkaServer; 26 | 27 | @Value("${kafka.consumer.group.string.id}") 28 | private String kafkaGroupStrId; 29 | 30 | @Value("${kafka.consumer.group.json.id}") 31 | private String kafkaGroupJsonId; 32 | 33 | @Bean 34 | public ConsumerFactory consumerFactory() { 35 | Map config = new HashMap<>(); 36 | 37 | config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 38 | config.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaGroupStrId); 39 | config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 40 | config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 41 | 42 | return new DefaultKafkaConsumerFactory<>(config); 43 | } 44 | 45 | @Bean 46 | public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { 47 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory(); 48 | factory.setConsumerFactory(consumerFactory()); 49 | return factory; 50 | } 51 | 52 | @Bean 53 | public ConsumerFactory tweetConsumerFactory() { 54 | Map config = new HashMap<>(); 55 | 56 | config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 57 | config.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaGroupJsonId); 58 | config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 59 | config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); 60 | return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), 61 | new JsonDeserializer<>(Tweet.class, false)); 62 | } 63 | 64 | @Bean 65 | public ConcurrentKafkaListenerContainerFactory tweetKafkaListenerFactory() { 66 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 67 | factory.setConsumerFactory(tweetConsumerFactory()); 68 | return factory; 69 | } 70 | 71 | @Bean 72 | public ConsumerFactory articleConsumerFactory() { 73 | Map config = new HashMap<>(); 74 | 75 | config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 76 | config.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaGroupJsonId); 77 | config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 78 | config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); 79 | return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), 80 | new JsonDeserializer<>(Article.class, false)); 81 | } 82 | 83 | @Bean 84 | public ConcurrentKafkaListenerContainerFactory articleKafkaListenerFactory() { 85 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 86 | factory.setConsumerFactory(articleConsumerFactory()); 87 | return factory; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/controller/ArticleController.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import com.sayedbaladoh.buzzdiggr.consumer.model.Article; 19 | import com.sayedbaladoh.buzzdiggr.consumer.service.ArticleService; 20 | 21 | @RestController 22 | @CrossOrigin(origins = "${frontend.url}", allowedHeaders = "*") 23 | @RequestMapping("/api/articles") 24 | public class ArticleController { 25 | 26 | private final Logger LOG = LoggerFactory.getLogger(ArticleController.class); 27 | 28 | @Autowired 29 | private ArticleService articleService; 30 | 31 | // @PostMapping() 32 | // public Article addNewArticle(@RequestBody Article article) { 33 | // LOG.info("Adding article : {}", article); 34 | // articleService.add(article); 35 | // LOG.info("Added article : {}", article); 36 | // return article; 37 | // } 38 | 39 | @GetMapping("/all") 40 | public List
getAllArticle() { 41 | return articleService.getAll(); 42 | } 43 | 44 | @GetMapping() 45 | public Page
getAllArticle(Pageable pageable) { 46 | return articleService.getAll(pageable); 47 | } 48 | 49 | @GetMapping(params = { "q" }) 50 | public Page
search(@RequestParam(name = "q") String keyword, Pageable pageable) { 51 | return articleService.getByText(keyword, pageable); 52 | } 53 | 54 | @GetMapping(params = { "title" }) 55 | public List
searchByUserName(@RequestParam(name = "title") String title) { 56 | return articleService.getByTitle(title); 57 | } 58 | 59 | @DeleteMapping("/all") 60 | public ResponseEntity deleteBodyType() { 61 | articleService.removeAll(); 62 | return ResponseEntity.ok().build(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/controller/TweetController.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import com.sayedbaladoh.buzzdiggr.consumer.model.Tweet; 19 | import com.sayedbaladoh.buzzdiggr.consumer.service.TweetService; 20 | 21 | @RestController 22 | @CrossOrigin(origins = "${frontend.url}", allowedHeaders = "*") 23 | @RequestMapping("/api/tweets") 24 | public class TweetController { 25 | 26 | private final Logger LOG = LoggerFactory.getLogger(TweetController.class); 27 | 28 | @Autowired 29 | private TweetService tweetService; 30 | 31 | // @PostMapping() 32 | // public Tweet addNewTweet(@RequestBody Tweet tweet) { 33 | // LOG.info("Adding tweet : {}", tweet); 34 | // tweetService.add(tweet); 35 | // LOG.info("Added tweet : {}", tweet); 36 | // return tweet; 37 | // } 38 | 39 | @GetMapping("/all") 40 | public List getAllTweet() { 41 | return tweetService.getAll(); 42 | } 43 | 44 | @GetMapping() 45 | public Page getAllTweet(Pageable pageable) { 46 | return tweetService.getAll(pageable); 47 | } 48 | 49 | @GetMapping(params = { "q" }) 50 | public Page search(@RequestParam(name = "q") String keyword, Pageable pageable) { 51 | return tweetService.getByText(keyword, pageable); 52 | } 53 | 54 | @GetMapping(params = { "userName" }) 55 | public List searchByUserName(@RequestParam(name = "userName") String userName) { 56 | return tweetService.getByUserName(userName); 57 | } 58 | 59 | @GetMapping(params = { "language" }) 60 | public List searchByLanguage(@RequestParam(name = "language") String language) { 61 | return tweetService.getByLanguage(language); 62 | } 63 | 64 | @DeleteMapping("/all") 65 | public ResponseEntity deleteBodyType() { 66 | tweetService.removeAll(); 67 | return ResponseEntity.ok().build(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/model/Article.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.model; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.elasticsearch.annotations.Document; 5 | 6 | 7 | @Document(indexName = "crawler", type = "article") 8 | public class Article { 9 | 10 | @Id 11 | private String id; 12 | private String url; 13 | private String title; 14 | private String text; 15 | 16 | public Article(){ 17 | 18 | } 19 | 20 | public Article(String url, String title, String text) { 21 | super(); 22 | this.url = url; 23 | this.title = title; 24 | this.text = text; 25 | } 26 | 27 | public Article(String id, String url, String title, String text) { 28 | super(); 29 | this.id = id; 30 | this.url = url; 31 | this.title = title; 32 | this.text = text; 33 | } 34 | 35 | public String getId() { 36 | return id; 37 | } 38 | 39 | public void setId(String id) { 40 | this.id = id; 41 | } 42 | public String getUrl() { 43 | return url; 44 | } 45 | public void setUrl(String url) { 46 | this.url = url; 47 | } 48 | public String getTitle() { 49 | return title; 50 | } 51 | public void setTitle(String title) { 52 | this.title = title; 53 | } 54 | public String getText() { 55 | return text; 56 | } 57 | public void setText(String text) { 58 | this.text = text; 59 | } 60 | @Override 61 | public String toString() { 62 | return "Article [url=" + url + ", title=" + title + ", text=" + text + "]"; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/model/Tweet.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.model; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.elasticsearch.annotations.Document; 7 | 8 | 9 | @Document(indexName = "stream", type = "tweet") 10 | public class Tweet { 11 | 12 | @Id 13 | private String id; 14 | private String text; 15 | private Date date; 16 | private String language; 17 | private String userName; 18 | 19 | public Tweet(){ 20 | 21 | } 22 | 23 | public Tweet(String id, String text, Date date, String language, String userName) { 24 | super(); 25 | this.id = id; 26 | this.text = text; 27 | this.date = date; 28 | this.language = language; 29 | this.userName = userName; 30 | } 31 | 32 | public String getId() { 33 | return id; 34 | } 35 | 36 | public void setId(String id) { 37 | this.id = id; 38 | } 39 | 40 | public String getText() { 41 | return text; 42 | } 43 | 44 | public void setText(String text) { 45 | this.text = text; 46 | } 47 | 48 | public Date getDate() { 49 | return date; 50 | } 51 | 52 | public void setDate(Date date) { 53 | this.date = date; 54 | } 55 | 56 | public String getLanguage() { 57 | return language; 58 | } 59 | 60 | public void setLanguage(String language) { 61 | this.language = language; 62 | } 63 | 64 | public String getUserName() { 65 | return userName; 66 | } 67 | 68 | public void setUserName(String userName) { 69 | this.userName = userName; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "Tweet [id=" + id + ", text=" + text + ", date=" + date + ", language=" + language + ", userName=" 75 | + userName + "]"; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/repository/ArticleRepository.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import com.sayedbaladoh.buzzdiggr.consumer.model.Article; 11 | 12 | 13 | @Repository 14 | public interface ArticleRepository extends ElasticsearchRepository { 15 | 16 | Page
findByText(String text, Pageable pageable); 17 | 18 | List
findByTitle(String title); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/repository/TweetRepository.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import com.sayedbaladoh.buzzdiggr.consumer.model.Tweet; 11 | 12 | @Repository 13 | public interface TweetRepository extends ElasticsearchRepository { 14 | 15 | Page findByText(String text, Pageable pageable); 16 | 17 | List findByLanguage(String language); 18 | 19 | List findByUserName(String userName); 20 | } 21 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/service/ArticleService.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.service; 2 | 3 | import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.stereotype.Service; 16 | 17 | import com.sayedbaladoh.buzzdiggr.consumer.model.Article; 18 | import com.sayedbaladoh.buzzdiggr.consumer.repository.ArticleRepository; 19 | 20 | @Service 21 | public class ArticleService { 22 | 23 | @Autowired 24 | ArticleRepository articleRepository; 25 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(ArticleService.class); 27 | 28 | @Value("${elasticsearch.index.crawler}") 29 | private String indexName; 30 | 31 | @Value("${elasticsearch.type.article}") 32 | private String articleType; 33 | 34 | public void add(Article article) { 35 | if (article.getId() == null || article.getId().equals("")) 36 | article.setId(UUID.randomUUID().toString()); 37 | articleRepository.save(article); 38 | } 39 | 40 | public void add(List
articles) { 41 | articleRepository.saveAll(articles); 42 | } 43 | 44 | // public void addBulk(List
articles) { 45 | // try { 46 | // if (!template.indexExists(indexName)) { 47 | // template.createIndex(indexName); 48 | // } 49 | // ObjectMapper mapper = new ObjectMapper(); 50 | // List queries = new ArrayList<>(); 51 | // for (Article article : articles) { 52 | // IndexQuery indexQuery = new IndexQuery(); 53 | // indexQuery.setSource(mapper.writeValueAsString(article)); 54 | // indexQuery.setIndexName(indexName); 55 | // indexQuery.setType(articleType); 56 | // queries.add(indexQuery); 57 | // } 58 | // if (queries.size() > 0) { 59 | // template.bulkIndex(queries); 60 | // } 61 | // 62 | // template.refresh(indexName); 63 | // 64 | // LOGGER.info("BulkIndex completed: {}"); 65 | // } catch (Exception e) { 66 | // LOGGER.error("Error bulk index", e); 67 | // } 68 | // } 69 | 70 | public void removeAll() { 71 | articleRepository.deleteAll(); 72 | } 73 | 74 | public List
getAll() { 75 | List
articlesList = new ArrayList<>(); 76 | Iterable
articleses = articleRepository.findAll(); 77 | articleses.forEach(articlesList::add); 78 | return articlesList; 79 | } 80 | 81 | public Page
getAll(Pageable page) { 82 | return articleRepository.findAll(page); 83 | } 84 | 85 | // search term all fields 86 | public List
get(String query) { 87 | List
articlesList = new ArrayList<>(); 88 | Iterable
articleses = articleRepository.search(queryStringQuery(query)); 89 | articleses.forEach(articlesList::add); 90 | return articlesList; 91 | } 92 | 93 | public Page
getByText(String text, Pageable pageable) { 94 | return articleRepository.findByText(text, pageable); 95 | } 96 | 97 | public List
getByTitle(String title) { 98 | return articleRepository.findByTitle(title); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/service/KafkaReciever.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.sayedbaladoh.buzzdiggr.consumer.model.Article; 10 | import com.sayedbaladoh.buzzdiggr.consumer.model.Tweet; 11 | 12 | @Service 13 | public class KafkaReciever { 14 | 15 | private static final Logger LOGGER = LoggerFactory.getLogger(KafkaReciever.class); 16 | 17 | @Autowired 18 | TweetService tweetService; 19 | 20 | @Autowired 21 | ArticleService articleService; 22 | 23 | @KafkaListener(topics = "${kafka.topic.string.name}", groupId = "${kafka.consumer.group.string.id}") 24 | public void consume(String message) { 25 | LOGGER.info("Recieved data='{}' from kafka", message); 26 | } 27 | 28 | @KafkaListener(topics = "${kafka.topic.json.tweets}", groupId = "${kafka.consumer.group.json.id}", containerFactory = "tweetKafkaListenerFactory") 29 | public void consumeJson(Tweet tweet) { 30 | tweetService.add(tweet); 31 | LOGGER.info("Recieved data='{}' from kafka", tweet); 32 | } 33 | 34 | @KafkaListener(topics = "${kafka.topic.json.articles}", groupId = "${kafka.consumer.group.json.id}", containerFactory = "articleKafkaListenerFactory") 35 | public void consumeJson(Article article) { 36 | articleService.add(article); 37 | LOGGER.info("Recieved data='{}' from kafka", article); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /consumer/src/main/java/com/sayedbaladoh/buzzdiggr/consumer/service/TweetService.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer.service; 2 | 3 | import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.stereotype.Service; 16 | 17 | import com.sayedbaladoh.buzzdiggr.consumer.model.Tweet; 18 | import com.sayedbaladoh.buzzdiggr.consumer.repository.TweetRepository; 19 | 20 | @Service 21 | public class TweetService { 22 | 23 | @Autowired 24 | TweetRepository tweetRepository; 25 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(TweetService.class); 27 | 28 | @Value("${elasticsearch.index.stream}") 29 | private String indexName; 30 | 31 | @Value("${elasticsearch.type.tweet}") 32 | private String tweetType; 33 | 34 | public void add(Tweet tweet) { 35 | if (tweet.getId() == null || tweet.getId().equals("")) 36 | tweet.setId(UUID.randomUUID().toString()); 37 | tweetRepository.save(tweet); 38 | } 39 | 40 | public void add(List tweets) { 41 | tweetRepository.saveAll(tweets); 42 | } 43 | 44 | // public void addBulk(List tweets) { 45 | // try { 46 | // if (!template.indexExists(indexName)) { 47 | // template.createIndex(indexName); 48 | // } 49 | // ObjectMapper mapper = new ObjectMapper(); 50 | // List queries = new ArrayList<>(); 51 | // for (Tweet tweet : tweets) { 52 | // IndexQuery indexQuery = new IndexQuery(); 53 | // indexQuery.setSource(mapper.writeValueAsString(tweet)); 54 | // indexQuery.setIndexName(indexName); 55 | // indexQuery.setType(tweetType); 56 | // queries.add(indexQuery); 57 | // } 58 | // if (queries.size() > 0) { 59 | // template.bulkIndex(queries); 60 | // } 61 | // 62 | // template.refresh(indexName); 63 | // 64 | // LOGGER.info("BulkIndex completed: {}"); 65 | // } catch (Exception e) { 66 | // LOGGER.error("Error bulk index", e); 67 | // } 68 | // } 69 | 70 | public void removeAll() { 71 | tweetRepository.deleteAll(); 72 | } 73 | 74 | public List getAll() { 75 | List tweetsList = new ArrayList<>(); 76 | Iterable tweetses = tweetRepository.findAll(); 77 | tweetses.forEach(tweetsList::add); 78 | return tweetsList; 79 | } 80 | 81 | public Page getAll(Pageable page) { 82 | return tweetRepository.findAll(page); 83 | } 84 | 85 | // search term all fields 86 | public List get(String query) { 87 | List tweetsList = new ArrayList<>(); 88 | Iterable tweetses = tweetRepository.search(queryStringQuery(query)); 89 | tweetses.forEach(tweetsList::add); 90 | return tweetsList; 91 | } 92 | 93 | public Page getByText(String text, Pageable pageable) { 94 | return tweetRepository.findByText(text, pageable); 95 | } 96 | 97 | public List getByLanguage(String language) { 98 | return tweetRepository.findByLanguage(language); 99 | } 100 | 101 | public List getByUserName(String userName) { 102 | return tweetRepository.findByUserName(userName); 103 | } 104 | 105 | // @Autowired 106 | // private ElasticsearchTemplate elasticsearchTemplate; 107 | // 108 | // public List findAllByText(String text) { 109 | // MatchQueryBuilder queryBuilder = QueryBuilders.matchPhrasePrefixQuery("name", name); 110 | // 111 | // //You can query as many indices as you want 112 | // IndicesQueryBuilder builder = QueryBuilders.indicesQuery(queryBuilder, "stream", "crawler"); 113 | // 114 | // SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); 115 | // 116 | // return elasticsearchTemplate.query(searchQuery, response -> { 117 | // SearchHits hits = response.getHits(); 118 | // List result = new ArrayList<>(); 119 | // Arrays.stream(hits.getHits()).forEach(h -> { 120 | // Map source = h.getSource(); 121 | // //get only id just for test 122 | // Tweet tweet = new Tweet() 123 | // .setId(String.valueOf(source.getOrDefault("id", null))); 124 | // result.add(tweet); 125 | // }); 126 | // return result; 127 | // }); 128 | // } 129 | } 130 | -------------------------------------------------------------------------------- /consumer/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Server Properties 2 | spring.application.name=consumer-service 3 | server.port=8082 4 | server.servlet.context-path=/consumer 5 | 6 | # INFO Endpoint Configuration 7 | info.app.name=@project.name@ 8 | info.app.description=@project.description@ 9 | info.app.version=@project.version@ 10 | info.app.encoding=@project.build.sourceEncoding@ 11 | info.app.java.version=@java.version@ 12 | 13 | #URLs for allow CrossOrigin 14 | frontend.url = http://localhost:4200 15 | -------------------------------------------------------------------------------- /consumer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | kafka: 2 | boot: 3 | server: 127.0.0.1:9092 4 | consumer: 5 | group: 6 | string: 7 | id: group_str 8 | json: 9 | id: group_json 10 | topic: 11 | string: 12 | name: strings 13 | json: 14 | tweets: tweets 15 | articles: articles 16 | 17 | spring: 18 | data: 19 | elasticsearch: 20 | cluster-name: docker-cluster 21 | cluster-nodes: 127.0.0.1:9300 22 | elasticsearch: 23 | index: 24 | stream: stream 25 | crawler: crawler 26 | type: 27 | tweet: tweet 28 | article: article 29 | -------------------------------------------------------------------------------- /consumer/src/test/java/com/sayedbaladoh/buzzdiggr/consumer/ConsumerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.consumer; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ConsumerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | zookeeper: 4 | image: wurstmeister/zookeeper 5 | ports: 6 | - "2181:2181" 7 | #- "2888:2888" 8 | #- "3888:3888" 9 | 10 | kafka: 11 | image: wurstmeister/kafka 12 | links: 13 | - zookeeper:zk 14 | ports: 15 | - "9092:9092" 16 | environment: 17 | KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 18 | KAFKA_ADVERTISED_PORT: "9092" 19 | KAFKA_CREATE_TOPICS: "tweets:1:1,articles:1:1,strings:1:1" 20 | KAFKA_ZOOKEEPER_CONNECT: zk:2181 21 | volumes: 22 | - /var/run/docker.sock:/var/run/docker.sock 23 | depends_on: 24 | - zookeeper 25 | 26 | elasticsearch: 27 | image: docker.elastic.co/elasticsearch/elasticsearch:6.6.2 28 | container_name: elasticsearch 29 | environment: 30 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 31 | # - xpack.security.enabled=false 32 | - discovery.type=single-node 33 | # volumes: 34 | # - esdata1:/usr/share/elasticsearch/data 35 | ports: 36 | - 9200:9200 37 | - 9300:9300 38 | 39 | kibana: 40 | image: docker.elastic.co/kibana/kibana:6.6.2 41 | container_name: kibana 42 | environment: 43 | ELASTICSEARCH_URL: "http://elasticsearch:9200" 44 | ports: 45 | - 5601:5601 46 | depends_on: 47 | - elasticsearch 48 | 49 | volumes: 50 | esdata1: 51 | driver: local -------------------------------------------------------------------------------- /front-end/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /front-end/README.md: -------------------------------------------------------------------------------- 1 | # FrontendSearch 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /front-end/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "frontend-search": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/frontend-search", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": false, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | } 54 | ] 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular-devkit/build-angular:dev-server", 60 | "options": { 61 | "browserTarget": "frontend-search:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "frontend-search:build:production" 66 | } 67 | } 68 | }, 69 | "extract-i18n": { 70 | "builder": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "browserTarget": "frontend-search:build" 73 | } 74 | }, 75 | "test": { 76 | "builder": "@angular-devkit/build-angular:karma", 77 | "options": { 78 | "main": "src/test.ts", 79 | "polyfills": "src/polyfills.ts", 80 | "tsConfig": "tsconfig.spec.json", 81 | "karmaConfig": "karma.conf.js", 82 | "assets": [ 83 | "src/favicon.ico", 84 | "src/assets" 85 | ], 86 | "styles": [ 87 | "src/styles.css" 88 | ], 89 | "scripts": [] 90 | } 91 | }, 92 | "lint": { 93 | "builder": "@angular-devkit/build-angular:tslint", 94 | "options": { 95 | "tsConfig": [ 96 | "tsconfig.app.json", 97 | "tsconfig.spec.json", 98 | "e2e/tsconfig.json" 99 | ], 100 | "exclude": [ 101 | "**/node_modules/**" 102 | ] 103 | } 104 | }, 105 | "e2e": { 106 | "builder": "@angular-devkit/build-angular:protractor", 107 | "options": { 108 | "protractorConfig": "e2e/protractor.conf.js", 109 | "devServerTarget": "frontend-search:serve" 110 | }, 111 | "configurations": { 112 | "production": { 113 | "devServerTarget": "frontend-search:serve:production" 114 | } 115 | } 116 | } 117 | } 118 | }}, 119 | "defaultProject": "frontend-search" 120 | } -------------------------------------------------------------------------------- /front-end/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /front-end/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /front-end/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to frontend-search!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /front-end/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /front-end/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /front-end/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/frontend-search'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-search", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~8.0.1", 15 | "@angular/common": "~8.0.1", 16 | "@angular/compiler": "~8.0.1", 17 | "@angular/core": "~8.0.1", 18 | "@angular/forms": "~8.0.1", 19 | "@angular/platform-browser": "~8.0.1", 20 | "@angular/platform-browser-dynamic": "~8.0.1", 21 | "@angular/router": "~8.0.1", 22 | "lodash": "^4.17.19", 23 | "rxjs": "~6.4.0", 24 | "tslib": "^1.9.0", 25 | "zone.js": "~0.9.1" 26 | }, 27 | "devDependencies": { 28 | "@angular-devkit/build-angular": "~0.800.0", 29 | "@angular/cli": "~8.0.3", 30 | "@angular/compiler-cli": "~8.0.1", 31 | "@angular/language-service": "~8.0.1", 32 | "@types/node": "~8.9.4", 33 | "@types/jasmine": "~3.3.8", 34 | "@types/jasminewd2": "~2.0.3", 35 | "codelyzer": "^5.0.0", 36 | "jasmine-core": "~3.4.0", 37 | "jasmine-spec-reporter": "~4.2.1", 38 | "karma": "~4.1.0", 39 | "karma-chrome-launcher": "~2.2.0", 40 | "karma-coverage-istanbul-reporter": "~2.0.1", 41 | "karma-jasmine": "~2.0.1", 42 | "karma-jasmine-html-reporter": "^1.4.0", 43 | "protractor": "~5.4.0", 44 | "ts-node": "~7.0.0", 45 | "tslint": "~5.15.0", 46 | "typescript": "~3.4.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /front-end/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { SettingComponent } from './setting/setting.component'; 4 | import { SearchComponent } from './search/search.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', redirectTo: 'add', pathMatch: 'full' }, 8 | { path: 'config', component: SettingComponent }, 9 | { path: 'search', component: SearchComponent } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forRoot(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class AppRoutingModule { } 17 | -------------------------------------------------------------------------------- /front-end/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/app/app.component.css -------------------------------------------------------------------------------- /front-end/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 |
17 | 25 | 26 | 29 |
30 | 31 |
32 |
-------------------------------------------------------------------------------- /front-end/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'frontend-search'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('frontend-search'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to frontend-search!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /front-end/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'Full text Search'; 10 | description = 'Search for social media and web'; 11 | } 12 | -------------------------------------------------------------------------------- /front-end/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { AppComponent } from './app.component'; 7 | import { SearchComponent } from './search/search.component'; 8 | import { SearchService } from './search/search.service'; 9 | import { SettingComponent } from './setting/setting.component'; 10 | 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | SearchComponent, 16 | SettingComponent 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | ReactiveFormsModule, 21 | HttpClientModule, 22 | AppRoutingModule 23 | ], 24 | providers: [SearchService], 25 | bootstrap: [AppComponent] 26 | }) 27 | export class AppModule { } 28 | -------------------------------------------------------------------------------- /front-end/src/app/search/search.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/app/search/search.component.css -------------------------------------------------------------------------------- /front-end/src/app/search/search.component.html: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | 40 | 43 | 46 |
47 |
48 |
49 |
50 |
51 |

Not found!

52 |
53 |
54 |
55 |

{{results.totalElements}} Hits, {{results.totalPages}} Pages

56 |

Page: {{results.number+1}}, Displaying Results: ({{results.numberOfElements}})

57 |
58 | 59 |
60 | 61 | 62 |
63 |
64 |
65 | 66 |
67 |
68 |

69 | {{result.userName}} 70 |

71 |

{{result.text}}

72 |

73 | {{result.language | uppercase}} 74 |

75 |

76 | {{result.date | date:'long'}} 77 |

78 |
79 | 80 |
81 |

82 | {{result.title}} 83 |

84 |

85 | 86 | {{result.url}} 87 |

88 |

{{result.text}}

89 | 90 |
91 | 92 |
93 |

94 | {{result.title}} 95 |

96 |

{{result.text}}

97 | 98 |
99 |
100 |
101 | 102 |
-------------------------------------------------------------------------------- /front-end/src/app/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchComponent } from './search.component'; 4 | 5 | describe('SearchComponent', () => { 6 | let component: SearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | 4 | import { SearchService } from '../search/search.service'; 5 | import { pipe } from 'rxjs' 6 | import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; 7 | 8 | @Component({ 9 | selector: 'app-search', 10 | templateUrl: './search.component.html', 11 | styleUrls: ['./search.component.css'] 12 | }) 13 | export class SearchComponent implements OnInit { 14 | 15 | results: any[] = []; 16 | queryField: FormControl = new FormControl(); 17 | searchAt: string; 18 | constructor(private _apiService: SearchService) { 19 | this.searchAt = "tweets"; 20 | } 21 | 22 | ngOnInit() { 23 | 24 | this.queryField.valueChanges.pipe( 25 | debounceTime(200), 26 | distinctUntilChanged(), 27 | switchMap((query) => (this.searchAt == 'tweets') ? this._apiService.searchTweets(query) : (this.searchAt == 'web') ? this._apiService.searchArticles(query) : this._apiService.search(query) 28 | )).subscribe((result: any) => { this.results = result }); 29 | } 30 | 31 | onItemChange(item) { 32 | console.log("item: " + item) 33 | this.searchAt = item; 34 | let query = this.queryField.value; 35 | if ((this.searchAt == 'tweets')) 36 | this._apiService.searchTweets(query) 37 | .subscribe((result: any) => { this.results = result }); 38 | else if ((this.searchAt == 'web')) 39 | this._apiService.searchArticles(query) 40 | .subscribe((result: any) => { this.results = result }); 41 | else if ((this.searchAt == 'all')) 42 | this._apiService.search(query) 43 | .subscribe((result: any) => { this.results = result }); 44 | } 45 | 46 | /** Get next page of search results */ 47 | getResultsPage(pageNo) { 48 | let query = this.queryField.value; 49 | if ((this.searchAt == 'tweets')) 50 | this._apiService.searchTweetsPage(query, pageNo) 51 | .subscribe((result: any) => { this.results = result }); 52 | else if ((this.searchAt == 'web')) 53 | this._apiService.searchArticlesPage(query, pageNo) 54 | .subscribe((result: any) => { this.results = result }); 55 | else if ((this.searchAt == 'all')) 56 | this._apiService.searchPage(query, pageNo) 57 | .subscribe((result: any) => { this.results = result }); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /front-end/src/app/search/search.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchService } from './search.service'; 4 | 5 | describe('SearchService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: SearchService = TestBed.get(SearchService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /front-end/src/app/search/search.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class SearchService { 8 | root: string = 'http://localhost:8082'; 9 | baseSearchUrl: string = this.root + '/consumer/api/search?q='; 10 | baseTweetsUrl: string = this.root + '/consumer/api/tweets?q='; 11 | baseArticlesUrl: string = this.root + '/consumer/api/articles?q='; 12 | corsHeaders: HttpHeaders; 13 | 14 | constructor(private http: HttpClient) { 15 | this.corsHeaders = new HttpHeaders({ 16 | 'Content-Type': 'application/json', 17 | 'Accept': 'application/json', 18 | 'Access-Control-Allow-Origin': this.root 19 | }); 20 | } 21 | 22 | search(queryString: string) { 23 | let _URL = this.baseSearchUrl + queryString; 24 | return this.http.get(_URL, { headers: this.corsHeaders }); 25 | } 26 | 27 | searchPage(queryString: string, pageNo: number) { 28 | let _URL = this.baseSearchUrl + queryString + '&page=' + pageNo;; 29 | return this.http.get(_URL, { headers: this.corsHeaders }); 30 | } 31 | 32 | searchTweets(queryString: string) { 33 | let _URL = this.baseTweetsUrl + queryString; 34 | return this.http.get(_URL, { headers: this.corsHeaders }); 35 | } 36 | 37 | searchTweetsPage(queryString: string, pageNo: number) { 38 | let _URL = this.baseTweetsUrl + queryString + '&page=' + pageNo; 39 | return this.http.get(_URL, { headers: this.corsHeaders }); 40 | } 41 | 42 | searchArticles(queryString: string) { 43 | let _URL = this.baseArticlesUrl + queryString; 44 | return this.http.get(_URL, { headers: this.corsHeaders }); 45 | } 46 | 47 | searchArticlesPage(queryString: string, pageNo: number) { 48 | let _URL = this.baseArticlesUrl + queryString + '&page=' + pageNo;; 49 | return this.http.get(_URL, { headers: this.corsHeaders }); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /front-end/src/app/setting/setting.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/app/setting/setting.component.css -------------------------------------------------------------------------------- /front-end/src/app/setting/setting.component.html: -------------------------------------------------------------------------------- 1 |

2 | Settings 3 |

4 |
5 |

6 | Streamer Settings 7 |

8 |
9 | 10 | 11 |
12 | 13 |
14 |

Launch real-time tweets Streamer

15 |
16 |
17 | 18 |
19 | * Add multiple keywords seprated by , like: Egypt, مصر 20 |
21 | 22 |
23 | Total number of tweets, default: (100) 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 |

37 | Crawler Settings 38 |

39 |
40 | 41 | 42 |
43 |
44 |

Launch web crawler

45 |
46 |
47 | 48 |
49 | * 50 |
51 | 52 |
53 | * 54 |
55 | 56 |
57 | The limit for retrieving pages, Defult (20) 58 |
59 | 60 |
61 | The maximum pages to search, Default (100) 62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 |
71 | 72 |
-------------------------------------------------------------------------------- /front-end/src/app/setting/setting.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingComponent } from './setting.component'; 4 | 5 | describe('SettingComponent', () => { 6 | let component: SettingComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SettingComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SettingComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /front-end/src/app/setting/setting.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormControl, Validators } from '@angular/forms'; 3 | import { SettingService } from '../setting/setting.service'; 4 | 5 | @Component({ 6 | selector: 'app-setting', 7 | templateUrl: './setting.component.html', 8 | styleUrls: ['./setting.component.css'] 9 | }) 10 | export class SettingComponent implements OnInit { 11 | 12 | twitterForm: FormGroup; 13 | crawlerForm: FormGroup; 14 | constructor(private _settingService: SettingService) { } 15 | 16 | ngOnInit() { 17 | this.twitterForm = new FormGroup({ 18 | q: new FormControl('', Validators.required), 19 | count: new FormControl('') 20 | }); 21 | 22 | this.crawlerForm = new FormGroup({ 23 | url: new FormControl('http://', Validators.required), 24 | q: new FormControl('', Validators.required), 25 | count: new FormControl(''), 26 | maxPagesToSearch: new FormControl('') 27 | }); 28 | 29 | } 30 | isTweeterDone = true; 31 | onSubmitTwitterForm(form: FormGroup) { 32 | // console.log('Valid?', form.valid); // true or false 33 | // console.log('query', form.value.q); 34 | // console.log('Count', form.value.count); 35 | if (form.valid) { 36 | this.isTweeterDone = false; 37 | this._settingService.lanchTweetsStreamer(form.value.q, form.value.count) 38 | .subscribe((result: any) => { this.isTweeterDone = true }); 39 | } 40 | } 41 | 42 | isCrawlerDone = true; 43 | onSubmitCrawlerForm(form: FormGroup) { 44 | // console.log('Valid?', form.valid); // true or false 45 | // console.log('URL', form.value.url); 46 | // console.log('Query', form.value.q); 47 | // console.log('Count', form.value.count); 48 | // console.log('maxPagesToSearch', form.value.maxPagesToSearch); 49 | if (form.valid) { 50 | this.isCrawlerDone = false; 51 | this._settingService.lanchCrawlerStreamer(form.value.q, form.value.url, form.value.count, form.value.maxPagesToSearch) 52 | .subscribe((result: any) => { this.isCrawlerDone = true }); 53 | } 54 | } 55 | 56 | isDeleteingTweets = false; 57 | deleteTweets() { 58 | this.isDeleteingTweets = true; 59 | this._settingService.deleteAllTweets() 60 | .subscribe(() => { 61 | this.isDeleteingTweets = false; 62 | }); 63 | } 64 | 65 | isDeleteingArticles = false; 66 | deleteCrawler() { 67 | this.isDeleteingArticles = true; 68 | this._settingService.deleteAllArticles() 69 | .subscribe(() => { 70 | this.isDeleteingArticles = false; 71 | }); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /front-end/src/app/setting/setting.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingService } from './setting.service'; 4 | 5 | describe('SettingService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: SettingService = TestBed.get(SettingService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /front-end/src/app/setting/setting.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class SettingService { 8 | 9 | rootProducer: string = 'http://localhost:8081'; 10 | baseTwitterUrl: string = this.rootProducer + '/producer/api/stream/twitter?q='; 11 | baseCrawlerUrl: string = this.rootProducer + '/producer/api/stream/crawler?q='; 12 | 13 | rootConsumer: string = 'http://localhost:8082'; 14 | baseTweetsUrl: string = this.rootConsumer + '/consumer/api/tweets'; 15 | baseArticlesUrl: string = this.rootConsumer + '/consumer/api/articles'; 16 | 17 | corsProducerHeaders: HttpHeaders; 18 | corsConsumerHeaders: HttpHeaders; 19 | 20 | constructor(private http: HttpClient) { 21 | this.corsProducerHeaders = new HttpHeaders({ 22 | 'Content-Type': 'application/json', 23 | 'Accept': 'application/json', 24 | 'Access-Control-Allow-Origin': this.rootProducer 25 | }); 26 | 27 | this.corsConsumerHeaders = new HttpHeaders({ 28 | 'Content-Type': 'application/json', 29 | 'Accept': 'application/json', 30 | 'Access-Control-Allow-Origin': this.rootConsumer 31 | }); 32 | } 33 | 34 | lanchTweetsStreamer(query: string, count: number) { 35 | // console.log("count: " + count); 36 | let _URL = this.baseTwitterUrl + query + (count ? '&count=' + count : ''); 37 | // console.log("_URL: " + _URL); 38 | return this.http.get(_URL, { headers: this.corsProducerHeaders }); 39 | } 40 | 41 | lanchCrawlerStreamer(query: string, url: string, count: number, maxPagesToSearch: number) { 42 | // console.log("count: " + count); 43 | let _URL = this.baseCrawlerUrl + query + '&url=' + url + (count ? '&count=' + count : '') + (maxPagesToSearch ? '&maxPagesToSearch=' + maxPagesToSearch : ''); 44 | // console.log("_URL: " + _URL); 45 | return this.http.get(_URL, { headers: this.corsProducerHeaders }); 46 | } 47 | 48 | deleteAllTweets() { 49 | let _URL = this.baseTweetsUrl + '/all'; 50 | // console.log('deleteTweets'+ _URL) 51 | return this.http.delete(_URL, { headers: this.corsConsumerHeaders }); 52 | 53 | } 54 | 55 | deleteAllArticles() { 56 | let _URL = this.baseArticlesUrl + '/all'; 57 | // console.log('deleteAllArticles'+ _URL) 58 | return this.http.delete(_URL, { headers: this.corsConsumerHeaders }); 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /front-end/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/assets/.gitkeep -------------------------------------------------------------------------------- /front-end/src/assets/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/assets/spinner.gif -------------------------------------------------------------------------------- /front-end/src/assets/spinnerGrey.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/assets/spinnerGrey.gif -------------------------------------------------------------------------------- /front-end/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /front-end/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /front-end/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/front-end/src/favicon.ico -------------------------------------------------------------------------------- /front-end/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FrontendSearch 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /front-end/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /front-end/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /front-end/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /front-end/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /front-end/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /front-end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "importHelpers": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /front-end/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /front-end/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warn" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } -------------------------------------------------------------------------------- /images/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/images/Architecture.png -------------------------------------------------------------------------------- /images/FullTextSearchUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/images/FullTextSearchUI.png -------------------------------------------------------------------------------- /images/SearchFullTextSearchUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/images/SearchFullTextSearchUI.png -------------------------------------------------------------------------------- /images/SettingsFullTextSearchUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/images/SettingsFullTextSearchUI.png -------------------------------------------------------------------------------- /images/dockerComposeUpPs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/images/dockerComposeUpPs.png -------------------------------------------------------------------------------- /producer/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /producer/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /producer/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SayedBaladoh/Full-Text-Search-using-Docker-Kafka-Elasticsearch-Kibana-Java-Spring-Boot-HBC-Jsoup-Angular/2cc60ae89faf4ed15fccead7c7d776b2a7e29380/producer/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /producer/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /producer/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /producer/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /producer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.5.RELEASE 9 | 10 | 11 | com.sayedbaladoh.buzzdiggr 12 | producer 13 | 0.0.1-SNAPSHOT 14 | producer 15 | Producer service to collect, clean data collected from different sources, and send it to Kafka producer 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-actuator 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.kafka 32 | spring-kafka 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | org.springframework.kafka 42 | spring-kafka-test 43 | test 44 | 45 | 46 | 47 | com.twitter 48 | hbc-core 49 | 2.2.0 50 | 51 | 52 | org.json 53 | json 54 | 20180813 55 | 56 | 57 | 58 | org.jsoup 59 | jsoup 60 | 1.12.1 61 | 62 | 63 | 64 | io.springfox 65 | springfox-swagger2 66 | 2.8.0 67 | 68 | 69 | io.springfox 70 | springfox-swagger-ui 71 | 2.8.0 72 | 73 | 74 | com.google.guava 75 | guava 76 | 23.0 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-maven-plugin 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/ProducerApplication.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | 11 | import com.sayedbaladoh.buzzdiggr.producer.property.TwitterProperties; 12 | import com.sayedbaladoh.buzzdiggr.producer.service.crawler.Spider; 13 | import com.sayedbaladoh.buzzdiggr.producer.service.stream.TwitterStream; 14 | 15 | @EnableConfigurationProperties(TwitterProperties.class) 16 | @SpringBootApplication 17 | public class ProducerApplication implements CommandLineRunner { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ProducerApplication.class); 20 | 21 | @Autowired 22 | private TwitterStream twitterStreamingService; 23 | 24 | @Autowired 25 | private Spider spiderService; 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(ProducerApplication.class, args); 29 | } 30 | 31 | // Test stream Twitter tweets and web Crawler 32 | @Override 33 | public void run(String... strings) throws Exception { 34 | // logger.info("Running Twitter Streaming ..."); 35 | // twitterStreamingService.stream(10, "twitterapi", "محمد صلاح", "#Sports"); 36 | // logger.info("Running Crawller Streaming ..."); 37 | // spiderService.crawl("https://www.shorouknews.com/", "محمد", 15, 50); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/config/KakfaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.config; 2 | 3 | import com.sayedbaladoh.buzzdiggr.producer.model.Tweet; 4 | import com.sayedbaladoh.buzzdiggr.producer.model.Article; 5 | import org.apache.kafka.clients.producer.ProducerConfig; 6 | import org.apache.kafka.common.serialization.StringSerializer; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 11 | import org.springframework.kafka.core.KafkaTemplate; 12 | import org.springframework.kafka.core.ProducerFactory; 13 | import org.springframework.kafka.support.serializer.JsonSerializer; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * Kakfa Producer Configuration 20 | * 21 | * @author SayedBaladoh 22 | * 23 | */ 24 | @Configuration 25 | public class KakfaProducerConfig { 26 | 27 | @Value("${kafka.boot.server}") 28 | private String kafkaServer; 29 | 30 | // String Producer Factory 31 | @Bean 32 | public ProducerFactory producerFactory() { 33 | 34 | Map config = new HashMap<>(); 35 | 36 | config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 37 | config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 38 | config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 39 | 40 | return new DefaultKafkaProducerFactory<>(config); 41 | } 42 | 43 | // String Kafka Template 44 | @Bean 45 | public KafkaTemplate kafkaTemplate() { 46 | return new KafkaTemplate<>(producerFactory()); 47 | } 48 | 49 | 50 | // Tweet Producer Factory 51 | @Bean 52 | public ProducerFactory tweetProducerFactory() { 53 | 54 | Map config = new HashMap<>(); 55 | 56 | config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 57 | config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 58 | config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 59 | 60 | return new DefaultKafkaProducerFactory<>(config); 61 | } 62 | 63 | // Tweet Kafka Template 64 | @Bean 65 | public KafkaTemplate tweetKafkaTemplate() { 66 | return new KafkaTemplate<>(tweetProducerFactory()); 67 | } 68 | 69 | // Article Producer Factory 70 | @Bean 71 | public ProducerFactory articleProducerFactory() { 72 | 73 | Map config = new HashMap<>(); 74 | 75 | config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 76 | config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 77 | config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 78 | 79 | return new DefaultKafkaProducerFactory<>(config); 80 | } 81 | 82 | // Article Kafka Template 83 | @Bean 84 | public KafkaTemplate articleKafkaTemplate() { 85 | return new KafkaTemplate<>(articleProducerFactory()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.Contact; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | @Configuration 15 | @EnableSwagger2 16 | public class SwaggerConfig { 17 | @Bean 18 | public Docket Api() { 19 | return new Docket(DocumentationType.SWAGGER_2) 20 | .select().apis(RequestHandlerSelectors.basePackage("com.sayedbaladoh.buzzdiggr.producer.controller")) 21 | // .paths(regex("/api.*")) 22 | .build() 23 | .apiInfo(metaData()); 24 | } 25 | 26 | private ApiInfo metaData() { 27 | return new ApiInfoBuilder() 28 | .title("Producer Service API") 29 | .description("Producer service to collect, clean data collected from different sources, and send it to Kafka producer") 30 | .version("1.0.0") 31 | // .license("Apache License Version 2.0") 32 | // .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0") 33 | .contact( 34 | new Contact("Sayed Baladoh", "https://www.linkedin.com/in/sayed-baladoh-227aa66b/", "sayedbaladoh@yahoo.com")) 35 | .build(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/controller/StreamController.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.CrossOrigin; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import com.sayedbaladoh.buzzdiggr.producer.service.crawler.Spider; 15 | import com.sayedbaladoh.buzzdiggr.producer.service.stream.TwitterStream; 16 | 17 | import io.swagger.annotations.Api; 18 | import io.swagger.annotations.ApiOperation; 19 | 20 | /** 21 | * 22 | * Initialize and instantiate Streaming and Crawling API 23 | * 24 | * @author SayedBaladoh 25 | * 26 | */ 27 | @RestController 28 | @CrossOrigin(origins = "${frontend.url}") 29 | @RequestMapping("/api/stream") 30 | @Api(value = "Stream", description = "Initialize and instantiate Streaming and Crowling API") 31 | public class StreamController { 32 | 33 | private static final Logger LOGGER = LoggerFactory.getLogger(StreamController.class); 34 | 35 | @Autowired 36 | private TwitterStream twitterStreamingService; 37 | 38 | @Autowired 39 | private Spider spiderService; 40 | 41 | /** 42 | * stream 43 | * 44 | * Start retrieve real-time Tweets 45 | * 46 | * @param count 47 | * - The total number of tweets to stream. Default (100). 48 | * @param q 49 | * - List of search keywords 50 | * 51 | * @return 52 | */ 53 | @ApiOperation(value = "Start retrieve real-time Tweets") 54 | @GetMapping(value = "/twitter", params = { "q", "count" }) 55 | public ResponseEntity stream(@RequestParam(name = "count") int numberOfTweets, 56 | @RequestParam(name = "q") String[] keywords) { 57 | try { 58 | twitterStreamingService.stream(numberOfTweets, keywords); 59 | } catch (InterruptedException e) { 60 | // e.printStackTrace(); 61 | LOGGER.error(e.getMessage()); 62 | return ResponseEntity.badRequest().build(); 63 | } 64 | return ResponseEntity.ok().build(); 65 | } 66 | 67 | /** 68 | * stream 69 | * 70 | * Start retrieve real-time Tweets with default total number of tweets (100) 71 | * 72 | * @param q 73 | * - List of search keywords 74 | * 75 | * @return 76 | */ 77 | @ApiOperation(value = "Start retrieve real-time Tweets with default total number of tweets (100)") 78 | @GetMapping(value = "/twitter", params = { "q" }) 79 | public ResponseEntity stream(@RequestParam(name = "q") String[] keywords) { 80 | try { 81 | twitterStreamingService.stream(keywords); 82 | } catch (InterruptedException e) { 83 | LOGGER.error(e.getMessage()); 84 | return ResponseEntity.badRequest().build(); 85 | } 86 | return ResponseEntity.ok().build(); 87 | } 88 | 89 | /** 90 | * crawl 91 | * 92 | * launch web crawler 93 | * 94 | * @param url 95 | * - The starting point of the crawler 96 | * @param q 97 | * - The keyword or string that you are searching for 98 | * @param count 99 | * - The limit for retrieving pages 100 | * @param maxPagesToSearch 101 | * - The maximum pages to search 102 | */ 103 | @ApiOperation(value = "launch web crawler") 104 | @GetMapping(value = "/crawler", params = { "url", "q", "count", "maxPagesToSearch" }) 105 | public ResponseEntity crawl(@RequestParam(name = "url") String url, @RequestParam(name = "q") String keyword, 106 | @RequestParam(name = "count") int pagesLimit, 107 | @RequestParam(name = "maxPagesToSearch") int maxPagesToSearch) { 108 | spiderService.crawl(url, keyword, pagesLimit, maxPagesToSearch); 109 | return ResponseEntity.ok().build(); 110 | } 111 | 112 | /** 113 | * crawl 114 | * 115 | * launch web crawler 116 | * 117 | * @param url 118 | * - The starting point of the crawler 119 | * @param q 120 | * - The keyword or string that you are searching for 121 | * @param count 122 | * - The limit for retrieving pages 123 | */ 124 | @ApiOperation(value = "launch web crawler") 125 | @GetMapping(value = "/crawler", params = { "url", "q", "count" }) 126 | public ResponseEntity crawl(@RequestParam(name = "url") String url, @RequestParam(name = "q") String keyword, 127 | @RequestParam(name = "count") int pagesLimit) { 128 | spiderService.crawl(url, keyword, pagesLimit); 129 | return ResponseEntity.ok().build(); 130 | } 131 | 132 | /** 133 | * crawl 134 | * 135 | * launch web crawler 136 | * 137 | * @param url 138 | * - The starting point of the crawler 139 | * @param q 140 | * - The keyword or string that you are searching for 141 | * @param maxPagesToSearch 142 | * - The maximum pages to search 143 | */ 144 | @ApiOperation(value = "launch web crawler") 145 | @GetMapping(value = "/crawler", params = { "url", "q", "maxPagesToSearch" }) 146 | public ResponseEntity crawlByMax(@RequestParam(name = "url") String url, 147 | @RequestParam(name = "q") String keyword, @RequestParam(name = "maxPagesToSearch") int maxPagesToSearch) { 148 | spiderService.crawlByMax(url, keyword, maxPagesToSearch); 149 | return ResponseEntity.ok().build(); 150 | } 151 | 152 | /** 153 | * crawl 154 | * 155 | * launch web crawler 156 | * 157 | * @param url 158 | * - The starting point of the crawler 159 | * @param q 160 | * - The keyword or string that you are searching for 161 | */ 162 | @ApiOperation(value = "launch web crawler") 163 | @GetMapping(value = "/crawler", params = { "url", "q" }) 164 | public ResponseEntity crawl(@RequestParam(name = "url") String url, @RequestParam(name = "q") String keyword) { 165 | spiderService.crawl(url, keyword); 166 | return ResponseEntity.ok().build(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/model/Article.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.model; 2 | 3 | public class Article { 4 | 5 | private String url; 6 | private String title; 7 | private String text; 8 | 9 | public Article(){ 10 | 11 | } 12 | 13 | public Article(String url, String title, String text) { 14 | super(); 15 | this.url = url; 16 | this.title = title; 17 | this.text = text; 18 | } 19 | 20 | public String getUrl() { 21 | return url; 22 | } 23 | public void setUrl(String url) { 24 | this.url = url; 25 | } 26 | public String getTitle() { 27 | return title; 28 | } 29 | public void setTitle(String title) { 30 | this.title = title; 31 | } 32 | public String getText() { 33 | return text; 34 | } 35 | public void setText(String text) { 36 | this.text = text; 37 | } 38 | @Override 39 | public String toString() { 40 | return "Article [url=" + url + ", title=" + title + ", text=" + text + "]"; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/model/Tweet.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.model; 2 | 3 | import java.util.Date; 4 | 5 | public class Tweet { 6 | 7 | private String id; 8 | private String text; 9 | private Date date; 10 | private String language; 11 | private String userName; 12 | 13 | public String getId() { 14 | return id; 15 | } 16 | 17 | public void setId(String id) { 18 | this.id = id; 19 | } 20 | 21 | public String getText() { 22 | return text; 23 | } 24 | 25 | public void setText(String text) { 26 | this.text = text; 27 | } 28 | 29 | public Date getDate() { 30 | return date; 31 | } 32 | 33 | public void setDate(Date date) { 34 | this.date = date; 35 | } 36 | 37 | public String getLanguage() { 38 | return language; 39 | } 40 | 41 | public void setLanguage(String language) { 42 | this.language = language; 43 | } 44 | 45 | public String getUserName() { 46 | return userName; 47 | } 48 | 49 | public void setUserName(String userName) { 50 | this.userName = userName; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Tweet [id=" + id + ", text=" + text + ", date=" + date + ", language=" + language + ", userName=" 56 | + userName + "]"; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/property/TwitterProperties.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * Twitter Properties specific to Twitter Social properties. 7 | * 8 | *

9 | * Properties are configured in the application.properties file. 10 | * Bind all the file Twitter properties to a POJO class 11 | *

12 | * 13 | * @author SayedBaladoh 14 | */ 15 | @ConfigurationProperties(prefix = "social.twitter.auth", ignoreUnknownFields = false) 16 | public class TwitterProperties { 17 | 18 | private String apiKey; 19 | private String apiSecretKey; 20 | private String accessToken; 21 | private String accessTokenSecret; 22 | public String getApiKey() { 23 | return apiKey; 24 | } 25 | public void setApiKey(String apiKey) { 26 | this.apiKey = apiKey; 27 | } 28 | public String getApiSecretKey() { 29 | return apiSecretKey; 30 | } 31 | public void setApiSecretKey(String apiSecretKey) { 32 | this.apiSecretKey = apiSecretKey; 33 | } 34 | public String getAccessToken() { 35 | return accessToken; 36 | } 37 | public void setAccessToken(String accessToken) { 38 | this.accessToken = accessToken; 39 | } 40 | public String getAccessTokenSecret() { 41 | return accessTokenSecret; 42 | } 43 | public void setAccessTokenSecret(String accessTokenSecret) { 44 | this.accessTokenSecret = accessTokenSecret; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/service/crawler/Spider.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.service.crawler; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | import org.jsoup.Connection; 9 | import org.jsoup.Jsoup; 10 | import org.jsoup.nodes.Document; 11 | import org.jsoup.nodes.Element; 12 | import org.jsoup.select.Elements; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.stereotype.Service; 18 | 19 | import com.sayedbaladoh.buzzdiggr.producer.model.Article; 20 | import com.sayedbaladoh.buzzdiggr.producer.service.kafka.Sender; 21 | 22 | 23 | /** 24 | * Simple web crawler 25 | * 26 | * @author SayedBaladoh 27 | * 28 | * Adapted from: 29 | * @see How to make a simple web crawler in Java - 30 | * http://www.netinstructions.com/how-to-make-a-simple-web-crawler-in-java/ 31 | * @see Simple web crawler - 32 | * https://www.programcreek.com/java-api-examples/?code=PacktPublishing/Machine-Learning-End-to-Endguide-for-Java-developers/Machine-Learning-End-to-Endguide-for-Java-developers-master/Module%201/JavaforDataScience_Code/chapter02/SimpleWebCrawler.java 33 | * 34 | */ 35 | @Service 36 | public class Spider { 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(Spider.class); 39 | 40 | @Autowired 41 | private Sender sender; 42 | 43 | @Value("${kafka.topic.json.article}") 44 | private String articleTopicName; 45 | 46 | @Value("${crawler.maxPagesToSearch}") 47 | private int maxPagesToSearch; 48 | @Value("${crawler.pagesLimit}") 49 | private int pagesLimit; 50 | 51 | private String startingURL; 52 | private Set visitedPages; 53 | private List pageList; 54 | // Use a fake USER_AGENT so the web server thinks the robot is a normal web browser. 55 | private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1"; 56 | 57 | /** 58 | * Main launching point for the Spider's functionality with default maxPagesToSearch and pagesLimit. 59 | * 60 | * @param url 61 | * - The starting point of the spider 62 | * @param searchTopic 63 | * - The word or string that you are searching for 64 | */ 65 | public void crawl(String url, String topic) { 66 | 67 | visitedPages = new HashSet(); 68 | pageList = new ArrayList(); 69 | 70 | this.startingURL = url; 71 | this.search(url, topic); 72 | 73 | LOGGER.info("**Crawling Done** Visited:'{}' web page(s), Found:'{}' web page(s).", this.visitedPages.size(), this.pageList.size()); 74 | } 75 | 76 | /** 77 | * Overload the main launching point for the Spider's functionality with default maxPagesToSearch. 78 | * 79 | * @param url 80 | * - The starting point of the spider 81 | * @param searchTopic 82 | * - The word or string that you are searching for 83 | * @param pagesLimit 84 | * - The limit for retrieving pages 85 | */ 86 | public void crawl(String url, String topic, int pagesLimit) { 87 | 88 | visitedPages = new HashSet(); 89 | pageList = new ArrayList(); 90 | 91 | this.startingURL = url; 92 | if (pagesLimit > 0) 93 | this.pagesLimit = pagesLimit; 94 | 95 | this.search(url, topic); 96 | 97 | LOGGER.info("**Crawling Done** Visited:'{}' web page(s), Found:'{}' web page(s).", this.visitedPages.size(), this.pageList.size()); 98 | } 99 | 100 | /** 101 | * Overload the launching point for the Spider's functionality with default pagesLimit. 102 | * 103 | * @param url 104 | * - The starting point of the spider 105 | * @param searchTopic 106 | * - The word or string that you are searching for 107 | * @param maxPagesToSearch 108 | * - The maximum pages to search 109 | */ 110 | public void crawlByMax(String url, String topic, int maxPagesToSearch) { 111 | 112 | visitedPages = new HashSet(); 113 | pageList = new ArrayList(); 114 | 115 | this.startingURL = url; 116 | if (maxPagesToSearch > 0) 117 | this.maxPagesToSearch = maxPagesToSearch; 118 | 119 | this.search(url, topic); 120 | 121 | LOGGER.info("**Crawling Done** Visited:'{}' web page(s), Found:'{}' web page(s).", this.visitedPages.size(), this.pageList.size()); 122 | } 123 | 124 | /** 125 | * Overload the main launching point for the Spider's functionality. 126 | * 127 | * @param url 128 | * - The starting point of the spider 129 | * @param searchTopic 130 | * - The word or string that you are searching for 131 | * @param pagesLimit 132 | * - The limit for retrieving pages 133 | * @param maxPagesToSearch 134 | * - The maxmum pages to search 135 | */ 136 | public void crawl(String url, String topic, int pagesLimit, int maxPagesToSearch) { 137 | 138 | visitedPages = new HashSet(); 139 | pageList = new ArrayList(); 140 | 141 | this.startingURL = url; 142 | if (pagesLimit > 0) 143 | this.pagesLimit = pagesLimit; 144 | if (maxPagesToSearch > 0) 145 | this.maxPagesToSearch = maxPagesToSearch; 146 | 147 | this.search(url, topic); 148 | 149 | LOGGER.info("**Crawling Done** Visited:'{}' web page(s), Found:'{}' web page(s).", this.visitedPages.size(), this.pageList.size()); 150 | } 151 | 152 | public void search(String url, String topic) { 153 | 154 | this.visitedPages.add(url); 155 | try { 156 | Connection connection = Jsoup.connect(url).userAgent(USER_AGENT); 157 | Document htmlDocument = connection.get(); 158 | // 200 is the HTTP OK status code indicating that everything 159 | // is great. 160 | // if (connection.response().statusCode() == 200) { 161 | // System.out.println("\n**Visiting** Received web page at " + url); 162 | // } 163 | if (!connection.response().contentType().contains("text/html")) { 164 | System.out.println("**Failure** Retrieved something other than HTML"); 165 | return; 166 | } 167 | 168 | String text = htmlDocument.body().text(); 169 | if (text.toLowerCase().contains(topic.toLowerCase())) { 170 | String title = htmlDocument.title(); 171 | 172 | LOGGER.info("**Success search** Topic:'{}' is existed at:'{}' with title:'{}'.", topic, url, title); 173 | this.pageList.add(url); 174 | 175 | Article article = new Article(url, title, text); 176 | LOGGER.info("Article: {} ", article); 177 | 178 | //Send article to Kafka 179 | sender.send(articleTopicName, article); 180 | 181 | } 182 | 183 | // Process page links 184 | Elements linksOnPage = htmlDocument.select("a[href]"); 185 | for (Element link : linksOnPage) { 186 | String absUrl = link.absUrl("href"); 187 | // Check URL is not already visited, search in the same domain, pages limit and max pages to search 188 | if (absUrl.contains(this.startingURL) && !this.visitedPages.contains(absUrl) 189 | && this.pageList.size() < this.pagesLimit && this.visitedPages.size() < this.maxPagesToSearch) { 190 | search(absUrl, topic); 191 | } 192 | } 193 | 194 | } catch (Exception ex) { 195 | // We were not successful in our HTTP request 196 | LOGGER.error(url, " must supply a valid URL"); 197 | } 198 | } 199 | 200 | // /** 201 | // * Test Case. 202 | // * 203 | // * @param args 204 | // * - not used 205 | // */ 206 | // public static void main(String[] args) { 207 | // Spider spider = new Spider(); 208 | //// spider.crawl("https://www.shorouknews.com/", "Mohammad", 15); 209 | // spider.crawl("https://www.shorouknews.com/", "محمد", 15, 50); 210 | // } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/service/kafka/Sender.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.service.kafka; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.kafka.core.KafkaTemplate; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.sayedbaladoh.buzzdiggr.producer.model.Article; 10 | import com.sayedbaladoh.buzzdiggr.producer.model.Tweet; 11 | 12 | /** 13 | * Kakfa Sender Service 14 | * 15 | * @author SayedBaladoh 16 | * 17 | */ 18 | @Service 19 | public class Sender { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(Sender.class); 22 | 23 | @Autowired 24 | private KafkaTemplate kafkaTemplate; 25 | 26 | @Autowired 27 | private KafkaTemplate tweetKafkaTemplate; 28 | 29 | @Autowired 30 | private KafkaTemplate articleKafkaTemplate; 31 | 32 | // Send String data 33 | public void send(String topic, String data) { 34 | 35 | LOGGER.info("sending data='{}' to topic='{}'", data, topic); 36 | kafkaTemplate.send(topic, data); 37 | } 38 | 39 | // Send Tweet object 40 | public void send(String topic, Tweet tweet) { 41 | 42 | LOGGER.info("sending data='{}' to kafka topic='{}'", tweet, topic); 43 | tweetKafkaTemplate.send(topic, tweet); 44 | 45 | } 46 | 47 | // Send Article object 48 | public void send(String topic, Article article) { 49 | 50 | LOGGER.info("sending data='{}' to kafka topic='{}'", article, topic); 51 | articleKafkaTemplate.send(topic, article); 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/service/stream/TweetHandler.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.service.stream; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | import org.springframework.stereotype.Service; 10 | 11 | import com.sayedbaladoh.buzzdiggr.producer.model.Tweet; 12 | import com.sayedbaladoh.buzzdiggr.producer.util.StringUtils; 13 | 14 | /** 15 | * Tweet Handler 16 | * 17 | * @author SayedBaladoh 18 | * 19 | */ 20 | @Service 21 | public class TweetHandler { 22 | 23 | /** 24 | * Parse Tweet from String to Object 25 | * 26 | * @param jsonText 27 | * - Json as a string 28 | * @return 29 | * - Parsed Tweet object 30 | */ 31 | public Tweet parseTweet(String jsonText) { 32 | 33 | Tweet tweet = new Tweet(); 34 | try { 35 | JSONObject jsonObject = new JSONObject(jsonText); 36 | 37 | tweet.setId(jsonObject.getString("id_str")); 38 | String cleanedtext = StringUtils.cleanText(jsonObject.getString("text")); 39 | tweet.setText(cleanedtext); 40 | 41 | // "EEE MMM d HH:mm:ss Z yyyy" 42 | SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss Z yyyy"); 43 | try { 44 | Date date = sdf.parse(jsonObject.getString("created_at")); 45 | tweet.setDate(date); 46 | } catch (ParseException ex) { 47 | ex.printStackTrace(); 48 | } 49 | 50 | tweet.setLanguage(jsonObject.getString("lang")); 51 | 52 | JSONObject user = jsonObject.getJSONObject("user"); 53 | tweet.setUserName(user.getString("name")); 54 | 55 | // this.place = jsonObject.getString("place"); 56 | // System.out.println("Text: " + jsonObject.getString("text")); 57 | // System.out.println("Created_at: " + 58 | // jsonObject.getString("created_at")); 59 | // System.out.println("lang: " + jsonObject.getString("lang")); 60 | // System.out.println("id_str: " + jsonObject.getString("id_str")); 61 | // System.out.println("place: " + jsonObject.getString("place")); 62 | // System.out.println("user name: " + user.getString("name")); 63 | // System.out.println("user profile_image_url: " + 64 | // user.getString("profile_image_url")); 65 | // System.out.println(); 66 | 67 | } catch (JSONException ex) { 68 | ex.printStackTrace(); 69 | } 70 | 71 | return tweet; 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/service/stream/TwitterStream.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.service.stream; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Service; 11 | 12 | import com.google.common.collect.Lists; 13 | import com.sayedbaladoh.buzzdiggr.producer.model.Tweet; 14 | import com.sayedbaladoh.buzzdiggr.producer.property.TwitterProperties; 15 | import com.sayedbaladoh.buzzdiggr.producer.service.kafka.Sender; 16 | import com.twitter.hbc.ClientBuilder; 17 | import com.twitter.hbc.core.Client; 18 | import com.twitter.hbc.core.Constants; 19 | import com.twitter.hbc.core.endpoint.StatusesFilterEndpoint; 20 | import com.twitter.hbc.core.processor.StringDelimitedProcessor; 21 | import com.twitter.hbc.httpclient.auth.Authentication; 22 | import com.twitter.hbc.httpclient.auth.OAuth1; 23 | 24 | /** 25 | * Twitter Streaming Service 26 | * 27 | * @author SayedBaladoh 28 | * 29 | */ 30 | @Service 31 | public class TwitterStream { 32 | 33 | private static final Logger LOGGER = LoggerFactory.getLogger(TwitterStream.class); 34 | 35 | @Autowired 36 | TwitterProperties twitterProperties; 37 | 38 | @Autowired 39 | TweetHandler tweetHandler; 40 | 41 | @Autowired 42 | private Sender sender; 43 | 44 | @Value("${kafka.topic.json.tweet}") 45 | private String tweetTopicName; 46 | 47 | @Value("${social.twitter.count}") 48 | private int count; 49 | 50 | /** 51 | * Stream tweets from twitter with default (100) total number of tweets to 52 | * stream 53 | * @param topics 54 | * - List of topics to search 55 | * @throws InterruptedException 56 | */ 57 | public void stream(String... topics) throws InterruptedException { 58 | this.stream(this.count, topics); 59 | } 60 | 61 | /** 62 | * Stream tweets from twitter 63 | * 64 | * @param numberOfTweets 65 | * - Number of tweets 66 | * @param topics 67 | * - List of topics to search 68 | * @throws InterruptedException 69 | */ 70 | public void stream(int numberOfTweets, String... topics) throws InterruptedException { 71 | 72 | // Creating Twitter Stream 73 | BlockingQueue queue = new LinkedBlockingQueue(10000); 74 | 75 | StatusesFilterEndpoint endpoint = new StatusesFilterEndpoint(); 76 | 77 | // Add some track terms 78 | endpoint.trackTerms(Lists.newArrayList(topics)); 79 | 80 | Authentication auth = new OAuth1(twitterProperties.getApiKey(), twitterProperties.getApiSecretKey(), 81 | twitterProperties.getAccessToken(), twitterProperties.getAccessTokenSecret()); 82 | 83 | // Create a new BasicClient. By default gzip is enabled. 84 | Client client = new ClientBuilder().hosts(Constants.STREAM_HOST).endpoint(endpoint).authentication(auth) 85 | .processor(new StringDelimitedProcessor(queue)).build(); 86 | 87 | // Establish a connection 88 | client.connect(); 89 | 90 | // Parse and process tweets 91 | for (int msgRead = 0; msgRead < numberOfTweets; msgRead++) { 92 | String msg = queue.take(); 93 | 94 | // LOGGER.info("Message: {} ", msg); 95 | 96 | // Parse and Clean Msg 97 | if (msg != null) { 98 | Tweet tweet = tweetHandler.parseTweet(msg); 99 | // LOGGER.info("Text: {} ", tweet.getText()); 100 | LOGGER.info("Tweet: {} ", tweet); 101 | 102 | // Send tweet to Kafka 103 | sender.send(tweetTopicName, tweet); 104 | } 105 | 106 | } 107 | 108 | client.stop(); 109 | 110 | LOGGER.info("'{}' messages processed!\n", client.getStatsTracker().getNumMessages()); 111 | 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /producer/src/main/java/com/sayedbaladoh/buzzdiggr/producer/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer.util; 2 | 3 | public class StringUtils { 4 | 5 | // ToDo : Remove Stop Words, Grammar checking, Spelling correction 6 | /** 7 | * Simple Text Cleaner 8 | * 9 | * @param text 10 | * - Text to clean 11 | * @return 12 | * - The cleaned text 13 | * 14 | * @author SayedBaladoh 15 | */ 16 | public static String cleanText(String text) { 17 | 18 | if (text == null && text.equals("")) 19 | return ""; 20 | // System.out.println("Dirty text: " + text); 21 | // Delete all usernames mentioned (?:\\s|\\A)@+([A-Za-z0-9-_]+) 22 | // Delete all RT (retweets flags) (RT) 23 | // Delete all hashtags mentioned (?:\\s|\\A)#+([A-Za-z0-9-_]+) 24 | // Delete all URLs (https?://(\\w+\\.)+\\S*) 25 | // Delete all no printable characters and Emojis ([\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]+) 26 | // Replace all break lines with spaces (\n) 27 | // Replace all double spaces with single spaces " {2,}" 28 | // Remove leading/trailing spaces trim() 29 | 30 | text = text.replaceAll("((?:\\s|\\A)@[A-Za-z0-9-_]+)|(RT)|((?:\\s|\\A)#+([A-Za-z0-9-_]+))|(https?://(\\w+\\.)+\\S*)|([\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]+)", "").replaceAll("\n", " ").replaceAll(" {2,}", " ").trim(); 31 | // System.out.println("Cleaned text: " + text); 32 | return text; 33 | } 34 | 35 | /** 36 | * Test Case. 37 | * 38 | * @param args 39 | * - not used 40 | */ 41 | public static void main(String[] args) { 42 | String text = "öäü L'alphabet est génial 😀! I luv صراع الكرة الذهبية 🏆\n\n🇦🇷 (الدوري my <3 iphone @abc12 & you’re awsm #apple. DisplayIsAwesomehttps://www.apple, sooo happppppy 🙂 http://www.apple.com sdas 👽😀☂❤华み원❤"; 43 | text="RT @H45HEM: \u0645\u062d\u0645\u062f \u0635\u0644\u0627\u062d \u064a\u0646\u0642\u0630 \u0633\u0645\u0643\u0629 \u0645\u0646 \u0627\u0644\u063a\u0631\u0642! \u0648\u0627\u0644\u0646\u0639\u0645 \u0641\u064a\u0643 \u064a\u0627 \u0641\u062e\u0631 \u0627\u0644\u0639\u0631\u0628. https://t.co/8rGdLuGqKE"; 44 | System.out.println("Dirty text: " + text); 45 | System.out.println("------------------------------------------------------------------------------"); 46 | System.out.println("Cleaned text: " + cleanText(text)); 47 | System.out.println("------------------------------------------------------------------------------"); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /producer/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Server Properties 2 | spring.application.name=producer-service 3 | server.port=8081 4 | server.servlet.context-path=/producer 5 | 6 | # INFO Endpoint Configuration 7 | info.app.name=@project.name@ 8 | info.app.description=@project.description@ 9 | info.app.version=@project.version@ 10 | info.app.encoding=@project.build.sourceEncoding@ 11 | info.app.java.version=@java.version@ 12 | 13 | #URLs for allow CrossOrigin 14 | frontend.url = http://localhost:4200 -------------------------------------------------------------------------------- /producer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | #Kafka Configs 2 | kafka: 3 | boot: 4 | server: 127.0.0.1:9092 5 | topic: 6 | string: 7 | name: strings 8 | json: 9 | tweet: tweets 10 | article: articles 11 | 12 | # Twitter Social Configs 13 | social: 14 | twitter: 15 | auth: 16 | apiKey: API_KEY 17 | apiSecretKey: API_SECRET_KEY 18 | accessToken: ACCESS_TOKEN 19 | accessTokenSecret: ACCESS_TOKEN_SECRET 20 | count: 100 21 | 22 | # Crawler Configs 23 | crawler: 24 | maxPagesToSearch: 100 25 | pagesLimit: 20 26 | -------------------------------------------------------------------------------- /producer/src/test/java/com/sayedbaladoh/buzzdiggr/producer/ProducerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sayedbaladoh.buzzdiggr.producer; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ProducerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------