From 31af7ea1d287b45e666c9c141d75e6c64f0c1a27 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:16:37 +0000 Subject: [PATCH 01/29] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5150e50f..05def9a9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/_uV8Mn8f) # 📘 Projektarbete: JPA + Hibernate med GitHub-flöde Projektet genomförs som antingen en Java CLI-applikation eller med hjĂ€lp av JavaFX om ni vill ha ett grafiskt grĂ€nssnitt. From 149c2cf9d25f0c59122d640c2f9054eb566a9c0b Mon Sep 17 00:00:00 2001 From: mattknatt Date: Tue, 16 Dec 2025 09:18:17 +0100 Subject: [PATCH 02/29] Adds docker-compose.yml --- docker-compose.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..fcd736ac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + myPod: + image: mysql:9.5.0 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: myPodDB + MYSQL_USER: user + MYSQL_PASSWORD: pass + ports: + - "3306:3306" + volumes: + - mypod_data:/var/lib/mysql +volumes: + mypod_data: From 31e5caa11409bb1c0c4c0c62f44c0b06c88d397f Mon Sep 17 00:00:00 2001 From: mattknatt Date: Tue, 16 Dec 2025 10:52:59 +0100 Subject: [PATCH 03/29] Adds PersistenceManager class --- .../java/org/example/PersistenceManager.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/org/example/PersistenceManager.java diff --git a/src/main/java/org/example/PersistenceManager.java b/src/main/java/org/example/PersistenceManager.java new file mode 100644 index 00000000..75ab1535 --- /dev/null +++ b/src/main/java/org/example/PersistenceManager.java @@ -0,0 +1,56 @@ +package org.example; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ScanResult; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.PersistenceConfiguration; +import org.hibernate.jpa.HibernatePersistenceConfiguration; + +import java.util.List; + + + +public class PersistenceManager { + + private static final EntityManagerFactory emf; + + static { + + List> entities = getEntities("org.example"); + + final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf") + .jdbcUrl("jdbc:mysql://localhost:3306/mypod") + .jdbcUsername("user") + .jdbcPassword("pass") + .property("hibernate.hbm2ddl.auto", "update") + .property("hibernate.show_sql", "true") + .property("hibernate.format_sql", "true") + .property("hibernate.highlight_sql", "true") + .managedClasses(entities); + + emf = cfg.createEntityManagerFactory(); + + } + + public static EntityManagerFactory getEntityManagerFactory() { + return emf; + } + + + + +//Not invented here!! + private static List> getEntities(String pkg) { + List> entities; + try (ScanResult scanResult = + new ClassGraph() + .enableClassInfo() + .enableAnnotationInfo() + .acceptPackages(pkg) + .scan()) { + entities = scanResult.getClassesWithAnnotation(Entity.class).loadClasses(); + } + return entities; + } +} From 2b062181832d4431408b402c122433f9e3838293 Mon Sep 17 00:00:00 2001 From: mattknatt Date: Tue, 16 Dec 2025 12:01:09 +0100 Subject: [PATCH 04/29] Adds entities --- .../java/org/example/PersistenceManager.java | 2 +- src/main/java/org/example/SongRepository.java | 9 ++ .../java/org/example/SongRepositoryImpl.java | 18 +++ src/main/java/org/example/entity/Album.java | 104 ++++++++++++++++++ src/main/java/org/example/entity/Artist.java | 71 ++++++++++++ .../java/org/example/entity/Playlist.java | 61 ++++++++++ src/main/java/org/example/entity/Song.java | 88 +++++++++++++++ 7 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/example/SongRepository.java create mode 100644 src/main/java/org/example/SongRepositoryImpl.java create mode 100644 src/main/java/org/example/entity/Album.java create mode 100644 src/main/java/org/example/entity/Artist.java create mode 100644 src/main/java/org/example/entity/Playlist.java create mode 100644 src/main/java/org/example/entity/Song.java diff --git a/src/main/java/org/example/PersistenceManager.java b/src/main/java/org/example/PersistenceManager.java index 75ab1535..83fb61fc 100644 --- a/src/main/java/org/example/PersistenceManager.java +++ b/src/main/java/org/example/PersistenceManager.java @@ -17,7 +17,7 @@ public class PersistenceManager { static { - List> entities = getEntities("org.example"); + List> entities = getEntities("org.example.entity"); final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf") .jdbcUrl("jdbc:mysql://localhost:3306/mypod") diff --git a/src/main/java/org/example/SongRepository.java b/src/main/java/org/example/SongRepository.java new file mode 100644 index 00000000..b17c8972 --- /dev/null +++ b/src/main/java/org/example/SongRepository.java @@ -0,0 +1,9 @@ +package org.example; + +import org.example.entity.Song; + +import java.util.List; + +public interface SongRepository { + List findSongByArtist(); +} diff --git a/src/main/java/org/example/SongRepositoryImpl.java b/src/main/java/org/example/SongRepositoryImpl.java new file mode 100644 index 00000000..49034852 --- /dev/null +++ b/src/main/java/org/example/SongRepositoryImpl.java @@ -0,0 +1,18 @@ +package org.example; + +import jakarta.persistence.EntityManagerFactory; +import org.example.entity.Song; + +import java.util.List; + +public class SongRepositoryImpl implements SongRepository { + + private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); + + + @Override + public List findSongByArtist() { + return List.of(); + } +} + diff --git a/src/main/java/org/example/entity/Album.java b/src/main/java/org/example/entity/Album.java new file mode 100644 index 00000000..754f35bb --- /dev/null +++ b/src/main/java/org/example/entity/Album.java @@ -0,0 +1,104 @@ +package org.example.entity; + +import jakarta.persistence.*; +import org.hibernate.proxy.HibernateProxy; + +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; + +@Entity +public class Album { + + @Id + @Column(name="album_id") + private Long albumId; + + private String name; + + private String genre; + + private LocalDate year; + + private Long trackCount; + + @OneToMany + @JoinColumn(name="song_id") + private List song; + + @ManyToOne + @JoinColumn(name="artist_id") + private Artist artist; + + public Long getAlbumId() { + return albumId; + } + + public void setAlbumId(Long albumId) { + this.albumId = albumId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGenre() { + return genre; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + public LocalDate getYear() { + return year; + } + + public void setYear(LocalDate year) { + this.year = year; + } + + public Long getTrackCount() { + return trackCount; + } + + public void setTrackCount(Long trackCount) { + this.trackCount = trackCount; + } + + public List getSong() { + return song; + } + + public void setSong(List song) { + this.song = song; + } + + public Artist getArtist() { + return artist; + } + + public void setArtist(Artist artist) { + this.artist = artist; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Album album = (Album) o; + return getAlbumId() != null && Objects.equals(getAlbumId(), album.getAlbumId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/org/example/entity/Artist.java b/src/main/java/org/example/entity/Artist.java new file mode 100644 index 00000000..37451f31 --- /dev/null +++ b/src/main/java/org/example/entity/Artist.java @@ -0,0 +1,71 @@ +package org.example.entity; + +import jakarta.persistence.*; +import org.hibernate.proxy.HibernateProxy; + +import java.util.List; +import java.util.Objects; + +@Entity +public class Artist { + + @Id + @Column(name="artist_id") + private Long artistId; + + private String name; + + private String country; + + @OneToMany + @JoinColumn(name="album_id") + private List album; + + public Long getArtistId() { + return artistId; + } + + public void setArtistId(Long artistId) { + this.artistId = artistId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public List getAlbum() { + return album; + } + + public void setAlbum(List album) { + this.album = album; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Artist artist = (Artist) o; + return getArtistId() != null && Objects.equals(getArtistId(), artist.getArtistId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/org/example/entity/Playlist.java b/src/main/java/org/example/entity/Playlist.java new file mode 100644 index 00000000..98c5e304 --- /dev/null +++ b/src/main/java/org/example/entity/Playlist.java @@ -0,0 +1,61 @@ +package org.example.entity; + +import jakarta.persistence.*; +import org.hibernate.proxy.HibernateProxy; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +public class Playlist { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long playlistId; + + private String name; + + @ManyToMany(cascade = CascadeType.PERSIST) + private Set songs = new HashSet<>(); + + public void addSong(Song song) { + this.songs.add(song); + } + + public void removeSong(Song song) { + this.songs.remove(song); + } + + public Long getPlaylistId() { + return playlistId; + } + + public void setPlaylistId(Long playlistId) { + this.playlistId = playlistId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Playlist playlist = (Playlist) o; + return getPlaylistId() != null && Objects.equals(getPlaylistId(), playlist.getPlaylistId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/org/example/entity/Song.java b/src/main/java/org/example/entity/Song.java new file mode 100644 index 00000000..e0b9d3d6 --- /dev/null +++ b/src/main/java/org/example/entity/Song.java @@ -0,0 +1,88 @@ +package org.example.entity; + +import jakarta.persistence.*; +import org.hibernate.proxy.HibernateProxy; + +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +public class Song { + + @Id + @Column(name="song_id") + private Long songId; + + private String title; + + private Long length; + + @ManyToOne + @JoinColumn(name = "album_id") + private Album album; + + @ManyToMany(mappedBy = "songs") + private Set playlist = new HashSet<>(); + + public Set getPlaylist() { + return playlist; + } + + public void setPlaylist(Set playlist) { + this.playlist = playlist; + } + + public Long getSongId() { + return songId; + } + + public void setSongId(Long id) { + this.songId = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Long getLength() { + return length; + } + + public void setLength(Long length) { + this.length = length; + } + + + public Album getAlbum() { + return album; + } + + public void setAlbum(Album album) { + this.album = album; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Song song = (Song) o; + return getSongId() != null && Objects.equals(getSongId(), song.getSongId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } + + //Todo: Generate toString? + +} From f10da7cb1aa874eb8a2889878fdf288f3c733a6c Mon Sep 17 00:00:00 2001 From: mattknatt Date: Tue, 16 Dec 2025 13:14:54 +0100 Subject: [PATCH 05/29] Corrects mapping in Album and Artist classes. Adds shutdown hook to close EntityManagerFactory Co-authored-by: Simon Forsberg Co-authored-by: Jesper Larsson --- src/main/java/org/example/PersistenceManager.java | 3 +++ src/main/java/org/example/entity/Album.java | 3 +-- src/main/java/org/example/entity/Artist.java | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/PersistenceManager.java b/src/main/java/org/example/PersistenceManager.java index 83fb61fc..99aa9780 100644 --- a/src/main/java/org/example/PersistenceManager.java +++ b/src/main/java/org/example/PersistenceManager.java @@ -31,6 +31,9 @@ public class PersistenceManager { emf = cfg.createEntityManagerFactory(); + //From CodeRabbit: Register a shutdown hook to properly release database connections on JVM exit. + Runtime.getRuntime().addShutdownHook(new Thread(emf::close)); + } public static EntityManagerFactory getEntityManagerFactory() { diff --git a/src/main/java/org/example/entity/Album.java b/src/main/java/org/example/entity/Album.java index 754f35bb..21a9060a 100644 --- a/src/main/java/org/example/entity/Album.java +++ b/src/main/java/org/example/entity/Album.java @@ -22,8 +22,7 @@ public class Album { private Long trackCount; - @OneToMany - @JoinColumn(name="song_id") + @OneToMany(mappedBy = "album") private List song; @ManyToOne diff --git a/src/main/java/org/example/entity/Artist.java b/src/main/java/org/example/entity/Artist.java index 37451f31..ca14fd78 100644 --- a/src/main/java/org/example/entity/Artist.java +++ b/src/main/java/org/example/entity/Artist.java @@ -17,8 +17,7 @@ public class Artist { private String country; - @OneToMany - @JoinColumn(name="album_id") + @OneToMany(mappedBy = "artist") private List album; public Long getArtistId() { From 2072f5269aa97cd6958208145976b032e1733fca Mon Sep 17 00:00:00 2001 From: mattknatt Date: Wed, 17 Dec 2025 11:59:31 +0100 Subject: [PATCH 06/29] Implements ItunesApiClient Co-authored-by: Simon Forsberg Co-authored-by: Jesper Larsson --- pom.xml | 10 ++++ src/main/java/org/example/App.java | 12 +++- .../java/org/example/DatabaseInitializer.java | 60 +++++++++++++++++++ .../java/org/example/ItunesApiClient.java | 59 ++++++++++++++++++ src/main/java/org/example/ItunesDTO.java | 22 +++++++ .../java/org/example/PersistenceManager.java | 2 +- src/main/java/org/example/SongRepository.java | 16 +++++ .../java/org/example/SongRepositoryImpl.java | 51 ++++++++++++++++ src/main/java/org/example/entity/Album.java | 23 ++++++- src/main/java/org/example/entity/Artist.java | 13 ++++ src/main/java/org/example/entity/Song.java | 22 +++++++ 11 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/example/DatabaseInitializer.java create mode 100644 src/main/java/org/example/ItunesApiClient.java create mode 100644 src/main/java/org/example/ItunesDTO.java diff --git a/pom.xml b/pom.xml index 909503d0..91a7807b 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,16 @@ 9.5.0 runtime + + com.fasterxml.jackson.core + jackson-databind + 2.17.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.1 + io.github.classgraph classgraph diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 165e5cd5..4359bb4b 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -2,6 +2,16 @@ public class App { public static void main(String[] args) { - System.out.println("Hello There!"); + PersistenceManager pm; + ItunesApiClient apiClient = new ItunesApiClient(); + SongRepository songRepo = new SongRepositoryImpl(); + + try(EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory()) { + assert emf.isOpen(); + DatabaseInitializer initializer = new DatabaseInitializer(apiClient, songRepo); + initializer.init(); + } catch (Exception e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/org/example/DatabaseInitializer.java b/src/main/java/org/example/DatabaseInitializer.java new file mode 100644 index 00000000..3411fcae --- /dev/null +++ b/src/main/java/org/example/DatabaseInitializer.java @@ -0,0 +1,60 @@ +package org.example; + +import org.example.entity.Album; +import org.example.entity.Artist; +import org.example.entity.Song; + +import java.util.ArrayList; +import java.util.List; + +public class DatabaseInitializer { + + private final ItunesApiClient apiClient; + + private final SongRepository songRepo; + + public DatabaseInitializer(ItunesApiClient apiClient, SongRepository songRepo) { + this.apiClient = apiClient; + this.songRepo = songRepo; + } + + public void init() { + if (songRepo.count() > 0) { //check if there is data already + return; + } + + List searches = List.of("the+war+on+drugs", + "refused", + "thrice", + "16+horsepower", + "viagra+boys", + "geese", + "ghost", + "run+the+jewels", + "rammstein", + "salvatore+ganacci", + "baroness" + ); + for (String term : searches) { + try { + apiClient.searchSongs(term).forEach(dto -> { + Song s = Song.fromDTO(dto); + Album al = Album.fromDTO(dto); + Artist ar = Artist.fromDTO(dto); +// s.setTitle(dto.trackName()); +// s.setSongId((long) dto.trackId()); +// s.setLength((long) dto.trackTimeMillis()); + songRepo.save(s); + songRepo.save(al); + songRepo.save(ar); + + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + +} + diff --git a/src/main/java/org/example/ItunesApiClient.java b/src/main/java/org/example/ItunesApiClient.java new file mode 100644 index 00000000..8477d3f6 --- /dev/null +++ b/src/main/java/org/example/ItunesApiClient.java @@ -0,0 +1,59 @@ +package org.example; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class ItunesApiClient { + + private final HttpClient client; + private final ObjectMapper mapper; + + public ItunesApiClient() { + this.client = HttpClient.newHttpClient(); + this.mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + } + + public List searchSongs(String term) throws Exception { + + + String encodedTerm = URLEncoder.encode(term, StandardCharsets.UTF_8); + String url = "https://itunes.apple.com/search?term=" + encodedTerm + "&entity=song&attribute=artistTerm&limit=3"; + + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(url)) + .build(); + + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + + // Kontrollera status + if (response.statusCode() != 200) { + throw new RuntimeException("API-fel: " + response.statusCode()); + } + + // Parsar JSON + JsonNode root = mapper.readTree(response.body()); + JsonNode results = root.get("results"); + + List songs = new ArrayList<>(); + for (JsonNode node : results) { + ItunesDTO song = mapper.treeToValue(node, ItunesDTO.class); + songs.add(song); + } + + return songs; + } +} + diff --git a/src/main/java/org/example/ItunesDTO.java b/src/main/java/org/example/ItunesDTO.java new file mode 100644 index 00000000..1540a5e6 --- /dev/null +++ b/src/main/java/org/example/ItunesDTO.java @@ -0,0 +1,22 @@ +package org.example; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.time.LocalDate; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record ItunesDTO (Long artistId, + Long collectionId, + Long trackId, + String trackName, + String artistName, + String collectionName, + String country, + String primaryGenreName, + LocalDate releaseDate, + Long trackCount, + Long trackTimeMillis) { + + public int releaseYear() { return releaseDate.getYear(); } +} + diff --git a/src/main/java/org/example/PersistenceManager.java b/src/main/java/org/example/PersistenceManager.java index 99aa9780..a75fafc0 100644 --- a/src/main/java/org/example/PersistenceManager.java +++ b/src/main/java/org/example/PersistenceManager.java @@ -20,7 +20,7 @@ public class PersistenceManager { List> entities = getEntities("org.example.entity"); final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf") - .jdbcUrl("jdbc:mysql://localhost:3306/mypod") + .jdbcUrl("jdbc:mysql://localhost:3306/myPodDB") .jdbcUsername("user") .jdbcPassword("pass") .property("hibernate.hbm2ddl.auto", "update") diff --git a/src/main/java/org/example/SongRepository.java b/src/main/java/org/example/SongRepository.java index b17c8972..62c0b0d0 100644 --- a/src/main/java/org/example/SongRepository.java +++ b/src/main/java/org/example/SongRepository.java @@ -1,9 +1,25 @@ package org.example; +import org.example.entity.Album; +import org.example.entity.Artist; import org.example.entity.Song; import java.util.List; public interface SongRepository { List findSongByArtist(); + + Long count(); + + boolean existsByUniqueId(Song song); + + boolean existsByUniqueId(Album album); + + boolean existsByUniqueId(Artist artist); + + void save(Song song); + + void save(Album album); + + void save(Artist artist); } diff --git a/src/main/java/org/example/SongRepositoryImpl.java b/src/main/java/org/example/SongRepositoryImpl.java index 49034852..2463b738 100644 --- a/src/main/java/org/example/SongRepositoryImpl.java +++ b/src/main/java/org/example/SongRepositoryImpl.java @@ -1,6 +1,9 @@ package org.example; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Query; +import org.example.entity.Album; +import org.example.entity.Artist; import org.example.entity.Song; import java.util.List; @@ -14,5 +17,53 @@ public class SongRepositoryImpl implements SongRepository { public List findSongByArtist() { return List.of(); } + + @Override + public Long count() { + return emf.createEntityManager() + .createQuery("select count(s) from Song s", Long.class) + .getSingleResult(); + } + + @Override + public boolean existsByUniqueId(Song song) { + return emf.createEntityManager() + .createQuery("select count(s) from Song s where s.songId = songId", Long.class) + .getSingleResult() > 0; + } + + @Override + public boolean existsByUniqueId(Album album) { + return emf.createEntityManager() + .createQuery("select count(a) from Album a where a.albumId = albumId", Long.class) + .getSingleResult() > 0; + } + + @Override + public boolean existsByUniqueId(Artist artist) { + return emf.createEntityManager() + .createQuery("select count(a) from Artist a where a.artistId = artistId", Long.class) + .getSingleResult() > 0; + } + + + @Override + public void save(Song song) { + if(!existsByUniqueId(song)) + emf.runInTransaction(em -> em.persist(song)); + + } + + @Override + public void save(Album album) { + if(!existsByUniqueId(album)) + emf.runInTransaction(em -> em.persist(album)); + } + + @Override + public void save(Artist artist) { + if(!existsByUniqueId(artist)) + emf.runInTransaction(em -> em.persist(artist)); + } } diff --git a/src/main/java/org/example/entity/Album.java b/src/main/java/org/example/entity/Album.java index 21a9060a..9b49b171 100644 --- a/src/main/java/org/example/entity/Album.java +++ b/src/main/java/org/example/entity/Album.java @@ -1,6 +1,7 @@ package org.example.entity; import jakarta.persistence.*; +import org.example.ItunesDTO; import org.hibernate.proxy.HibernateProxy; import java.time.LocalDate; @@ -18,7 +19,7 @@ public class Album { private String genre; - private LocalDate year; + private int year; private Long trackCount; @@ -29,6 +30,22 @@ public class Album { @JoinColumn(name="artist_id") private Artist artist; + protected Album (){} + + public Album(Long albumId, String name, String genre, int year, Long trackCount) { + this.albumId = albumId; + this.name = name; + this.genre = genre; + this.year = year; + this.trackCount = trackCount; + } + + public static Album fromDTO(ItunesDTO dto) { + return new Album(dto.collectionId(), dto.collectionName(), dto.primaryGenreName(), dto.releaseYear(), dto.trackCount()); + } + + + public Long getAlbumId() { return albumId; } @@ -53,11 +70,11 @@ public void setGenre(String genre) { this.genre = genre; } - public LocalDate getYear() { + public int getYear() { return year; } - public void setYear(LocalDate year) { + public void setYear(int year) { this.year = year; } diff --git a/src/main/java/org/example/entity/Artist.java b/src/main/java/org/example/entity/Artist.java index ca14fd78..5a14e93a 100644 --- a/src/main/java/org/example/entity/Artist.java +++ b/src/main/java/org/example/entity/Artist.java @@ -1,6 +1,7 @@ package org.example.entity; import jakarta.persistence.*; +import org.example.ItunesDTO; import org.hibernate.proxy.HibernateProxy; import java.util.List; @@ -20,6 +21,18 @@ public class Artist { @OneToMany(mappedBy = "artist") private List album; + protected Artist (){} + + public Artist (Long artistId, String name, String country) { + this.artistId = artistId; + this.name = name; + this.country = country; + } + + public static Artist fromDTO(ItunesDTO dto) { + return new Artist(dto.artistId(), dto.artistName(), dto.country()); + } + public Long getArtistId() { return artistId; } diff --git a/src/main/java/org/example/entity/Song.java b/src/main/java/org/example/entity/Song.java index e0b9d3d6..b73820bb 100644 --- a/src/main/java/org/example/entity/Song.java +++ b/src/main/java/org/example/entity/Song.java @@ -1,6 +1,7 @@ package org.example.entity; import jakarta.persistence.*; +import org.example.ItunesDTO; import org.hibernate.proxy.HibernateProxy; import java.time.LocalDate; @@ -26,6 +27,19 @@ public class Song { @ManyToMany(mappedBy = "songs") private Set playlist = new HashSet<>(); + protected Song() {} + + public Song(Long songId, String title, Long length) { + this.songId = songId; + this.title = title; + this.length = length; + + } + + public static Song fromDTO(ItunesDTO dto) { + return new Song(dto.trackId(), dto.trackName(), dto.trackTimeMillis()); + } + public Set getPlaylist() { return playlist; } @@ -83,6 +97,14 @@ public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } + //Todo: Generate toString? + @Override + public String toString() { + return getClass().getSimpleName() + "(" + + "songId = " + songId + ", " + + "title = " + title + ", " + + "length = " + length + ")"; + } } From 7bbf493687f009ddb5733cf159a97cecf06f9aec Mon Sep 17 00:00:00 2001 From: mattknatt Date: Wed, 17 Dec 2025 12:00:45 +0100 Subject: [PATCH 07/29] Imports entitymanagerfactory --- src/main/java/org/example/App.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 4359bb4b..9aec416c 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -1,5 +1,7 @@ package org.example; +import jakarta.persistence.EntityManagerFactory; + public class App { public static void main(String[] args) { PersistenceManager pm; From 4c5381355b7cdca6f8cbf43203aa7180117ce491 Mon Sep 17 00:00:00 2001 From: mattknatt Date: Wed, 17 Dec 2025 15:15:50 +0100 Subject: [PATCH 08/29] Fixes according to CodeRabbit feedback. Corrects duplicate handling when loading DB. Co-authored-by: Simon Forsberg Co-authored-by: Jesper Larsson Co-authored-by: Johan Briger --- src/main/java/org/example/App.java | 12 +++-- .../java/org/example/DatabaseInitializer.java | 47 ++++++++++--------- .../java/org/example/ItunesApiClient.java | 10 ++-- src/main/java/org/example/ItunesDTO.java | 27 ++++++----- .../java/org/example/SongRepositoryImpl.java | 46 +++++++++--------- src/main/java/org/example/entity/Album.java | 26 ++++++---- src/main/java/org/example/entity/Artist.java | 17 ++++--- src/main/java/org/example/entity/Song.java | 19 ++++---- 8 files changed, 115 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 9aec416c..14c919f2 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -4,16 +4,20 @@ public class App { public static void main(String[] args) { - PersistenceManager pm; ItunesApiClient apiClient = new ItunesApiClient(); SongRepository songRepo = new SongRepositoryImpl(); - try(EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory()) { - assert emf.isOpen(); + try { + EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); + if (!emf.isOpen()) { + throw new IllegalStateException("EntityManagerFactory is not open"); + } DatabaseInitializer initializer = new DatabaseInitializer(apiClient, songRepo); initializer.init(); + System.out.println("Database initialization completed successfully"); } catch (Exception e) { - throw new RuntimeException(e); + System.err.println("Database initialization failed: " + e.getMessage()); + throw new RuntimeException("Failed to initialize database", e); } } } diff --git a/src/main/java/org/example/DatabaseInitializer.java b/src/main/java/org/example/DatabaseInitializer.java index 3411fcae..52ee54ac 100644 --- a/src/main/java/org/example/DatabaseInitializer.java +++ b/src/main/java/org/example/DatabaseInitializer.java @@ -24,37 +24,38 @@ public void init() { } List searches = List.of("the+war+on+drugs", - "refused", - "thrice", - "16+horsepower", - "viagra+boys", - "geese", - "ghost", - "run+the+jewels", - "rammstein", - "salvatore+ganacci", - "baroness" + "refused", + "thrice", + "16+horsepower", + "viagra+boys", + "geese", + "ghost", + "run+the+jewels", + "rammstein", + "salvatore+ganacci", + "baroness" ); for (String term : searches) { try { apiClient.searchSongs(term).forEach(dto -> { - Song s = Song.fromDTO(dto); - Album al = Album.fromDTO(dto); Artist ar = Artist.fromDTO(dto); -// s.setTitle(dto.trackName()); -// s.setSongId((long) dto.trackId()); -// s.setLength((long) dto.trackTimeMillis()); - songRepo.save(s); - songRepo.save(al); - songRepo.save(ar); - + if (!songRepo.existsByUniqueId(ar)) { + songRepo.save(ar); + } + + Album al = Album.fromDTO(dto, ar); + if (!songRepo.existsByUniqueId(al)) { + songRepo.save(al); + } + + Song s = Song.fromDTO(dto, al); + if (!songRepo.existsByUniqueId(s)) { + songRepo.save(s); + } }); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to fetch or persist data for search term: " + term, e); } } } - - } - diff --git a/src/main/java/org/example/ItunesApiClient.java b/src/main/java/org/example/ItunesApiClient.java index 8477d3f6..b0184f0f 100644 --- a/src/main/java/org/example/ItunesApiClient.java +++ b/src/main/java/org/example/ItunesApiClient.java @@ -10,6 +10,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -26,13 +27,13 @@ public ItunesApiClient() { public List searchSongs(String term) throws Exception { - String encodedTerm = URLEncoder.encode(term, StandardCharsets.UTF_8); - String url = "https://itunes.apple.com/search?term=" + encodedTerm + "&entity=song&attribute=artistTerm&limit=3"; + String url = "https://itunes.apple.com/search?term=" + encodedTerm + "&entity=song&attribute=artistTerm&limit=8"; HttpRequest request = HttpRequest.newBuilder() .GET() .uri(URI.create(url)) + .timeout(Duration.ofSeconds(10)) .build(); HttpResponse response = @@ -43,9 +44,12 @@ public List searchSongs(String term) throws Exception { throw new RuntimeException("API-fel: " + response.statusCode()); } - // Parsar JSON + // Parse JSON JsonNode root = mapper.readTree(response.body()); JsonNode results = root.get("results"); + if (results == null || !results.isArray()) { + return List.of(); + } List songs = new ArrayList<>(); for (JsonNode node : results) { diff --git a/src/main/java/org/example/ItunesDTO.java b/src/main/java/org/example/ItunesDTO.java index 1540a5e6..c85e89ac 100644 --- a/src/main/java/org/example/ItunesDTO.java +++ b/src/main/java/org/example/ItunesDTO.java @@ -5,18 +5,19 @@ import java.time.LocalDate; @JsonIgnoreProperties(ignoreUnknown = true) -public record ItunesDTO (Long artistId, - Long collectionId, - Long trackId, - String trackName, - String artistName, - String collectionName, - String country, - String primaryGenreName, - LocalDate releaseDate, - Long trackCount, - Long trackTimeMillis) { +public record ItunesDTO(Long artistId, + Long collectionId, + Long trackId, + String trackName, + String artistName, + String collectionName, + String country, + String primaryGenreName, + LocalDate releaseDate, + Long trackCount, + Long trackTimeMillis) { - public int releaseYear() { return releaseDate.getYear(); } + public int releaseYear() { + return releaseDate != null ? releaseDate.getYear() : 0; + } } - diff --git a/src/main/java/org/example/SongRepositoryImpl.java b/src/main/java/org/example/SongRepositoryImpl.java index 2463b738..79569414 100644 --- a/src/main/java/org/example/SongRepositoryImpl.java +++ b/src/main/java/org/example/SongRepositoryImpl.java @@ -10,8 +10,7 @@ public class SongRepositoryImpl implements SongRepository { - private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); - + private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); @Override public List findSongByArtist() { @@ -20,50 +19,51 @@ public List findSongByArtist() { @Override public Long count() { - return emf.createEntityManager() - .createQuery("select count(s) from Song s", Long.class) - .getSingleResult(); + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(s) from Song s", Long.class) + .getSingleResult(); + } } @Override public boolean existsByUniqueId(Song song) { - return emf.createEntityManager() - .createQuery("select count(s) from Song s where s.songId = songId", Long.class) - .getSingleResult() > 0; + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(s) from Song s where s.songId = :songId", Long.class) + .setParameter("songId", song.getSongId()) + .getSingleResult() > 0; + } } @Override public boolean existsByUniqueId(Album album) { - return emf.createEntityManager() - .createQuery("select count(a) from Album a where a.albumId = albumId", Long.class) - .getSingleResult() > 0; + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(a) from Album a where a.albumId = :albumId", Long.class) + .setParameter("albumId", album.getAlbumId()) + .getSingleResult() > 0; + } } @Override public boolean existsByUniqueId(Artist artist) { - return emf.createEntityManager() - .createQuery("select count(a) from Artist a where a.artistId = artistId", Long.class) - .getSingleResult() > 0; + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(a) from Artist a where a.artistId = :artistId", Long.class) + .setParameter("artistId", artist.getArtistId()) + .getSingleResult() > 0; + } } - @Override public void save(Song song) { - if(!existsByUniqueId(song)) - emf.runInTransaction(em -> em.persist(song)); - + emf.runInTransaction(em -> em.persist(song)); } @Override public void save(Album album) { - if(!existsByUniqueId(album)) - emf.runInTransaction(em -> em.persist(album)); + emf.runInTransaction(em -> em.persist(album)); } @Override public void save(Artist artist) { - if(!existsByUniqueId(artist)) - emf.runInTransaction(em -> em.persist(artist)); + emf.runInTransaction(em -> em.persist(artist)); } } - diff --git a/src/main/java/org/example/entity/Album.java b/src/main/java/org/example/entity/Album.java index 9b49b171..1de7efeb 100644 --- a/src/main/java/org/example/entity/Album.java +++ b/src/main/java/org/example/entity/Album.java @@ -5,6 +5,7 @@ import org.hibernate.proxy.HibernateProxy; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -23,29 +24,38 @@ public class Album { private Long trackCount; - @OneToMany(mappedBy = "album") - private List song; +// @OneToMany(mappedBy = "album") + //private List song; + + // @ManyToOne(cascade = CascadeType.PERSIST) + // @JoinColumn(name="artist_id") + // private Artist artist; @ManyToOne - @JoinColumn(name="artist_id") + @JoinColumn(name = "artist_id") private Artist artist; + @OneToMany(mappedBy = "album", cascade = CascadeType.ALL, orphanRemoval = true) + private List song = new ArrayList<>(); + protected Album (){} - public Album(Long albumId, String name, String genre, int year, Long trackCount) { + public Album(Long albumId, String name, String genre, int year, Long trackCount, Artist artist) { this.albumId = albumId; this.name = name; this.genre = genre; this.year = year; this.trackCount = trackCount; + this.artist = artist; } - public static Album fromDTO(ItunesDTO dto) { - return new Album(dto.collectionId(), dto.collectionName(), dto.primaryGenreName(), dto.releaseYear(), dto.trackCount()); + public static Album fromDTO(ItunesDTO dto, Artist artist) { + if (dto.collectionId() == null || dto.collectionName() == null){ + throw new IllegalArgumentException("Required fields (albumId, albumName) cannot be null"); + } + return new Album(dto.collectionId(), dto.collectionName(), dto.primaryGenreName(), dto.releaseYear(), dto.trackCount(),artist); } - - public Long getAlbumId() { return albumId; } diff --git a/src/main/java/org/example/entity/Artist.java b/src/main/java/org/example/entity/Artist.java index 5a14e93a..0576f63c 100644 --- a/src/main/java/org/example/entity/Artist.java +++ b/src/main/java/org/example/entity/Artist.java @@ -4,6 +4,7 @@ import org.example.ItunesDTO; import org.hibernate.proxy.HibernateProxy; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -11,25 +12,29 @@ public class Artist { @Id - @Column(name="artist_id") + @Column(name = "artist_id") private Long artistId; private String name; private String country; + + @OneToMany(mappedBy = "artist", cascade = CascadeType.ALL, orphanRemoval = true) + private List album = new ArrayList<>(); - @OneToMany(mappedBy = "artist") - private List album; - - protected Artist (){} + protected Artist() { + } - public Artist (Long artistId, String name, String country) { + public Artist(Long artistId, String name, String country) { this.artistId = artistId; this.name = name; this.country = country; } public static Artist fromDTO(ItunesDTO dto) { + if (dto.artistId() == null || dto.artistName() == null) { + throw new IllegalArgumentException("Required fields (artistId, artistName) cannot be null"); + } return new Artist(dto.artistId(), dto.artistName(), dto.country()); } diff --git a/src/main/java/org/example/entity/Song.java b/src/main/java/org/example/entity/Song.java index b73820bb..5e2e21cb 100644 --- a/src/main/java/org/example/entity/Song.java +++ b/src/main/java/org/example/entity/Song.java @@ -13,7 +13,7 @@ public class Song { @Id - @Column(name="song_id") + @Column(name = "song_id") private Long songId; private String title; @@ -27,17 +27,21 @@ public class Song { @ManyToMany(mappedBy = "songs") private Set playlist = new HashSet<>(); - protected Song() {} + protected Song() { + } - public Song(Long songId, String title, Long length) { + public Song(Long songId, String title, Long length, Album album) { this.songId = songId; this.title = title; this.length = length; - + this.album = album; } - public static Song fromDTO(ItunesDTO dto) { - return new Song(dto.trackId(), dto.trackName(), dto.trackTimeMillis()); + public static Song fromDTO(ItunesDTO dto, Album album) { + if (dto.trackId() == null || dto.trackName() == null) { + throw new IllegalArgumentException("Required fields (trackId, trackName) cannot be null"); + } + return new Song(dto.trackId(), dto.trackName(), dto.trackTimeMillis(), album); } public Set getPlaylist() { @@ -97,9 +101,6 @@ public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); } - - //Todo: Generate toString? - @Override public String toString() { return getClass().getSimpleName() + "(" + From 5733aee0621b2d8ba004cf6e806ae0b72a21e87a Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Thu, 18 Dec 2025 13:33:22 +0100 Subject: [PATCH 09/29] repo split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Mattias Hagström Co-authored-by: Simon Forsberg Co-authored-by: Johan Briger --- src/main/java/org/example/App.java | 5 +- .../java/org/example/DatabaseInitializer.java | 18 ++++--- .../org/example/repo/AlbumRepository.java | 21 ++++++++ .../org/example/repo/AlbumRepositoryImpl.java | 49 +++++++++++++++++++ .../org/example/repo/ArtistRepository.java | 18 +++++++ .../example/repo/ArtistRepositoryImpl.java | 37 ++++++++++++++ .../org/example/repo/PlaylistRepository.java | 23 +++++++++ .../example/repo/PlaylistRepositoryImpl.java | 44 +++++++++++++++++ .../example/{ => repo}/SongRepository.java | 16 +++--- .../{ => repo}/SongRepositoryImpl.java | 29 +---------- 10 files changed, 218 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/example/repo/AlbumRepository.java create mode 100644 src/main/java/org/example/repo/AlbumRepositoryImpl.java create mode 100644 src/main/java/org/example/repo/ArtistRepository.java create mode 100644 src/main/java/org/example/repo/ArtistRepositoryImpl.java create mode 100644 src/main/java/org/example/repo/PlaylistRepository.java create mode 100644 src/main/java/org/example/repo/PlaylistRepositoryImpl.java rename src/main/java/org/example/{ => repo}/SongRepository.java (55%) rename src/main/java/org/example/{ => repo}/SongRepositoryImpl.java (52%) diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 14c919f2..891b71a9 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -1,18 +1,21 @@ package org.example; import jakarta.persistence.EntityManagerFactory; +import org.example.repo.*; public class App { public static void main(String[] args) { ItunesApiClient apiClient = new ItunesApiClient(); SongRepository songRepo = new SongRepositoryImpl(); + AlbumRepository albumRepo = new AlbumRepositoryImpl(); + ArtistRepository artistRepo = new ArtistRepositoryImpl(); try { EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); if (!emf.isOpen()) { throw new IllegalStateException("EntityManagerFactory is not open"); } - DatabaseInitializer initializer = new DatabaseInitializer(apiClient, songRepo); + DatabaseInitializer initializer = new DatabaseInitializer(apiClient, songRepo, albumRepo, artistRepo); initializer.init(); System.out.println("Database initialization completed successfully"); } catch (Exception e) { diff --git a/src/main/java/org/example/DatabaseInitializer.java b/src/main/java/org/example/DatabaseInitializer.java index 52ee54ac..92eda0f4 100644 --- a/src/main/java/org/example/DatabaseInitializer.java +++ b/src/main/java/org/example/DatabaseInitializer.java @@ -3,8 +3,10 @@ import org.example.entity.Album; import org.example.entity.Artist; import org.example.entity.Song; +import org.example.repo.AlbumRepository; +import org.example.repo.ArtistRepository; +import org.example.repo.SongRepository; -import java.util.ArrayList; import java.util.List; public class DatabaseInitializer { @@ -12,10 +14,14 @@ public class DatabaseInitializer { private final ItunesApiClient apiClient; private final SongRepository songRepo; + private final AlbumRepository albumRepo; + private final ArtistRepository artistRepo; - public DatabaseInitializer(ItunesApiClient apiClient, SongRepository songRepo) { + public DatabaseInitializer(ItunesApiClient apiClient, SongRepository songRepo , AlbumRepository albumRepo, ArtistRepository artistRepo) { this.apiClient = apiClient; this.songRepo = songRepo; + this.albumRepo = albumRepo; + this.artistRepo = artistRepo; } public void init() { @@ -39,13 +45,13 @@ public void init() { try { apiClient.searchSongs(term).forEach(dto -> { Artist ar = Artist.fromDTO(dto); - if (!songRepo.existsByUniqueId(ar)) { - songRepo.save(ar); + if (!artistRepo.existsByUniqueId(ar)) { + artistRepo.save(ar); } Album al = Album.fromDTO(dto, ar); - if (!songRepo.existsByUniqueId(al)) { - songRepo.save(al); + if (!albumRepo.existsByUniqueId(al)) { + albumRepo.save(al); } Song s = Song.fromDTO(dto, al); diff --git a/src/main/java/org/example/repo/AlbumRepository.java b/src/main/java/org/example/repo/AlbumRepository.java new file mode 100644 index 00000000..e9551505 --- /dev/null +++ b/src/main/java/org/example/repo/AlbumRepository.java @@ -0,0 +1,21 @@ +package org.example.repo; + +import org.example.entity.Album; +import org.example.entity.Artist; + +import java.util.List; + +public interface AlbumRepository { + + boolean existsByUniqueId(Album album); + + void save(Album album); + + Long count(); + + List findAll(); + + Album findByArtist(Artist artist); + + Album findByGenre(String genre); +} diff --git a/src/main/java/org/example/repo/AlbumRepositoryImpl.java b/src/main/java/org/example/repo/AlbumRepositoryImpl.java new file mode 100644 index 00000000..5e248caf --- /dev/null +++ b/src/main/java/org/example/repo/AlbumRepositoryImpl.java @@ -0,0 +1,49 @@ +package org.example.repo; + +import jakarta.persistence.EntityManagerFactory; +import org.example.PersistenceManager; +import org.example.entity.Album; +import org.example.entity.Artist; + +import java.util.List; + +public class AlbumRepositoryImpl implements AlbumRepository{ + + private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); + + @Override + public void save(Album album) { + emf.runInTransaction(em -> em.persist(album)); + } + + @Override + public Long count() { + return 0L; + } + + @Override + public List findAll() { + return List.of(); + } + + @Override + public Album findByArtist(Artist artist) { + return null; + } + + @Override + public Album findByGenre(String genre) { + return null; + } + + @Override + public boolean existsByUniqueId(Album album) { + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(a) from Album a where a.albumId = :albumId", Long.class) + .setParameter("albumId", album.getAlbumId()) + .getSingleResult() > 0; + } + } + + +} diff --git a/src/main/java/org/example/repo/ArtistRepository.java b/src/main/java/org/example/repo/ArtistRepository.java new file mode 100644 index 00000000..f3bfea5d --- /dev/null +++ b/src/main/java/org/example/repo/ArtistRepository.java @@ -0,0 +1,18 @@ +package org.example.repo; + +import org.example.entity.Artist; + +import java.util.List; + +public interface ArtistRepository { + + boolean existsByUniqueId(Artist artist); + + void save(Artist artist); + + Long count(); + + List findAll(); + + +} diff --git a/src/main/java/org/example/repo/ArtistRepositoryImpl.java b/src/main/java/org/example/repo/ArtistRepositoryImpl.java new file mode 100644 index 00000000..351609fb --- /dev/null +++ b/src/main/java/org/example/repo/ArtistRepositoryImpl.java @@ -0,0 +1,37 @@ +package org.example.repo; + +import jakarta.persistence.EntityManagerFactory; +import org.example.PersistenceManager; +import org.example.entity.Artist; + +import java.util.List; + +public class ArtistRepositoryImpl implements ArtistRepository{ + + private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); + + @Override + public void save(Artist artist) { + emf.runInTransaction(em -> em.persist(artist)); + } + + @Override + public Long count() { + return 0L; + } + + @Override + public List findAll() { + return List.of(); + } + + + @Override + public boolean existsByUniqueId(Artist artist) { + try (var em = emf.createEntityManager()) { + return em.createQuery("select count(a) from Artist a where a.artistId = :artistId", Long.class) + .setParameter("artistId", artist.getArtistId()) + .getSingleResult() > 0; + } + } +} diff --git a/src/main/java/org/example/repo/PlaylistRepository.java b/src/main/java/org/example/repo/PlaylistRepository.java new file mode 100644 index 00000000..61512ca3 --- /dev/null +++ b/src/main/java/org/example/repo/PlaylistRepository.java @@ -0,0 +1,23 @@ +package org.example.repo; + +import org.example.entity.Playlist; +import org.example.entity.Song; + +import java.util.List; + +public interface PlaylistRepository { + + List findAll(); + + List findSongsInPlaylist(Playlist playlist); + + void createPlaylist(String name); + + void deletePlaylist(Playlist playlist); + + void addSong(Playlist playlist, Song song); + + void removeSong(Playlist playlist, Song song); + + void renamePlaylist(Playlist playlist, String newName); +} diff --git a/src/main/java/org/example/repo/PlaylistRepositoryImpl.java b/src/main/java/org/example/repo/PlaylistRepositoryImpl.java new file mode 100644 index 00000000..abd4cb62 --- /dev/null +++ b/src/main/java/org/example/repo/PlaylistRepositoryImpl.java @@ -0,0 +1,44 @@ +package org.example.repo; + +import org.example.entity.Playlist; +import org.example.entity.Song; + +import java.util.List; + +public class PlaylistRepositoryImpl implements PlaylistRepository { + + @Override + public List findAll() { + return List.of(); + } + + @Override + public List findSongsInPlaylist(Playlist playlist) { + return List.of(); + } + + @Override + public void createPlaylist(String name) { + + } + + @Override + public void deletePlaylist(Playlist playlist) { + + } + + @Override + public void addSong(Playlist playlist, Song song) { + + } + + @Override + public void removeSong(Playlist playlist, Song song) { + + } + + @Override + public void renamePlaylist(Playlist playlist, String newName) { + + } +} diff --git a/src/main/java/org/example/SongRepository.java b/src/main/java/org/example/repo/SongRepository.java similarity index 55% rename from src/main/java/org/example/SongRepository.java rename to src/main/java/org/example/repo/SongRepository.java index 62c0b0d0..0eeccc04 100644 --- a/src/main/java/org/example/SongRepository.java +++ b/src/main/java/org/example/repo/SongRepository.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.repo; import org.example.entity.Album; import org.example.entity.Artist; @@ -7,19 +7,19 @@ import java.util.List; public interface SongRepository { - List findSongByArtist(); - Long count(); boolean existsByUniqueId(Song song); - boolean existsByUniqueId(Album album); + void save(Song song); + + List findAll(); - boolean existsByUniqueId(Artist artist); + List findByArtist(Artist artist); - void save(Song song); + // Redundant? + List findByAlbum(Album album); - void save(Album album); + List findByGenre(String genre); - void save(Artist artist); } diff --git a/src/main/java/org/example/SongRepositoryImpl.java b/src/main/java/org/example/repo/SongRepositoryImpl.java similarity index 52% rename from src/main/java/org/example/SongRepositoryImpl.java rename to src/main/java/org/example/repo/SongRepositoryImpl.java index 79569414..e17af356 100644 --- a/src/main/java/org/example/SongRepositoryImpl.java +++ b/src/main/java/org/example/repo/SongRepositoryImpl.java @@ -1,7 +1,7 @@ -package org.example; +package org.example.repo; import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.Query; +import org.example.PersistenceManager; import org.example.entity.Album; import org.example.entity.Artist; import org.example.entity.Song; @@ -34,36 +34,11 @@ public boolean existsByUniqueId(Song song) { } } - @Override - public boolean existsByUniqueId(Album album) { - try (var em = emf.createEntityManager()) { - return em.createQuery("select count(a) from Album a where a.albumId = :albumId", Long.class) - .setParameter("albumId", album.getAlbumId()) - .getSingleResult() > 0; - } - } - - @Override - public boolean existsByUniqueId(Artist artist) { - try (var em = emf.createEntityManager()) { - return em.createQuery("select count(a) from Artist a where a.artistId = :artistId", Long.class) - .setParameter("artistId", artist.getArtistId()) - .getSingleResult() > 0; - } - } @Override public void save(Song song) { emf.runInTransaction(em -> em.persist(song)); } - @Override - public void save(Album album) { - emf.runInTransaction(em -> em.persist(album)); - } - @Override - public void save(Artist artist) { - emf.runInTransaction(em -> em.persist(artist)); - } } From da32a4a0641cb410339ff62dc04cf49ddfda9f6b Mon Sep 17 00:00:00 2001 From: Jesper Larsson Date: Thu, 18 Dec 2025 13:46:22 +0100 Subject: [PATCH 10/29] repo split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Mattias Hagström Co-authored-by: Simon Forsberg Co-authored-by: Johan Briger --- .../org/example/repo/SongRepositoryImpl.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/repo/SongRepositoryImpl.java b/src/main/java/org/example/repo/SongRepositoryImpl.java index e17af356..6626abd6 100644 --- a/src/main/java/org/example/repo/SongRepositoryImpl.java +++ b/src/main/java/org/example/repo/SongRepositoryImpl.java @@ -12,10 +12,6 @@ public class SongRepositoryImpl implements SongRepository { private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); - @Override - public List findSongByArtist() { - return List.of(); - } @Override public Long count() { @@ -40,5 +36,25 @@ public void save(Song song) { emf.runInTransaction(em -> em.persist(song)); } + @Override + public List findAll() { + return List.of(); + } + + @Override + public List findByArtist(Artist artist) { + return List.of(); + } + + @Override + public List findByAlbum(Album album) { + return List.of(); + } + + @Override + public List findByGenre(String genre) { + return List.of(); + } + } From 583d9c9e4cbdd5c3c773c0dccc3f31e58dc35a38 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 18 Dec 2025 18:01:33 +0100 Subject: [PATCH 11/29] Add implementation for AlbumRepositoryl, add implementation for ArtistRepository --- .../java/org/example/PersistenceManager.java | 7 +-- .../org/example/repo/AlbumRepository.java | 8 ++-- .../org/example/repo/AlbumRepositoryImpl.java | 46 +++++++++++-------- .../org/example/repo/ArtistRepository.java | 3 +- .../example/repo/ArtistRepositoryImpl.java | 31 +++++++------ 5 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/example/PersistenceManager.java b/src/main/java/org/example/PersistenceManager.java index a75fafc0..817c23dd 100644 --- a/src/main/java/org/example/PersistenceManager.java +++ b/src/main/java/org/example/PersistenceManager.java @@ -9,8 +9,6 @@ import java.util.List; - - public class PersistenceManager { private static final EntityManagerFactory emf; @@ -40,10 +38,7 @@ public static EntityManagerFactory getEntityManagerFactory() { return emf; } - - - -//Not invented here!! + //Not invented here!! private static List> getEntities(String pkg) { List> entities; try (ScanResult scanResult = diff --git a/src/main/java/org/example/repo/AlbumRepository.java b/src/main/java/org/example/repo/AlbumRepository.java index e9551505..7130c18f 100644 --- a/src/main/java/org/example/repo/AlbumRepository.java +++ b/src/main/java/org/example/repo/AlbumRepository.java @@ -11,11 +11,11 @@ public interface AlbumRepository { void save(Album album); - Long count(); - List findAll(); - Album findByArtist(Artist artist); + List findByArtist(Artist artist); - Album findByGenre(String genre); + List findByGenre(String genre); + + Long count(); } diff --git a/src/main/java/org/example/repo/AlbumRepositoryImpl.java b/src/main/java/org/example/repo/AlbumRepositoryImpl.java index 5e248caf..fd1f4f16 100644 --- a/src/main/java/org/example/repo/AlbumRepositoryImpl.java +++ b/src/main/java/org/example/repo/AlbumRepositoryImpl.java @@ -7,43 +7,53 @@ import java.util.List; -public class AlbumRepositoryImpl implements AlbumRepository{ +public class AlbumRepositoryImpl implements AlbumRepository { private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); @Override - public void save(Album album) { - emf.runInTransaction(em -> em.persist(album)); + public boolean existsByUniqueId(Album album) { + return emf.callInTransaction(em -> + em.createQuery("select count(a) from Album a where a.albumId = :albumId", Long.class) + .setParameter("albumId", album.getAlbumId()) + .getSingleResult() > 0 + ); } @Override - public Long count() { - return 0L; + public void save(Album album) { + emf.runInTransaction(em -> em.persist(album)); } @Override public List findAll() { - return List.of(); + return emf.callInTransaction(em -> + em.createQuery("select a from Album a", Album.class) + .getResultList()); } @Override - public Album findByArtist(Artist artist) { - return null; + public List findByArtist(Artist artist) { + return emf.callInTransaction(em -> + em.createQuery("select a from Album a where a.artist = :artist", Album.class) + .setParameter("artist", artist) + .getResultList() + ); } @Override - public Album findByGenre(String genre) { - return null; + public List findByGenre(String genre) { + return emf.callInTransaction(em -> + em.createQuery("select a from Album a where a.genre = :genre", Album.class) + .setParameter("genre", genre) + .getResultList() + ); } @Override - public boolean existsByUniqueId(Album album) { - try (var em = emf.createEntityManager()) { - return em.createQuery("select count(a) from Album a where a.albumId = :albumId", Long.class) - .setParameter("albumId", album.getAlbumId()) - .getSingleResult() > 0; - } + public Long count() { + return emf.callInTransaction(em -> + em.createQuery("select count(a) from Album a", Long.class) + .getSingleResult()); } - - } diff --git a/src/main/java/org/example/repo/ArtistRepository.java b/src/main/java/org/example/repo/ArtistRepository.java index f3bfea5d..8c2ff72d 100644 --- a/src/main/java/org/example/repo/ArtistRepository.java +++ b/src/main/java/org/example/repo/ArtistRepository.java @@ -10,9 +10,8 @@ public interface ArtistRepository { void save(Artist artist); - Long count(); - List findAll(); + Long count(); } diff --git a/src/main/java/org/example/repo/ArtistRepositoryImpl.java b/src/main/java/org/example/repo/ArtistRepositoryImpl.java index 351609fb..2ab2047b 100644 --- a/src/main/java/org/example/repo/ArtistRepositoryImpl.java +++ b/src/main/java/org/example/repo/ArtistRepositoryImpl.java @@ -2,36 +2,41 @@ import jakarta.persistence.EntityManagerFactory; import org.example.PersistenceManager; +import org.example.entity.Album; import org.example.entity.Artist; +import org.example.entity.Song; import java.util.List; -public class ArtistRepositoryImpl implements ArtistRepository{ +public class ArtistRepositoryImpl implements ArtistRepository { private final EntityManagerFactory emf = PersistenceManager.getEntityManagerFactory(); @Override - public void save(Artist artist) { - emf.runInTransaction(em -> em.persist(artist)); + public boolean existsByUniqueId(Artist artist) { + return emf.callInTransaction(em -> + em.createQuery("select count(a) from Artist a where a.artistId = :artistId", Long.class) + .setParameter("artistId", artist.getArtistId()) + .getSingleResult() > 0 + ); } @Override - public Long count() { - return 0L; + public void save(Artist artist) { + emf.runInTransaction(em -> em.persist(artist)); } @Override public List findAll() { - return List.of(); + return emf.callInTransaction(em -> + em.createQuery("select a from Artist a", Artist.class) + .getResultList()); } - @Override - public boolean existsByUniqueId(Artist artist) { - try (var em = emf.createEntityManager()) { - return em.createQuery("select count(a) from Artist a where a.artistId = :artistId", Long.class) - .setParameter("artistId", artist.getArtistId()) - .getSingleResult() > 0; - } + public Long count() { + return emf.callInTransaction(em -> + em.createQuery("select count(a) from Artist a", Long.class) + .getSingleResult()); } } From b1fc312a338dab5f54c3a1d5f5fa7eba206cbfbc Mon Sep 17 00:00:00 2001 From: mattknatt Date: Thu, 18 Dec 2025 18:21:42 +0100 Subject: [PATCH 12/29] Implements methods from SongRepository --- .../org/example/repo/SongRepositoryImpl.java | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/example/repo/SongRepositoryImpl.java b/src/main/java/org/example/repo/SongRepositoryImpl.java index 6626abd6..b80e4696 100644 --- a/src/main/java/org/example/repo/SongRepositoryImpl.java +++ b/src/main/java/org/example/repo/SongRepositoryImpl.java @@ -6,6 +6,7 @@ import org.example.entity.Artist; import org.example.entity.Song; +import java.util.ArrayList; import java.util.List; public class SongRepositoryImpl implements SongRepository { @@ -38,23 +39,66 @@ public void save(Song song) { @Override public List findAll() { - return List.of(); + return emf.callInTransaction(em -> + em.createQuery("select s from Song s", Song.class) + .getResultList()); } @Override public List findByArtist(Artist artist) { - return List.of(); + if (artist == null) return new ArrayList<>(); + + return emf.callInTransaction(em -> + em.createQuery( + """ + select s + from Song s + join fetch s.album a + join fetch a.artist art + where art = :artist + """, + Song.class + ) + .setParameter("artist", artist) + .getResultList()); } + @Override public List findByAlbum(Album album) { - return List.of(); + if (album == null) return new ArrayList<>(); + + return emf.callInTransaction(em -> + em.createQuery( + """ + select s + from Song s + join fetch s.album a + join fetch a.artist art + where a = :album + """, + Song.class + ) + .setParameter("album", album) + .getResultList()); } @Override public List findByGenre(String genre) { - return List.of(); - } - + if (genre == null || genre.isBlank()) return new ArrayList<>(); + return emf.callInTransaction(em -> + em.createQuery( + """ + select s + from Song s + join fetch s.album a + join fetch a.artist art + where lower(a.genre) = lower(:genre) + """, + Song.class + ) + .setParameter("genre", genre) + .getResultList()); + } } From d1a4ba80cbb0d759d1daabc07b3164f796a37e23 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Thu, 18 Dec 2025 19:35:56 +0100 Subject: [PATCH 13/29] Updated pom.xml-file with JavaFX dependencies --- pom.xml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pom.xml b/pom.xml index 91a7807b..d68424a5 100644 --- a/pom.xml +++ b/pom.xml @@ -60,5 +60,49 @@ classgraph 4.8.184 + + org.openjfx + javafx-controls + 25 + + + org.openjfx + javafx-fxml + 25 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 25 + 25 + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + + + default-cli + + org.example.MyPod + app + app + app + true + true + true + + + + + + From 054eadf691223357760ea5675bbaa61ed74aea99 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Thu, 18 Dec 2025 19:38:42 +0100 Subject: [PATCH 14/29] Added Stylesheet Updated pom.xml-file with JavaFX dependencies --- src/main/resources/ipod_style.css | 83 +++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/main/resources/ipod_style.css diff --git a/src/main/resources/ipod_style.css b/src/main/resources/ipod_style.css new file mode 100644 index 00000000..057149f3 --- /dev/null +++ b/src/main/resources/ipod_style.css @@ -0,0 +1,83 @@ +/* Generella FĂ€rger */ +.ipod-body { + -fx-background-color: #D3D3D3; /* LjusgrĂ„ kropp */ +} + +/* --- SKÄRMEN --- */ +.ipod-screen { + -fx-background-color: #98fb98; /* Den klassiska grön-grĂ„ fĂ€rgen */ + -fx-border-color: #333; + -fx-border-width: 3; + -fx-pref-width: 260px; /* Fast bredd */ + -fx-pref-height: 180px; /* Fast höjd */ + -fx-max-height: 180px; + -fx-font-family: "Lucida Console", Monaco, monospace; /* Retro typsnitt */ +} + +.scroll-pane { + -fx-background-color: transparent; +} + +.screen-title { + -fx-text-fill: #333333; + -fx-font-size: 14pt; + -fx-font-weight: bold; + -fx-padding: 0 0 5px 0; /* Marginal under titeln */ +} + +.menu-item { + -fx-text-fill: #333333; /* Svart text */ + -fx-font-size: 11pt; + -fx-padding: 2px 5px 2px 5px; +} + +/* Den inverterade, markerade raden */ +.selected-item { + -fx-background-color: #333333; /* Svart bakgrund */ + -fx-text-fill: #ffffff; /* Vit text */ +} + +/* --- KLICKHJULET (Simulering) --- */ + +/* Den yttre grĂ„ ringen */ +.outer-wheel { + -fx-fill: #dddddd; /* Ljusare grĂ„ */ + -fx-stroke: #aaaaaa; + -fx-stroke-width: 2px; +} + +/* Mittenknappen (Select) */ +.center-button { + -fx-fill: #e5e5e5; /* Mycket ljusgrĂ„ */ + -fx-stroke: #aaaaaa; + -fx-stroke-width: 1px; +} + +/* Text pĂ„ hjulet (MENU, >>, <<, >) */ +.wheel-text { + -fx-fill: #333333; + -fx-font-size: 10pt; + -fx-font-weight: bold; +} +.wheel-text-menu { + -fx-font-size: 10pt; + -fx-font-weight: bold; + -fx-translate-y: -70px; /* Flyttar texten uppĂ„t till MENU-positionen */ +} +.wheel-text { + -fx-font-size: 10pt; + -fx-font-weight: bold; +} +.wheel-text-play { + -fx-font-size: 10pt; + -fx-font-weight: bold; + -fx-translate-y: 70px; /* Flyttar texten nedĂ„t till PLAY-positionen */ +} +/* Placera de horisontella knapparna */ +#ff-button { /* >> */ + -fx-translate-x: 70px; +} + +#rew-button { /* << */ + -fx-translate-x: -70px; +} From bc75e641adef2afb496ad02ca6e71b38acd9d252 Mon Sep 17 00:00:00 2001 From: Johan Briger Date: Thu, 18 Dec 2025 19:55:44 +0100 Subject: [PATCH 15/29] Added MyPod.java for UI Added Stylesheet Updated pom.xml-file with JavaFX dependencies --- src/main/java/org/example/App.java | 2 + src/main/java/org/example/MyPod.java | 243 +++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 src/main/java/org/example/MyPod.java diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 891b71a9..c0efd2e6 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -22,5 +22,7 @@ public static void main(String[] args) { System.err.println("Database initialization failed: " + e.getMessage()); throw new RuntimeException("Failed to initialize database", e); } + + } } diff --git a/src/main/java/org/example/MyPod.java b/src/main/java/org/example/MyPod.java new file mode 100644 index 00000000..01e32d5c --- /dev/null +++ b/src/main/java/org/example/MyPod.java @@ -0,0 +1,243 @@ +package org.example; +import jakarta.persistence.EntityManagerFactory; +import javafx.application.Application; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; +import javafx.stage.Stage; +import org.example.entity.Album; +import org.example.entity.Artist; +import org.example.entity.Song; +import org.example.repo.*; + +import java.util.ArrayList; +import java.util.List; + +public class MyPod extends Application{ + + + // AnvĂ€nd dina existerande komponenter frĂ„n projektet + private final SongRepository songRepo = new SongRepositoryImpl(); + private final ArtistRepository artistRepo = new ArtistRepositoryImpl(); + private final AlbumRepository albumRepo = new AlbumRepositoryImpl(); + private final ItunesApiClient apiClient = new ItunesApiClient(); + + private List songs; + private List artists; + private List albums; + + private final ObservableList mainMenu = FXCollections.observableArrayList( + "Songs", "Artists", "Albums", "Playlists"); + + private final List