package com.github.triceo.splitlog; import com.github.triceo.splitlog.api.*; import com.github.triceo.splitlog.formatters.UnifyingMessageFormatter; import com.github.triceo.splitlog.logging.SplitlogLoggerFactory; import com.github.triceo.splitlog.util.LogUtil; import com.github.triceo.splitlog.util.LogUtil.Level; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.*; /** * Will use {@link UnifyingMessageFormatter} as default message formatter. * */ final class DefaultMergingFollower extends AbstractCommonFollower<MergingFollower, Follower> implements MergingFollower { private static final Logger LOGGER = SplitlogLoggerFactory.getLogger(DefaultMergingFollower.class); private final ConsumerManager<MergingFollower> consumers = new ConsumerManager<>(this); private final Set<Follower> followers = new LinkedHashSet<>(); protected DefaultMergingFollower(final Follower... followers) { DefaultMergingFollower.LOGGER.info("Merging followers into {}.", this); final Collection<LogWatch> watches = new HashSet<>(); for (final Follower f : followers) { final DefaultFollower af = (DefaultFollower) f; this.followers.add(af); af.registerConsumer(this); watches.add(af.getFollowed()); } if (watches.size() < this.followers.size()) { DefaultMergingFollower.LOGGER.warn("Followers from the same LogWatch, possible message duplication."); } DefaultMergingFollower.LOGGER.info("Followers merged: {}.", this); } @Override protected ConsumerManager<MergingFollower> getConsumerManager() { return this.consumers; } @Override protected MessageFormatter getDefaultFormatter() { return UnifyingMessageFormatter.INSTANCE; } @Override public Collection<Follower> getMerged() { return Collections.unmodifiableSet(this.followers); } @Override public SortedSet<Message> getMessages(final SimpleMessageCondition condition, final MessageComparator order) { final SortedSet<Message> sorted = new TreeSet<>(order); for (final Follower f : this.getMerged()) { for (final Message m : f.getMessages()) { if (!condition.accept(m)) { continue; } sorted.add(m); } } return Collections.unmodifiableSortedSet(sorted); } @Override public synchronized boolean isStopped() { for (final Follower f : this.followers) { if (!f.isStopped()) { return false; } } return true; } @Override public MergingFollower mergeWith(final Follower f) { if (f == null) { throw new IllegalArgumentException("Cannot merge with null."); } final Set<Follower> followers = new HashSet<>(this.followers); followers.add(f); return new DefaultMergingFollower(followers.toArray(new Follower[followers.size()])); } @Override public MergingFollower mergeWith(final MergingFollower f) { if (f == null) { throw new IllegalArgumentException("Cannot merge with null."); } else if (f == this) { throw new IllegalArgumentException("Cannot merge with self."); } final Set<Follower> followers = new HashSet<>(this.followers); followers.addAll(f.getMerged()); return new DefaultMergingFollower(followers.toArray(new Follower[followers.size()])); } @Override public void messageReceived(final Message msg, final MessageDeliveryStatus status, final Follower source) { if (this.isStopped()) { throw new IllegalStateException("Follower already stopped."); } else if (!this.getMerged().contains(source)) { throw new IllegalArgumentException("Forbidden notification source: " + source); } LogUtil.newMessage(DefaultMergingFollower.LOGGER, Level.INFO, "New message received:", msg, status, source, this); this.getExpectationManager().messageReceived(msg, status, source); this.getConsumerManager().messageReceived(msg, status, this); } @Override public MergingFollower remove(final Follower f) { if (!this.getMerged().contains(f)) { return this; } else if (this.getMerged().size() == 1) { return null; } else { DefaultMergingFollower.LOGGER.info("Separating {} from {}.", f, this); final List<Follower> followers = new ArrayList<>(this.followers); followers.remove(f); return new DefaultMergingFollower(followers.toArray(new Follower[followers.size()])); } } @Override public boolean stop() { if (this.isStopped()) { return false; } DefaultMergingFollower.LOGGER.info("Stopping {}.", this); this.getMerged().forEach(Follower::stop); this.getConsumerManager().stop(); this.getExpectationManager().stop(); DefaultMergingFollower.LOGGER.info("Stopped {}.", this); return true; } @Override public String toString() { return "DefaultMergingFollower [getUniqueId()=" + this.getUniqueId() + ", " + "getMerged()=" + this.getMerged() + ", " + "isStopped()=" + this.isStopped() + "]"; } @Override public boolean write(final OutputStream stream, final SimpleMessageCondition condition, final MessageComparator order, final MessageFormatter formatter) { if (stream == null) { throw new IllegalArgumentException("Stream may not be null."); } else if (condition == null) { throw new IllegalArgumentException("Condition may not be null."); } else if (order == null) { throw new IllegalArgumentException("Comparator may not be null."); } /* * assemble messages per-follower, so that we can properly retrieve * their source */ final Collection<Message> messages = new TreeSet<>(order); final Map<Message, String> messagesToText = new HashMap<>(); for (final Follower f : this.getMerged()) { for (final Message m : f.getMessages(condition)) { messages.add(m); messagesToText.put(m, formatter.format(m, f.getFollowed().getWatchedFile())); } } // and now write them in their original order BufferedWriter w = null; try { w = new BufferedWriter(new OutputStreamWriter(stream, "UTF-8")); for (final Message msg : messages) { w.write(messagesToText.get(msg)); w.newLine(); } return true; } catch (final IOException ex) { return false; } finally { IOUtils.closeQuietly(w); } } }