Implement trackhasartist relationship management

Add complete implementation for track-artist relationship management,
  following the established albumhasgenre pattern. This enables tracking
  which artists are associated with each track in the media library.

  Changes:
  - Fix TrackHasArtist model: Rename from TrackHasArtistt.java (typo)
    and correct table name from "trackhasgenre" to "trackhasartist"
  - Implement TrackHasArtistRepository with full CRUD operations
  - Implement TrackHasArtistService with business logic and validation
  - Add TrackHasArtistMapper for entity/protobuf conversion
  - Create action handlers:
    * CreateTrackHasArtistHandler (trackhasartist.create)
    * DeleteTrackHasArtistHandler (trackhasartist.delete)
    * GetTrackHasArtistByIdHandler (trackhasartist.getById)
    * GetTrackHasArtistHandler (trackhasartist.getAll)
  - Register TrackHasArtistService in DelegateActionManager for
    automatic handler discovery and dependency injection
  - Proto file (trackhasartist.proto) was already correct

  The implementation validates track and artist existence before creating
  relationships and provides proper error handling with appropriate HTTP
  status codes.

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

  Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda 2025-12-09 02:44:57 -03:00
parent e0799828d0
commit 7fd3c3e9f7
10 changed files with 535 additions and 0 deletions

View File

@ -0,0 +1,47 @@
package com.mediamanager.mapper;
import com.mediamanager.model.TrackHasArtist;
import com.mediamanager.protocol.messages.TrackHasArtistMessages;
public class TrackHasArtistMapper {
public static TrackHasArtistMessages.TrackHasArtist toProtobuf(TrackHasArtist entity) {
if (entity == null) {
return null;
}
TrackHasArtistMessages.TrackHasArtist.Builder builder = TrackHasArtistMessages.TrackHasArtist.newBuilder();
Integer id = entity.getId();
if (id != null) {
builder.setId(id);
}
// Map Track foreign key
if (entity.getTrack() != null && entity.getTrack().getId() != null) {
builder.setFkTrackId(entity.getTrack().getId());
}
// Map Artist foreign key
if (entity.getArtist() != null && entity.getArtist().getId() != null) {
builder.setFkArtistId(entity.getArtist().getId());
}
return builder.build();
}
public static TrackHasArtist toEntity(TrackHasArtistMessages.TrackHasArtist protobuf) {
if (protobuf == null) {
return null;
}
TrackHasArtist entity = new TrackHasArtist();
if (protobuf.getId() > 0) {
entity.setId(protobuf.getId());
}
// Note: Foreign key relationships (Track, Artist) are handled in the service layer
return entity;
}
}

View File

@ -0,0 +1,55 @@
package com.mediamanager.model;
import jakarta.persistence.*;
@Entity
@Table(name = "trackhasartist")
public class TrackHasArtist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_track_id", nullable = false)
private Track track;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_artist_id", nullable = false)
private Artist artist;
public TrackHasArtist() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Track getTrack() {
return track;
}
public void setTrack(Track track) {
this.track = track;
}
public Artist getArtist() {
return artist;
}
public void setArtist(Artist artist) {
this.artist = artist;
}
@Override
public String toString() {
return "TrackHasArtist{" +
"id=" + id +
", trackId=" + (track != null ? track.getId() : null) +
", artistId=" + (artist != null ? artist.getId() : null) +
'}';
}
}

View File

@ -0,0 +1,85 @@
package com.mediamanager.repository;
import com.mediamanager.model.TrackHasArtist;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Optional;
public class TrackHasArtistRepository {
private static final Logger logger = LogManager.getLogger(TrackHasArtistRepository.class);
private final EntityManagerFactory entityManagerFactory;
public TrackHasArtistRepository(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
public TrackHasArtist save(TrackHasArtist trackHasArtist) {
logger.debug("Saving TrackHasArtist: {}", trackHasArtist);
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
try {
em.persist(trackHasArtist);
em.getTransaction().commit();
logger.debug("TrackHasArtist has been saved successfully");
return trackHasArtist;
} catch (Exception e) {
em.getTransaction().rollback();
logger.error("Error while saving TrackHasArtist: {}", e.getMessage());
throw e;
} finally {
if (em.isOpen()) em.close();
}
}
public List<TrackHasArtist> findAll() {
logger.debug("Finding All TrackHasArtist");
EntityManager em = entityManagerFactory.createEntityManager();
try{
return em.createQuery("select t from TrackHasArtist t", TrackHasArtist.class).getResultList();
}finally {
if (em.isOpen()) em.close();
}
}
public Optional<TrackHasArtist> findById(Integer id) {
logger.debug("Finding TrackHasArtist with id: {}", id);
EntityManager em = entityManagerFactory.createEntityManager();
try{
TrackHasArtist trackHasArtist = em.find(TrackHasArtist.class, id);
return Optional.ofNullable(trackHasArtist);
}finally {
if (em.isOpen()) em.close();
}
}
public boolean deleteById(Integer id){
logger.debug("Deleting TrackHasArtist with id: {}", id);
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
try{
TrackHasArtist trackHasArtist = em.find(TrackHasArtist.class, id);
if (trackHasArtist == null) {
em.getTransaction().rollback();
return false;
}
em.remove(trackHasArtist);
em.getTransaction().commit();
logger.debug("TrackHasArtist has been deleted successfully");
return true;
} catch (Exception e) {
em.getTransaction().rollback();
logger.error("Error while deleting TrackHasArtist: {}", e.getMessage());
throw e;
} finally {
if (em.isOpen()) em.close();
}
}
}

View File

@ -13,6 +13,7 @@ import com.mediamanager.service.bitdepth.BitDepthService;
import com.mediamanager.service.bitrate.BitRateService; import com.mediamanager.service.bitrate.BitRateService;
import com.mediamanager.service.composer.ComposerService; import com.mediamanager.service.composer.ComposerService;
import com.mediamanager.service.trackhasgenre.TrackHasGenreService; import com.mediamanager.service.trackhasgenre.TrackHasGenreService;
import com.mediamanager.service.trackhasartist.TrackHasArtistService;
import com.mediamanager.repository.GenreRepository; import com.mediamanager.repository.GenreRepository;
import com.mediamanager.service.artist.ArtistService; import com.mediamanager.service.artist.ArtistService;
@ -117,6 +118,10 @@ public class DelegateActionManager {
TrackHasGenreService trackHasGenreService = new TrackHasGenreService(trackHasGenreRepository, trackRepository, genreRepository); TrackHasGenreService trackHasGenreService = new TrackHasGenreService(trackHasGenreRepository, trackRepository, genreRepository);
serviceLocator.register(TrackHasGenreService.class, trackHasGenreService); serviceLocator.register(TrackHasGenreService.class, trackHasGenreService);
TrackHasArtistRepository trackHasArtistRepository = new TrackHasArtistRepository(entityManagerFactory);
TrackHasArtistService trackHasArtistService = new TrackHasArtistService(trackHasArtistRepository, trackRepository, artistRepository);
serviceLocator.register(TrackHasArtistService.class, trackHasArtistService);
serviceLocator.logRegisteredServices(); serviceLocator.logRegisteredServices();
logger.info("Services initialized successfully"); logger.info("Services initialized successfully");

View File

@ -0,0 +1,54 @@
package com.mediamanager.service.delegate.handler.trackhasartist;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.TrackHasArtistMapper;
import com.mediamanager.model.TrackHasArtist;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.TrackHasArtistMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.trackhasartist.TrackHasArtistService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Action("trackhasartist.create")
public class CreateTrackHasArtistHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(CreateTrackHasArtistHandler.class);
private final TrackHasArtistService trackHasArtistService;
public CreateTrackHasArtistHandler(TrackHasArtistService trackHasArtistService) {
this.trackHasArtistService = trackHasArtistService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException {
try{
TrackHasArtistMessages.CreateTrackHasArtistRequest createRequest =
TrackHasArtistMessages.CreateTrackHasArtistRequest.parseFrom(requestPayload);
TrackHasArtist trackHasArtist = trackHasArtistService.createTrackHasArtist(
createRequest.getFkTrackId() > 0 ? createRequest.getFkTrackId() : null,
createRequest.getFkArtistId() > 0 ? createRequest.getFkArtistId() : null
);
TrackHasArtistMessages.TrackHasArtist trackHasArtistProto = TrackHasArtistMapper.toProtobuf(trackHasArtist);
TrackHasArtistMessages.CreateTrackHasArtistResponse createTrackHasArtistResponse = TrackHasArtistMessages.CreateTrackHasArtistResponse.newBuilder()
.setTrackhasartist(trackHasArtistProto)
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(createTrackHasArtistResponse.toByteString());
} catch (IllegalArgumentException e) {
logger.error("Validation error", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(400)
.setPayload(ByteString.copyFromUtf8("Validation error: " + e.getMessage()));
} catch (Exception e) {
logger.error("Error creating track has artist", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
}
}
}

View File

@ -0,0 +1,62 @@
package com.mediamanager.service.delegate.handler.trackhasartist;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.TrackHasArtistMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.trackhasartist.TrackHasArtistService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Action("trackhasartist.delete")
public class DeleteTrackHasArtistHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(DeleteTrackHasArtistHandler.class);
private final TrackHasArtistService trackHasArtistService;
public DeleteTrackHasArtistHandler(TrackHasArtistService trackHasArtistService) {
this.trackHasArtistService = trackHasArtistService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
throws InvalidProtocolBufferException {
try {
TrackHasArtistMessages.DeleteTrackHasArtistRequest deleteRequest =
TrackHasArtistMessages.DeleteTrackHasArtistRequest.parseFrom(requestPayload);
int id = deleteRequest.getId();
boolean success = trackHasArtistService.deleteTrackHasArtist(id);
TrackHasArtistMessages.DeleteTrackHasArtistResponse deleteResponse;
if (success) {
deleteResponse = TrackHasArtistMessages.DeleteTrackHasArtistResponse.newBuilder()
.setSuccess(true)
.setMessage("Track has artist deleted successfully")
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(deleteResponse.toByteString());
} else {
deleteResponse = TrackHasArtistMessages.DeleteTrackHasArtistResponse.newBuilder()
.setSuccess(false)
.setMessage("Track has artist not found")
.build();
return TransportProtocol.Response.newBuilder()
.setStatusCode(404)
.setPayload(deleteResponse.toByteString());
}
} catch (Exception e) {
logger.error("Error deleting track has artist", e);
TrackHasArtistMessages.DeleteTrackHasArtistResponse deleteResponse =
TrackHasArtistMessages.DeleteTrackHasArtistResponse.newBuilder()
.setSuccess(false)
.setMessage("Error: " + e.getMessage())
.build();
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(deleteResponse.toByteString());
}
}
}

View File

@ -0,0 +1,56 @@
package com.mediamanager.service.delegate.handler.trackhasartist;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.TrackHasArtistMapper;
import com.mediamanager.model.TrackHasArtist;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.TrackHasArtistMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.trackhasartist.TrackHasArtistService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Optional;
@Action(value = "trackhasartist.getById")
public class GetTrackHasArtistByIdHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(GetTrackHasArtistByIdHandler.class);
private final TrackHasArtistService trackHasArtistService;
public GetTrackHasArtistByIdHandler(TrackHasArtistService trackHasArtistService) {
this.trackHasArtistService = trackHasArtistService;
}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload)
throws InvalidProtocolBufferException{
try{
TrackHasArtistMessages.GetTrackHasArtistByIdRequest getByIdRequest =
TrackHasArtistMessages.GetTrackHasArtistByIdRequest.parseFrom(requestPayload);
int id = getByIdRequest.getId();
Optional<TrackHasArtist> trackHasArtistOpt = trackHasArtistService.getTrackHasArtistById(id);
if (trackHasArtistOpt.isEmpty()){
logger.warn("TrackHasArtist not found with ID: {}", id);
return TransportProtocol.Response.newBuilder()
.setStatusCode(404)
.setPayload(ByteString.copyFromUtf8("TrackHasArtist not found"));
}
TrackHasArtistMessages.TrackHasArtist trackHasArtistProto = TrackHasArtistMapper.toProtobuf(trackHasArtistOpt.get());
TrackHasArtistMessages.GetTrackHasArtistByIdResponse getByIdResponse = TrackHasArtistMessages.GetTrackHasArtistByIdResponse.newBuilder()
.setTrackhasartist(trackHasArtistProto)
.build();
return TransportProtocol.Response.newBuilder()
.setPayload(getByIdResponse.toByteString());
} catch (Exception e) {
logger.error("Error getting track has artist by ID", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: "+ e.getMessage()));
}
}
}

View File

@ -0,0 +1,48 @@
package com.mediamanager.service.delegate.handler.trackhasartist;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.mediamanager.mapper.TrackHasArtistMapper;
import com.mediamanager.model.TrackHasArtist;
import com.mediamanager.protocol.TransportProtocol;
import com.mediamanager.protocol.messages.TrackHasArtistMessages;
import com.mediamanager.service.delegate.ActionHandler;
import com.mediamanager.service.delegate.annotation.Action;
import com.mediamanager.service.trackhasartist.TrackHasArtistService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
@Action("trackhasartist.getAll")
public class GetTrackHasArtistHandler implements ActionHandler {
private static final Logger logger = LogManager.getLogger(GetTrackHasArtistHandler.class);
private final TrackHasArtistService trackHasArtistService;
public GetTrackHasArtistHandler(TrackHasArtistService trackHasArtistService){this.trackHasArtistService = trackHasArtistService;}
@Override
public TransportProtocol.Response.Builder handle(ByteString requestPayload) throws InvalidProtocolBufferException {
try{
List<TrackHasArtist> trackHasArtists = trackHasArtistService.getAllTrackHasArtists();
TrackHasArtistMessages.GetTrackHasArtistsResponse.Builder responseBuilder = TrackHasArtistMessages.GetTrackHasArtistsResponse.newBuilder();
for (TrackHasArtist trackHasArtist : trackHasArtists) {
TrackHasArtistMessages.TrackHasArtist trackHasArtistProto = TrackHasArtistMapper.toProtobuf(trackHasArtist);
responseBuilder.addTrackhasartist(trackHasArtistProto);
}
TrackHasArtistMessages.GetTrackHasArtistsResponse getTrackHasArtistsResponse = responseBuilder.build();
return TransportProtocol.Response.newBuilder()
.setPayload(getTrackHasArtistsResponse.toByteString());
}catch (Exception e){
logger.error("Error getting track has artists", e);
return TransportProtocol.Response.newBuilder()
.setStatusCode(500)
.setPayload(ByteString.copyFromUtf8("Error: " + e.getMessage()));
}
}
}

View File

@ -0,0 +1,77 @@
package com.mediamanager.service.trackhasartist;
import com.mediamanager.model.Track;
import com.mediamanager.model.TrackHasArtist;
import com.mediamanager.model.Artist;
import com.mediamanager.repository.TrackHasArtistRepository;
import com.mediamanager.repository.TrackRepository;
import com.mediamanager.repository.ArtistRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Optional;
public class TrackHasArtistService {
private static final Logger logger = LogManager.getLogger(TrackHasArtistService.class);
private final TrackHasArtistRepository repository;
private final TrackRepository trackRepository;
private final ArtistRepository artistRepository;
public TrackHasArtistService(TrackHasArtistRepository repository, TrackRepository trackRepository, ArtistRepository artistRepository) {
this.repository = repository;
this.trackRepository = trackRepository;
this.artistRepository = artistRepository;
}
public TrackHasArtist createTrackHasArtist(Integer trackId, Integer artistId) {
logger.debug("Creating track has artist relationship - trackId:{}, artistId:{}", trackId, artistId);
if (trackId == null || trackId <= 0) {
throw new IllegalArgumentException("Track ID cannot be null or invalid");
}
if (artistId == null || artistId <= 0) {
throw new IllegalArgumentException("Artist ID cannot be null or invalid");
}
// Verify Track exists
Optional<Track> track = trackRepository.findById(trackId);
if (track.isEmpty()) {
throw new IllegalArgumentException("Track not found with id: " + trackId);
}
// Verify Artist exists
Optional<Artist> artist = artistRepository.findById(artistId);
if (artist.isEmpty()) {
throw new IllegalArgumentException("Artist not found with id: " + artistId);
}
TrackHasArtist trackHasArtist = new TrackHasArtist();
trackHasArtist.setTrack(track.get());
trackHasArtist.setArtist(artist.get());
return repository.save(trackHasArtist);
}
public List<TrackHasArtist> getAllTrackHasArtists() {
logger.info("Getting all track has artist relationships");
return repository.findAll();
}
public Optional<TrackHasArtist> getTrackHasArtistById(Integer id) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
logger.info("Getting track has artist by id:{}", id);
return repository.findById(id);
}
public boolean deleteTrackHasArtist(Integer id) {
if (id == null) {
throw new IllegalArgumentException("Track has artist id cannot be null");
}
logger.info("Deleting track has artist:{}", id);
return repository.deleteById(id);
}
}

View File

@ -0,0 +1,46 @@
syntax = "proto3";
option java_package = "com.mediamanager.protocol.messages";
option java_outer_classname = "TrackHasArtistMessages";
package mediamanager.messages;
message TrackHasArtist{
int32 id = 1;
int32 fk_track_id = 2;
int32 fk_artist_id =3;
}
message CreateTrackHasArtistRequest {
int32 fk_track_id = 1;
int32 fk_artist_id = 2;
}
message CreateTrackHasArtistResponse {
TrackHasArtist trackhasartist = 1;
}
message GetTrackHasArtistsRequest {
}
message GetTrackHasArtistsResponse {
repeated TrackHasArtist trackhasartist = 1;
}
message GetTrackHasArtistByIdRequest {
int32 id = 1;
}
message GetTrackHasArtistByIdResponse {
TrackHasArtist trackhasartist = 1;
}
message DeleteTrackHasArtistRequest {
int32 id = 1;
}
message DeleteTrackHasArtistResponse {
bool success = 1;
string message = 2;
}