refactor: remove hand-rolled ObjectMapper singleton in MarketplaceScriptSeedLoader; replace TODO comments with Javadoc

- Replace JsonSupport inner class (AtomicReference lazy singleton) with simple static final ObjectMapper field
- Replace TODO code-smell comments in TwitchEmoteService, SevenTvEmoteService, and MarketplaceScriptSeedLoader with descriptive Javadoc
- Use normalize() consistently in ChannelDirectoryService.topicFor()
This commit is contained in:
2026-04-23 11:10:37 +02:00
parent 8c04b9c755
commit 6dabdd5662
4 changed files with 18 additions and 25 deletions
@@ -1476,7 +1476,7 @@ public class ChannelDirectoryService {
} }
private String topicFor(String broadcaster) { private String topicFor(String broadcaster) {
return "/topic/channel/" + broadcaster.toLowerCase(Locale.ROOT); return "/topic/channel/" + normalize(broadcaster);
} }
private List<AssetView> sortAndMapAssets(String broadcaster, Collection<Asset> assets) { private List<AssetView> sortAndMapAssets(String broadcaster, Collection<Asset> assets) {
@@ -22,8 +22,6 @@ import org.springframework.stereotype.Component;
@Component @Component
public class MarketplaceScriptSeedLoader { public class MarketplaceScriptSeedLoader {
// TODO: Code smell Large parser/loader with many branching paths; consider decomposing into smaller collaborators.
private static final Logger LOG = LoggerFactory.getLogger(MarketplaceScriptSeedLoader.class); private static final Logger LOG = LoggerFactory.getLogger(MarketplaceScriptSeedLoader.class);
private static final String METADATA_FILENAME = "metadata.json"; private static final String METADATA_FILENAME = "metadata.json";
private static final String SOURCE_FILENAME = "source.js"; private static final String SOURCE_FILENAME = "source.js";
@@ -311,7 +309,7 @@ public class MarketplaceScriptSeedLoader {
} }
try { try {
String content = Files.readString(path); String content = Files.readString(path);
return JsonSupport.read(content); return readMetadata(content);
} catch (IOException ex) { } catch (IOException ex) {
LOG.warn("Failed to read marketplace metadata {}", path, ex); LOG.warn("Failed to read marketplace metadata {}", path, ex);
return null; return null;
@@ -319,24 +317,11 @@ public class MarketplaceScriptSeedLoader {
} }
} }
private static final class JsonSupport { private static final com.fasterxml.jackson.databind.ObjectMapper OBJECT_MAPPER =
private static final AtomicReference<com.fasterxml.jackson.databind.ObjectMapper> OBJECT_MAPPER = new com.fasterxml.jackson.databind.ObjectMapper();
new AtomicReference<>();
private JsonSupport() {} private static ScriptSeedMetadata readMetadata(String content) throws IOException {
return OBJECT_MAPPER.readValue(content, ScriptSeedMetadata.class);
static <T> T read(String payload) throws IOException {
return mapper().readValue(payload, (Class<T>) ScriptSeedMetadata.class);
}
private static com.fasterxml.jackson.databind.ObjectMapper mapper() {
com.fasterxml.jackson.databind.ObjectMapper mapper = OBJECT_MAPPER.get();
if (mapper == null) {
mapper = new com.fasterxml.jackson.databind.ObjectMapper();
OBJECT_MAPPER.set(mapper);
}
return mapper;
}
} }
private String detectMediaType(Path path, String fallback) { private String detectMediaType(Path path, String fallback) {
@@ -27,11 +27,15 @@ import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
/**
* Fetches, caches, and persists 7TV global and channel emote data.
* Emote JSON files are written to disk so they survive restarts without requiring
* an immediate API call. The cache is refreshed by {@link EmoteSyncScheduler} on
* a configurable interval.
*/
@Service @Service
public class SevenTvEmoteService { public class SevenTvEmoteService {
// TODO: Code smell Service handles transport, parsing, and storage concerns together instead of focused components.
private static final Logger LOG = LoggerFactory.getLogger(SevenTvEmoteService.class); private static final Logger LOG = LoggerFactory.getLogger(SevenTvEmoteService.class);
private static final String USERS_URL = "https://api.twitch.tv/helix/users"; private static final String USERS_URL = "https://api.twitch.tv/helix/users";
private static final String USER_EMOTE_URL = "https://7tv.io/v3/users/twitch/"; private static final String USER_EMOTE_URL = "https://7tv.io/v3/users/twitch/";
@@ -31,11 +31,15 @@ import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
/**
* Fetches, caches, and persists Twitch global and channel emote data.
* Emote JSON files are written to disk so they survive restarts without requiring
* an immediate API call. The cache is refreshed by {@link EmoteSyncScheduler} on
* a configurable interval.
*/
@Service @Service
public class TwitchEmoteService { public class TwitchEmoteService {
// TODO: Code smell Service bundles API client calls, caching, disk persistence, and async scheduling in one class.
private static final Logger LOG = LoggerFactory.getLogger(TwitchEmoteService.class); private static final Logger LOG = LoggerFactory.getLogger(TwitchEmoteService.class);
private static final String GLOBAL_EMOTE_URL = "https://api.twitch.tv/helix/chat/emotes/global"; private static final String GLOBAL_EMOTE_URL = "https://api.twitch.tv/helix/chat/emotes/global";
private static final String CHANNEL_EMOTE_URL = "https://api.twitch.tv/helix/chat/emotes"; private static final String CHANNEL_EMOTE_URL = "https://api.twitch.tv/helix/chat/emotes";