PageRenderTime 119ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/atlasapi/media/channel/MongoChannelStore.java

https://github.com/atlasapi/atlas-persistence
Java | 457 lines | 384 code | 57 blank | 16 comment | 74 complexity | 05cef351b630823a035349797b2bb9cf MD5 | raw file
  1. package org.atlasapi.media.channel;
  2. import java.util.Collection;
  3. import java.util.Objects;
  4. import java.util.Set;
  5. import java.util.function.Function;
  6. import java.util.stream.StreamSupport;
  7. import javax.annotation.Nullable;
  8. import org.atlasapi.persistence.ids.MongoSequentialIdGenerator;
  9. import com.metabroadcast.common.base.Maybe;
  10. import com.metabroadcast.common.ids.SubstitutionTableNumberCodec;
  11. import com.metabroadcast.common.persistence.mongo.DatabasedMongo;
  12. import com.metabroadcast.common.persistence.mongo.MongoConstants;
  13. import com.metabroadcast.common.persistence.mongo.MongoQueryBuilder;
  14. import com.metabroadcast.common.persistence.mongo.MongoSortBuilder;
  15. import com.metabroadcast.common.stream.MoreCollectors;
  16. import com.google.common.base.Equivalence;
  17. import com.google.common.base.Joiner;
  18. import com.google.common.base.Optional;
  19. import com.google.common.collect.ArrayListMultimap;
  20. import com.google.common.collect.Iterables;
  21. import com.google.common.collect.Multimap;
  22. import com.google.common.collect.Sets;
  23. import com.google.common.collect.Sets.SetView;
  24. import com.mongodb.BasicDBObject;
  25. import com.mongodb.DBCollection;
  26. import com.mongodb.DBCursor;
  27. import com.mongodb.DBObject;
  28. import org.joda.time.DateTime;
  29. import org.joda.time.DateTimeZone;
  30. import static com.google.common.base.Preconditions.checkNotNull;
  31. import static com.metabroadcast.common.persistence.mongo.MongoBuilders.where;
  32. import static com.metabroadcast.common.persistence.mongo.MongoConstants.SINGLE;
  33. import static com.metabroadcast.common.persistence.mongo.MongoConstants.UPSERT;
  34. import static org.atlasapi.media.channel.ChannelTranslator.ADVERTISE_FROM;
  35. import static org.atlasapi.media.channel.ChannelTranslator.AVAILABLE_ON;
  36. import static org.atlasapi.media.channel.ChannelTranslator.BROADCASTER;
  37. import static org.atlasapi.media.channel.ChannelTranslator.CHANNEL_TYPE;
  38. import static org.atlasapi.media.channel.ChannelTranslator.KEY;
  39. import static org.atlasapi.media.channel.ChannelTranslator.MEDIA_TYPE;
  40. import static org.atlasapi.media.channel.ChannelTranslator.PUBLISHER;
  41. import static org.atlasapi.media.channel.ChannelTranslator.URI;
  42. import static org.atlasapi.persistence.media.entity.IdentifiedTranslator.CANONICAL_URL;
  43. import static org.atlasapi.persistence.media.entity.IdentifiedTranslator.IDS_NAMESPACE;
  44. import static org.atlasapi.persistence.media.entity.IdentifiedTranslator.IDS_VALUE;
  45. public class MongoChannelStore extends BaseChannelStore implements ServiceChannelStore {
  46. public static final String COLLECTION_NAME = "channels";
  47. private static final ChannelTranslator translator = new ChannelTranslator();
  48. private static final String NUMBERING_CHANNEL_GROUP_ID = Joiner.on('.')
  49. .join(
  50. ChannelTranslator.NUMBERINGS,
  51. ChannelNumberingTranslator.CHANNEL_GROUP_KEY,
  52. MongoConstants.ID
  53. );
  54. private static final Function<DBObject, Channel> DB_TO_CHANNEL_TRANSLATOR =
  55. input -> translator.fromDBObject(input, null);
  56. private final DBCollection collection;
  57. private final ChannelGroupResolver channelGroupResolver;
  58. private final ChannelGroupWriter channelGroupWriter;
  59. private final Equivalence<Channel> channelEquivalence;
  60. private MongoSequentialIdGenerator idGenerator;
  61. private SubstitutionTableNumberCodec codec;
  62. public MongoChannelStore(
  63. DatabasedMongo mongo,
  64. ChannelGroupResolver channelGroupResolver,
  65. ChannelGroupWriter channelGroupWriter
  66. ) {
  67. this(mongo, channelGroupResolver, channelGroupWriter, new DefaultEquivalence());
  68. }
  69. public MongoChannelStore(
  70. DatabasedMongo mongo,
  71. ChannelGroupResolver channelGroupResolver,
  72. ChannelGroupWriter channelGroupWriter,
  73. Equivalence<Channel> channelEquivalence
  74. ) {
  75. this.channelGroupResolver = channelGroupResolver;
  76. this.channelGroupWriter = channelGroupWriter;
  77. this.collection = mongo.collection(COLLECTION_NAME);
  78. this.idGenerator = new MongoSequentialIdGenerator(mongo, COLLECTION_NAME);
  79. this.codec = new SubstitutionTableNumberCodec();
  80. this.channelEquivalence = channelEquivalence;
  81. }
  82. @SuppressWarnings("deprecation") // specified by interface
  83. @Override
  84. public Maybe<Channel> fromKey(final String key) {
  85. return Maybe.fromPossibleNullValue(
  86. translator.fromDBObject(
  87. collection.findOne(where().fieldEquals(KEY, key).build()),
  88. null
  89. )
  90. );
  91. }
  92. @SuppressWarnings("deprecation") // specified by interface
  93. @Override
  94. public Maybe<Channel> fromId(long id) {
  95. return Maybe.fromPossibleNullValue(
  96. translator.fromDBObject(
  97. collection.findOne(where().idEquals(id).build()),
  98. null
  99. )
  100. );
  101. }
  102. @SuppressWarnings("deprecation") // specified by interface
  103. @Override
  104. public Maybe<Channel> fromUri(final String uri) {
  105. return Maybe.fromPossibleNullValue(
  106. translator.fromDBObject(
  107. collection.findOne(where().fieldEquals(CANONICAL_URL, uri).build()),
  108. null
  109. )
  110. );
  111. }
  112. @Override
  113. public Iterable<Channel> forIds(Iterable<Long> ids) {
  114. return Iterables.transform(
  115. getOrderedCursor(where().longIdIn(ids).build()),
  116. DB_TO_CHANNEL_TRANSLATOR::apply
  117. );
  118. }
  119. @Override
  120. public Iterable<Channel> all() {
  121. return Iterables.transform(
  122. getOrderedCursor(new BasicDBObject()),
  123. DB_TO_CHANNEL_TRANSLATOR::apply
  124. );
  125. }
  126. @Override
  127. public Iterable<Channel> allChannels(ChannelQuery query) {
  128. MongoQueryBuilder mongoQuery = new MongoQueryBuilder();
  129. if (query.getBroadcaster().isPresent()) {
  130. mongoQuery.fieldEquals(BROADCASTER, query.getBroadcaster().get().key());
  131. }
  132. if (query.getMediaType().isPresent()) {
  133. mongoQuery.fieldEquals(MEDIA_TYPE, query.getMediaType().get().name());
  134. }
  135. if (query.getAvailableFrom().isPresent()) {
  136. mongoQuery.fieldEquals(AVAILABLE_ON, query.getAvailableFrom().get().key());
  137. }
  138. if (query.getChannelGroups().isPresent()) {
  139. mongoQuery.longFieldIn(NUMBERING_CHANNEL_GROUP_ID, query.getChannelGroups().get());
  140. }
  141. if (query.getGenres().isPresent()) {
  142. mongoQuery.fieldIn(ChannelTranslator.GENRES_KEY, query.getGenres().get());
  143. }
  144. if (query.getAdvertisedOn().isPresent()) {
  145. mongoQuery.fieldBeforeOrAt(ADVERTISE_FROM, query.getAdvertisedOn().get());
  146. }
  147. if (query.getPublisher().isPresent()) {
  148. mongoQuery.fieldEquals(PUBLISHER, query.getPublisher().get().key());
  149. }
  150. if (query.getUri().isPresent()) {
  151. mongoQuery.fieldEquals(URI, query.getUri().get());
  152. }
  153. if (query.getAliasNamespace().isPresent()) {
  154. mongoQuery.fieldEquals(IDS_NAMESPACE, query.getAliasNamespace().get());
  155. }
  156. if (query.getAliasValue().isPresent()) {
  157. mongoQuery.fieldEquals(IDS_VALUE, query.getAliasValue().get());
  158. }
  159. if (query.getChannelType().isPresent()) {
  160. mongoQuery.fieldEquals(CHANNEL_TYPE, query.getChannelType().get().name());
  161. }
  162. return Iterables.transform(
  163. getOrderedCursor(mongoQuery.build()),
  164. DB_TO_CHANNEL_TRANSLATOR::apply
  165. );
  166. }
  167. @SuppressWarnings("deprecation") // specified by interface
  168. @Override
  169. public Maybe<Channel> forAlias(String alias) {
  170. MongoQueryBuilder query = new MongoQueryBuilder()
  171. .fieldEquals("aliases", alias);
  172. DBCursor cursor = getOrderedCursor(query.build());
  173. if (Iterables.isEmpty(cursor)) {
  174. return Maybe.nothing();
  175. }
  176. return Maybe.just(translator.fromDBObject(Iterables.getOnlyElement(cursor), null));
  177. }
  178. // this method fetches channels by its aliases that are stored as ids in Mongo
  179. @Override
  180. public Iterable<Channel> forKeyPairAlias(ChannelQuery channelQuery) {
  181. MongoQueryBuilder queryBuilder = new MongoQueryBuilder();
  182. queryBuilder.fieldEquals(IDS_NAMESPACE, channelQuery.getAliasNamespace().get());
  183. queryBuilder.fieldEquals(IDS_VALUE, channelQuery.getAliasValue().get());
  184. return StreamSupport.stream(getOrderedCursor(queryBuilder.build()).spliterator(), false)
  185. .map(DB_TO_CHANNEL_TRANSLATOR)
  186. .collect(MoreCollectors.toImmutableList());
  187. }
  188. private DBCursor getOrderedCursor(DBObject query) {
  189. return collection.find(query)
  190. .sort(new MongoSortBuilder().ascending(MongoConstants.ID).build());
  191. }
  192. @Override
  193. public Channel createOrUpdate(Channel channel) {
  194. checkNotNull(channel);
  195. checkNotNull(channel.getUri());
  196. Optional<Channel> existing = fromUri(channel.getUri()).toGuavaOptional();
  197. if (existing.isPresent()) {
  198. maintainParentLinks(channel, existing.get());
  199. channel.setId((existing.get().getId()));
  200. } else {
  201. channel.setId(codec.decode(idGenerator.generate()).longValue());
  202. }
  203. updateNumberingsOnChannelGroups(channel, existing);
  204. ensureParentReference(channel);
  205. setLastUpdated(channel, existing.orNull(), DateTime.now(DateTimeZone.UTC));
  206. collection.update(
  207. new BasicDBObject(URI, channel.getUri()),
  208. translator.toDBObject(null, channel),
  209. UPSERT,
  210. SINGLE
  211. );
  212. return channel;
  213. }
  214. private void maintainParentLinks(Channel newChannel, Channel existingChannel) {
  215. if (existingChannel.getParent() != null && (
  216. newChannel.getParent() == null
  217. || !existingChannel.getParent().equals(newChannel.getParent())
  218. )) {
  219. Optional<Channel> optOldParent = fromId(existingChannel.getParent()).toGuavaOptional();
  220. if (!optOldParent.isPresent()) {
  221. throw new IllegalStateException(String.format(
  222. "Parent channel with id %s not found for channel with id %s",
  223. newChannel.getParent(),
  224. newChannel.getId()
  225. ));
  226. }
  227. Channel oldParent = optOldParent.get();
  228. Set<Long> variations = Sets.newHashSet(oldParent.getVariations());
  229. variations.remove(existingChannel.getId());
  230. oldParent.setVariationIds(variations);
  231. collection.update(
  232. new BasicDBObject(MongoConstants.ID, oldParent.getId()),
  233. translator.toDBObject(null, oldParent),
  234. UPSERT,
  235. SINGLE
  236. );
  237. }
  238. newChannel.setVariationIds(existingChannel.getVariations());
  239. }
  240. private void updateNumberingsOnChannelGroups(Channel channel, Optional<Channel> existingRecord) {
  241. if (existingRecord.isPresent()
  242. && channel.getChannelNumbers().equals(existingRecord.get().getChannelNumbers())) {
  243. return;
  244. }
  245. if (existingRecord.isPresent()) {
  246. Set<ChannelNumbering> existingChannelNumbers = existingRecord.get().getChannelNumbers();
  247. removeStaleLinks(channel, existingChannelNumbers);
  248. addNewLinks(channel, existingChannelNumbers);
  249. return;
  250. }
  251. // if the channel is new, add all channel numbers
  252. addNewLinks(channel, Sets.newHashSet());
  253. }
  254. private void addNewLinks(Channel channel, Set<ChannelNumbering> existingChannelNumbers) {
  255. Multimap<Long, ChannelNumbering> newChannelGroupMapping = ArrayListMultimap.create();
  256. SetView<ChannelNumbering> newChannelNumberings = Sets.difference(
  257. channel.getChannelNumbers(),
  258. existingChannelNumbers
  259. );
  260. // group new channel numbering per channel group
  261. newChannelNumberings.forEach(numbering -> {
  262. numbering.setChannel(channel.getId());
  263. newChannelGroupMapping.put(numbering.getChannelGroup(), numbering);
  264. });
  265. newChannelGroupMapping.keySet().forEach(channelGroupId -> {
  266. ChannelGroup channelGroup = resolveChannelGroupForId(channelGroupId);
  267. Collection<ChannelNumbering> newChannelGroupNumberings = newChannelGroupMapping.get(
  268. channelGroupId
  269. );
  270. newChannelGroupNumberings.forEach(channelGroup::addChannelNumbering);
  271. channelGroupWriter.createOrUpdate(channelGroup);
  272. });
  273. }
  274. private void ensureParentReference(Channel channel) {
  275. if (channel.getParent() != null) {
  276. Optional<Channel> optParent = fromId(channel.getParent()).toGuavaOptional();
  277. if (!optParent.isPresent()) {
  278. throw new IllegalArgumentException(String.format(
  279. "Parent channel with id %s not found for channel with id %s",
  280. channel.getParent(),
  281. channel.getId()
  282. ));
  283. }
  284. Channel parent = optParent.get();
  285. parent.addVariation(channel.getId());
  286. collection.update(
  287. new BasicDBObject(MongoConstants.ID, parent.getId()),
  288. translator.toDBObject(null, parent),
  289. UPSERT,
  290. SINGLE
  291. );
  292. }
  293. }
  294. private void setLastUpdated(Channel current, @Nullable Channel previous, DateTime now) {
  295. if (previous == null
  296. || current.getLastUpdated() == null
  297. || !channelEquivalence.equivalent(current, previous)) {
  298. current.setLastUpdated(now);
  299. }
  300. }
  301. /**
  302. * Given a channel containing a set of the new ChannelNumbering and a set of the existing
  303. * ChannelNumberings, determines those numberings not in the new set, and removes them from
  304. * their channel group.
  305. *
  306. * @param channel
  307. * @param existingNumbers
  308. */
  309. private void removeStaleLinks(
  310. Channel channel,
  311. Set<ChannelNumbering> existingNumbers
  312. ) {
  313. Multimap<Long, ChannelNumbering> expiredChannelGroupMapping = ArrayListMultimap.create();
  314. SetView<ChannelNumbering> expiredNumberings = Sets.difference(
  315. existingNumbers,
  316. channel.getChannelNumbers()
  317. );
  318. // group expired channel numberings per channel group
  319. expiredNumberings.forEach(numbering -> {
  320. numbering.setChannel(channel.getId());
  321. expiredChannelGroupMapping.put(numbering.getChannelGroup(), numbering);
  322. });
  323. expiredChannelGroupMapping.keySet().forEach(channelGroupId -> {
  324. ChannelGroup channelGroup = resolveChannelGroupForId(channelGroupId);
  325. Collection<ChannelNumbering> expiredChannelGroupNumberings = expiredChannelGroupMapping.get(
  326. channelGroupId
  327. );
  328. Iterable<ChannelNumbering> nonExpired = Iterables.filter(
  329. channelGroup.getChannelNumberings(),
  330. channelNumbering -> !expiredChannelGroupNumberings.contains(channelNumbering)
  331. );
  332. channelGroup.setChannelNumberings(nonExpired);
  333. channelGroupWriter.createOrUpdate(channelGroup);
  334. });
  335. }
  336. private ChannelGroup resolveChannelGroupForId(Long channelGroupId) {
  337. Optional<ChannelGroup> optGroup = channelGroupResolver.channelGroupFor(channelGroupId);
  338. if (!optGroup.isPresent()) {
  339. throw new IllegalStateException(String.format(
  340. "ChannelGroup with id %s not found",
  341. channelGroupId
  342. ));
  343. }
  344. return optGroup.get();
  345. }
  346. @Override
  347. public void start() {
  348. /* no-op */
  349. }
  350. @Override
  351. public void shutdown() {
  352. /* no-op */
  353. }
  354. private static class DefaultEquivalence extends Equivalence<Channel> {
  355. @Override
  356. protected boolean doEquivalent(@Nullable Channel a, @Nullable Channel b) {
  357. return a == b
  358. || a != null
  359. && b != null
  360. // Identified
  361. && Objects.equals(a.getId(), b.getId())
  362. && Objects.equals(a.getCanonicalUri(), b.getCanonicalUri())
  363. && Objects.equals(a.getCurie(), b.getCurie())
  364. && Objects.equals(a.getAliasUrls(), b.getAliasUrls())
  365. && Objects.equals(a.getAliases(), b.getAliases())
  366. && Objects.equals(a.getEquivalentTo(), b.getEquivalentTo())
  367. // Channel
  368. && a.getSource() == b.getSource() && Objects.equals(a.getTitle(), b.getTitle())
  369. && Objects.equals(a.getImages(), b.getImages())
  370. && Objects.equals(a.getRelatedLinks(), b.getRelatedLinks())
  371. && a.getMediaType() == b.getMediaType()
  372. && Objects.equals(a.getKey(), b.getKey())
  373. && Objects.equals(a.getHighDefinition(), b.getHighDefinition())
  374. && Objects.equals(a.getRegional(), b.getRegional())
  375. && Objects.equals(a.getAdult(), b.getAdult())
  376. && Objects.equals(a.getTimeshift(), b.getTimeshift())
  377. && Objects.equals(a.isTimeshifted(), b.isTimeshifted())
  378. && a.getBroadcaster() == b.getBroadcaster()
  379. && Objects.equals(a.getAdvertiseFrom(), b.getAdvertiseFrom())
  380. && Objects.equals(a.getAvailableFrom(), b.getAvailableFrom())
  381. && Objects.equals(a.getVariations(), b.getVariations())
  382. && Objects.equals(a.getParent(), b.getParent())
  383. && Objects.equals(a.getChannelNumbers(), b.getChannelNumbers())
  384. && Objects.equals(a.getStartDate(), b.getStartDate())
  385. && Objects.equals(a.getEndDate(), b.getEndDate())
  386. && Objects.equals(a.getGenres(), b.getGenres())
  387. && Objects.equals(a.getShortDescription(), b.getShortDescription())
  388. && Objects.equals(a.getMediumDescription(), b.getMediumDescription())
  389. && Objects.equals(a.getLongDescription(), b.getLongDescription())
  390. && Objects.equals(a.getRegion(), b.getRegion())
  391. && a.getChannelType() == b.getChannelType()
  392. && Objects.equals(a.getTargetRegions(), b.getTargetRegions())
  393. && Objects.equals(a.getInteractive(), b.getInteractive());
  394. }
  395. @Override
  396. protected int doHash(Channel channel) {
  397. return channel.hashCode();
  398. }
  399. }
  400. }