Bladeren bron

MediaHub Application: search files

Antoine DORIAN 2 jaren geleden
bovenliggende
commit
cdde6da5b6

+ 14
- 21
README.md Bestand weergeven

@@ -1,29 +1,22 @@
1 1
 # mediahub
2 2
 
3
+Bilan: 
4
+<br>Voici ma Solution a l"exercice de recherche d'information dans des fichiers TSV en utilisant la bibliotheque Akka Stream realiser en Java et Scala
5
+<br>Le service de recherche est fait en Scala, celui-ci est intègrer dans une solution Springboot en Java
3 6
 
4
-Instruction :
5
-Vous allez avoir droit à 5 fichier.
7
+Instruction:
8
+<br>Pour le bon fonctionement du projet, il est necessaire de telecharger les fichiers suivant et de les placer dans le repertoire resources, ne pas dezipper les fichiers (main\resources): 
6 9
 
7
-Ces 5 fichiers Charles pense qu'il faudra les charger dans dictionnary, dans 5 map et ensuite vous allez devoir les requêté à l’aide d’akkastream.
10
+- https://datasets.imdbws.com/name.basics.tsv.gz
11
+- https://datasets.imdbws.com/title.episode.tsv.gz
12
+- https://datasets.imdbws.com/title.ratings.tsv.gz
13
+- https://datasets.imdbws.com/title.principals.tsv.gz
14
+- https://datasets.imdbws.com/title.basics.tsv.gz
8 15
 
9
-Le principe d’akka est que sa permet de créer un pipe, vous avez la même logique que des streams en java. Donc le principe est que vous avez deux, trois recherche à opérer et vous allez faire un cor ( une api business) qui permet de faire une recherche et qu’il faudra écrire en SCALA.
16
+Afin de lancer la solution, il suffit de lancer MediahubApplication qui se trouve dans la partie Java du projet
10 17
 
11
-L’idéal serait d’écrire tous le service en scala, regarder sur internet comment créer un service en scala.
18
+Les deux endpoints disponibles pour tester le service sont:
12 19
 
13
-Sinon,  vous faites un springboot qui va appeler ce core dev en scala. Ce qui est certain c’est que le core doit être écrit en scala sur la base de l’api stream AKKA.
20
+- http://localhost:8080/api/teamMembers?movieTitle=Carmencita
21
+- http://localhost:8080/api/tvSeries
14 22
 
15
-Le sentiment de Charles est qu’il faut bien montrer le principe du pipe, c’est à dire que vous allez avoir potentiellement 5 map et l’idée est d’avoir un seul stream qui va permettre de les requêtes et de concaténer les  résultats, pour en faire un résultat final qui va être envoyé au service.
16
-
17
-Vous en discuterez lundi avec Charles pour savoir de quelle façon le faire.
18
-
19
-
20
-
21
-Pour résumer :
22
-
23
-Un core qui va être développé en scala au travers de la librairie Akkastream ( akkastream va permettre de requêter les différend fichier comme étant des source de donnée).
24
-
25
-Sur la base de cette source-là vous allez avoir des sources de données que vous allez concaténer au travers d’un stream et c’est sur ce stream là que vous allez faire vos recherches.
26
-
27
-A voir comment vous allez gérer ça lundi avec Charles.
28
-
29
-Ce service, il faudra l’exposer avec un springboot qui prépose juste une API requête qui appellera ce cœur business.

+ 58
- 0
pom.xml Bestand weergeven

@@ -25,6 +25,30 @@
25 25
     <build>
26 26
         <plugins>
27 27
             <plugin>
28
+                <groupId>org.apache.maven.plugins</groupId>
29
+                <artifactId>maven-compiler-plugin</artifactId>
30
+                <version>3.8.1</version>
31
+                <configuration>
32
+                    <source>18</source>
33
+                    <target>18</target>
34
+                </configuration>
35
+            </plugin>
36
+            <plugin>
37
+                <groupId>org.apache.maven.plugins</groupId>
38
+                <artifactId>maven-shade-plugin</artifactId>
39
+                <version>3.2.4</version>
40
+                <configuration>
41
+                </configuration>
42
+                <executions>
43
+                    <execution>
44
+                        <phase>package</phase>
45
+                        <goals>
46
+                            <goal>shade</goal>
47
+                        </goals>
48
+                    </execution>
49
+                </executions>
50
+            </plugin>
51
+            <plugin>
28 52
                 <groupId>net.alchim31.maven</groupId>
29 53
                 <artifactId>scala-maven-plugin</artifactId>
30 54
                 <version>3.1.3</version>
@@ -64,6 +88,19 @@
64 88
             <groupId>com.typesafe.akka</groupId>
65 89
             <artifactId>akka-stream_${scala.binary.version}</artifactId>
66 90
         </dependency>
91
+        <dependency>
92
+            <groupId>com.lightbend.akka</groupId>
93
+            <artifactId>akka-stream-alpakka-csv_${scala.binary.version}</artifactId>
94
+            <version>4.0.0</version>
95
+        </dependency>
96
+
97
+        <!-- alpakka -->
98
+        <dependency>
99
+            <groupId>com.lightbend.akka</groupId>
100
+            <artifactId>akka-stream-alpakka-file_2.13</artifactId>
101
+            <version>4.0.0</version>
102
+        </dependency>
103
+
67 104
         <!-- SpringBoot -->
68 105
         <dependency>
69 106
             <groupId>org.springframework.boot</groupId>
@@ -85,6 +122,27 @@
85 122
             <artifactId>spring-boot-starter-test</artifactId>
86 123
             <scope>test</scope>
87 124
         </dependency>
125
+        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-scala -->
126
+        <dependency>
127
+            <groupId>com.fasterxml.jackson.module</groupId>
128
+            <artifactId>jackson-module-scala_2.13</artifactId>
129
+            <version>2.14.0-rc1</version>
130
+        </dependency>
131
+        <!-- https://mvnrepository.com/artifact/org.json4s/json4s-native -->
132
+        <dependency>
133
+            <groupId>org.json4s</groupId>
134
+            <artifactId>json4s-native_2.13</artifactId>
135
+            <version>4.1.0-M1</version>
136
+        </dependency>
137
+        <!-- https://mvnrepository.com/artifact/org.json4s/json4s -->
138
+        <dependency>
139
+            <groupId>org.json4s</groupId>
140
+            <artifactId>json4s_2.11</artifactId>
141
+            <version>3.2.11</version>
142
+        </dependency>
143
+
88 144
     </dependencies>
89 145
 
146
+
147
+
90 148
 </project>

+ 13
- 0
src/main/java/com/mediahub/MediahubApplication.java Bestand weergeven

@@ -0,0 +1,13 @@
1
+package com.mediahub;
2
+
3
+import org.springframework.boot.SpringApplication;
4
+import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+@SpringBootApplication
7
+public class MediahubApplication {
8
+
9
+    public static void main(String[] args) {
10
+        SpringApplication.run(MediahubApplication.class, args);
11
+    }
12
+
13
+}

+ 53
- 0
src/main/java/com/mediahub/controller/MovieController.java Bestand weergeven

@@ -0,0 +1,53 @@
1
+package com.mediahub.controller;
2
+
3
+import com.mediahub.JsonCustomMapping;
4
+import com.mediahub.MovieService;
5
+import com.mediahub.MovieServiceTrait;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.http.HttpStatus;
8
+import org.springframework.http.ResponseEntity;
9
+import org.springframework.web.bind.annotation.GetMapping;
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
+
15
+@RestController
16
+@RequestMapping("/api")
17
+public class MovieController {
18
+    @Autowired
19
+    MovieServiceTrait movieServiceTrait;
20
+
21
+    @GetMapping("/teamMembers")
22
+    public ResponseEntity getTeamMembers(@RequestParam(required = true) String movieTitle) {
23
+        scala.collection.immutable.List<MovieService.Principal> principalList;
24
+
25
+        try {
26
+            if (movieTitle == null)
27
+                return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
28
+            else {
29
+                principalList = (movieServiceTrait.principalsForMovieName(movieTitle).toList());
30
+            }
31
+
32
+            return new ResponseEntity<>(new JsonCustomMapping().mapperPrincipal(principalList), HttpStatus.OK);
33
+
34
+        } catch (Exception e) {
35
+            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
36
+        }
37
+
38
+    }
39
+
40
+    @GetMapping("/tvSeries")
41
+    public ResponseEntity getTvSeries() {
42
+        scala.collection.immutable.List<MovieService.TvSeries> tvSeriesList;
43
+
44
+        try {
45
+            tvSeriesList = (movieServiceTrait.tvSeriesWithGreatestNumberOfEpisodes().toList());
46
+            return new ResponseEntity<>(new JsonCustomMapping().mapperSeries(tvSeriesList), HttpStatus.OK);
47
+        } catch (Exception e) {
48
+            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
49
+        }
50
+
51
+    }
52
+
53
+}

+ 15
- 0
src/main/scala/com/mediahub/JsonCustomMapping.scala Bestand weergeven

@@ -0,0 +1,15 @@
1
+package com.mediahub
2
+
3
+import org.json4s.DefaultFormats
4
+import org.json4s.native.Json
5
+
6
+class JsonCustomMapping extends MovieService {
7
+
8
+  def mapperPrincipal(list:  List[Principal]): String = {
9
+     Json(DefaultFormats).write(list)
10
+  }
11
+
12
+  def mapperSeries(list: List[TvSeries]): String = {
13
+    Json(DefaultFormats).write(list)
14
+  }
15
+}

+ 146
- 0
src/main/scala/com/mediahub/MovieQueryService.scala Bestand weergeven

@@ -0,0 +1,146 @@
1
+package com.mediahub
2
+
3
+import akka.NotUsed
4
+import akka.actor.ActorSystem
5
+import akka.stream.ActorAttributes.supervisionStrategy
6
+import akka.stream.Supervision.resumingDecider
7
+import akka.stream.alpakka.csv.scaladsl.{CsvParsing, CsvToMap}
8
+import akka.stream.scaladsl.{Compression, FileIO, Flow, Sink, Source}
9
+import akka.util.ByteString
10
+import org.springframework.stereotype.Component
11
+
12
+import java.io.File
13
+import java.nio.charset.StandardCharsets
14
+import java.nio.file.Paths
15
+import scala.collection.{immutable, mutable}
16
+import scala.concurrent.Await
17
+import scala.concurrent.duration.DurationInt
18
+import scala.language.postfixOps
19
+import scala.util.Try
20
+
21
+
22
+@Component
23
+class MovieQueryService extends MovieServiceTrait {
24
+
25
+  implicit val system: ActorSystem = ActorSystem()
26
+
27
+  val titleBasicsResource: File = new File("src/main/resources/title.basics.tsv.gz")
28
+  val titlePrincipalsResource: File = new File("src/main/resources/title.principals.tsv.gz")
29
+  val nameBasicsResource: File = new File("src/main/resources/name.basics.tsv.gz")
30
+  val titleEpisodeResource: File = new File("src/main/resources/title.episode.tsv.gz")
31
+
32
+  def fileSource(file: File): Source[ByteString, NotUsed] = {
33
+    Source.single(file)
34
+      .flatMapConcat(f => FileIO.fromPath(Paths.get(f.getPath), 1 * 1024 * 1024 * 2))
35
+      .via(Compression.gunzip())
36
+  }
37
+
38
+  val lineParser: Flow[ByteString, Map[String, String], NotUsed] = {
39
+    CsvParsing
40
+      .lineScanner(CsvParsing.Tab, CsvParsing.DoubleQuote, CsvParsing.DoubleQuote)
41
+      .via(CsvToMap.toMapAsStringsCombineAll(headerPlaceholder = Option.empty))
42
+  }
43
+
44
+  override def principalsForMovieName(name: String): List[Principal] = {
45
+
46
+    val titleId: String = getIdOfTitle(titleBasicsResource, name)
47
+    val personsIdList: List[String] = getIdOfPersons(titlePrincipalsResource, titleId).toList.flatten
48
+    val personsList = getPersons(nameBasicsResource, personsIdList)
49
+    personsList
50
+  }
51
+
52
+  override def tvSeriesWithGreatestNumberOfEpisodes(): List[TvSeries] = {
53
+    val seriesTitleMap = getSeriesTitle(titleEpisodeResource)
54
+    val titleListMaxEpisode = getTitleByIds(titleBasicsResource, seriesTitleMap)
55
+    titleListMaxEpisode
56
+  }
57
+
58
+
59
+
60
+  def getIdOfTitle(file: File, titleName: String): String = {
61
+    val result = fileSource(file)
62
+      .mapAsync(50) {
63
+        res =>
64
+          Source.single(res)
65
+            .via(lineParser)
66
+            .filter(row => row.getOrElse("primaryTitle", "") == titleName)
67
+            .map(a => a.get("tconst"))
68
+            .withAttributes(supervisionStrategy(resumingDecider))
69
+            .runWith(Sink.head)
70
+      }
71
+      .withAttributes(supervisionStrategy(resumingDecider))
72
+      .runWith(Sink.head)
73
+
74
+    Await.result(result, 5 minutes)
75
+    result.value.get.get.get
76
+  }
77
+
78
+  def getTitleByIds(file: File, titleIdMap: mutable.Map[String, Int]): List[TvSeries] = {
79
+    val result = Source.single(file)
80
+      .flatMapConcat(f => FileIO.fromPath(Paths.get(f.getPath), 1 * 1024 * 1024))
81
+      .via(Compression.gunzip())
82
+      .via(CsvParsing.lineScanner(CsvParsing.Tab, CsvParsing.DoubleQuote, CsvParsing.DoubleQuote))
83
+      .via(CsvToMap.toMapAsStringsCombineAll(StandardCharsets.UTF_8, Option.empty))
84
+      .filter(row => titleIdMap.keySet.toList.contains(row.getOrElse("tconst", "")))
85
+      .map(a => TvSeries(a("originalTitle"), Try(a.getOrElse("startYear","").toInt).getOrElse(0), a.getOrElse("endYear","").toIntOption, a.get("genres").toList))
86
+      .runWith(Sink.collection)
87
+
88
+    Await.result(result, 5 minutes)
89
+    result.value.get.get.toList
90
+  }
91
+
92
+  def getIdOfPersons(file: File, titleId: String): immutable.Iterable[Option[String]] = {
93
+    val result = fileSource(file)
94
+      .mapAsync(50) {
95
+        res =>
96
+          Source.single(res)
97
+            .via(lineParser)
98
+            .filter(row => row.getOrElse("tconst", "") == titleId)
99
+            .map(a => a.get("nconst"))
100
+            .withAttributes(supervisionStrategy(resumingDecider))
101
+            .runWith(Sink.collection)
102
+      }
103
+      .withAttributes(supervisionStrategy(resumingDecider))
104
+      .runWith(Sink.head)
105
+
106
+    Await.result(result, 5 minutes)
107
+  }
108
+
109
+
110
+  def getPersons(file: File, personsIdList: List[String]): List[Principal] = {
111
+    val result = Source.single(file)
112
+      .flatMapConcat(f => FileIO.fromPath(Paths.get(f.getPath), 1 * 1024 * 1024))
113
+      .via(Compression.gunzip())
114
+      .via(CsvParsing.lineScanner(CsvParsing.Tab, CsvParsing.DoubleQuote, CsvParsing.DoubleQuote))
115
+      .via(CsvToMap.toMapAsStringsCombineAll(StandardCharsets.UTF_8, Option.empty))
116
+      .filter(row => personsIdList.contains(row.getOrElse("nconst", "")))
117
+      .map(a => Principal(a("primaryName"), Try(a("birthYear").toInt).getOrElse(0), a("deathYear").toIntOption, a.get("primaryProfession").toList))
118
+      .runWith(Sink.collection)
119
+
120
+    Await.result(result, 5 minutes)
121
+    result.value.get.get.toList
122
+  }
123
+
124
+  def getSeriesTitle(file: File): mutable.Map[String, Int] = {
125
+
126
+    val seriesMap = collection.mutable.Map("0" -> 0)
127
+    val maxSeriesMap = collection.mutable.Map("0" -> 0)
128
+
129
+    val result = Source.single(file)
130
+      .flatMapConcat(f => FileIO.fromPath(Paths.get(f.getPath), 1 * 1024 * 1024))
131
+      .via(Compression.gunzip())
132
+      .via(CsvParsing.lineScanner(CsvParsing.Tab, CsvParsing.DoubleQuote, CsvParsing.DoubleQuote))
133
+      .via(CsvToMap.toMapAsStringsCombineAll(StandardCharsets.UTF_8, Option.empty))
134
+      .map(row => seriesMap += seriesMap.get(row("parentTconst")).map(x => row("parentTconst") -> (x + 1)).getOrElse(row("parentTconst") -> 1))
135
+      .runWith(Sink.seq)
136
+
137
+    Await.result(result, 5 minutes)
138
+
139
+    for (_ <- 1 to 10) {
140
+      maxSeriesMap.addOne(seriesMap.maxBy(x => x._2)._1 -> seriesMap.maxBy(x => x._2)._2)
141
+      seriesMap.remove(seriesMap.maxBy(x => x._2)._1)
142
+    }
143
+    maxSeriesMap
144
+  }
145
+
146
+}

+ 20
- 0
src/main/scala/com/mediahub/MovieService.scala Bestand weergeven

@@ -0,0 +1,20 @@
1
+package com.mediahub
2
+
3
+class MovieService {
4
+
5
+  final case class Principal(
6
+                              name: String,
7
+                              birthYear: Int,
8
+                              deathYear: Option[Int],
9
+                              profession: List[String])
10
+
11
+  case class TvSeries(
12
+                       original: String,
13
+                       startYear: Int,
14
+                       endYear: Option[Int],
15
+                       genres: List[String])
16
+
17
+
18
+}
19
+
20
+

+ 8
- 0
src/main/scala/com/mediahub/MovieServiceTrait.scala Bestand weergeven

@@ -0,0 +1,8 @@
1
+package com.mediahub
2
+
3
+
4
+trait MovieServiceTrait extends  MovieService {
5
+    def principalsForMovieName(name: String):  List[Principal]
6
+    def tvSeriesWithGreatestNumberOfEpisodes():  List[TvSeries]
7
+
8
+}

+ 0
- 13
src/main/scala/com/mediahub/SpringBootIntegration.scala Bestand weergeven

@@ -1,13 +0,0 @@
1
-package com.mediahub
2
-
3
-import org.springframework.boot.{CommandLineRunner, SpringApplication}
4
-import org.springframework.boot.autoconfigure.SpringBootApplication
5
-
6
-@SpringBootApplication
7
-class SpringBootIntegration extends CommandLineRunner{
8
-  override def run(args: String*): Unit = println("\n\n\n *** Hello World \n\n\n")
9
-}
10
-
11
-object SpringBootIntegration extends App {
12
-  SpringApplication.run(classOf[SpringBootIntegration])
13
-}

Powered by TurnKey Linux.