http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/FluentFutureStream.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/FluentFutureStream.java b/server/container/util/src/main/java/org/apache/james/util/FluentFutureStream.java new file mode 100644 index 0000000..9dcae7a --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/FluentFutureStream.java @@ -0,0 +1,191 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collector; +import java.util.stream.Stream; + +public class FluentFutureStream<T> { + + private final CompletableFuture<Stream<T>> completableFuture; + + /** + * Constructs a FluentFutureStream from a future of Stream. + */ + public static <T> FluentFutureStream<T> of(CompletableFuture<Stream<T>> completableFuture) { + return new FluentFutureStream<>(completableFuture); + } + + /** + * Constructs a FluentFutureStream from a Stream of Future + */ + public static <T> FluentFutureStream<T> of(Stream<CompletableFuture<T>> completableFutureStream) { + return new FluentFutureStream<>(CompletableFutureUtil.allOf(completableFutureStream)); + } + + /** + * Constructs a FluentFutureStream from a Stream of Future of Stream. + * + * Underlying streams are flatMapped. + */ + public static <T> FluentFutureStream<T> ofNestedStreams(Stream<CompletableFuture<Stream<T>>> completableFuture) { + return of(completableFuture) + .flatMap(Function.identity()); + } + + /** + * Constructs a FluentFutureStream from a Stream of Future of Optionals. + * + * Underlying optionals are unboxed. + */ + public static <T> FluentFutureStream<T> ofOptionals(Stream<CompletableFuture<Optional<T>>> completableFuture) { + return of(completableFuture) + .flatMapOptional(Function.identity()); + } + + /** + * Constructs a FluentFutureStream from the supplied futures. + */ + @SafeVarargs + public static <T> FluentFutureStream<T> ofFutures(CompletableFuture<T>... completableFutures) { + return new FluentFutureStream<>(CompletableFutureUtil.allOfArray(completableFutures)); + } + + private FluentFutureStream(CompletableFuture<Stream<T>> completableFuture) { + this.completableFuture = completableFuture; + } + + /** + * For all values of the underlying stream, an action will be performed. + */ + public FluentFutureStream<T> performOnAll(Function<T, CompletableFuture<Void>> action) { + return FluentFutureStream.of( + CompletableFutureUtil.performOnAll(completableFuture(), action)); + } + + /** + * Apply a transformation to all values of the underlying stream. + */ + public <U> FluentFutureStream<U> map(Function<T, U> function) { + return FluentFutureStream.of( + CompletableFutureUtil.map(completableFuture(), function)); + } + + /** + * Apply a transformation to all value of the underlying stream. + * + * As the supplied transformation produces streams, the results will be flatMapped. + */ + public <U> FluentFutureStream<U> flatMap(Function<T, Stream<U>> function) { + return FluentFutureStream.of(completableFuture().thenApply(stream -> + stream.flatMap(function))); + } + + /** + * Apply a transformation to all value of the underlying stream. + * + * As the supplied transformation produces optionals, the results will be unboxed. + */ + public <U> FluentFutureStream<U> flatMapOptional(Function<T, Optional<U>> function) { + return map(function) + .flatMap(OptionalUtils::toStream); + } + + /** + * Apply a transformation to all value of the underlying stream. + * + * As the supplied transformation produces futures, we need to compose the returned values. + */ + public <U> FluentFutureStream<U> thenComposeOnAll(Function<T, CompletableFuture<U>> function) { + return FluentFutureStream.of( + CompletableFutureUtil.thenComposeOnAll(completableFuture(), function)); + } + + /** + * Apply a transformation to all value of the underlying stream. + * + * As the supplied transformation produces futures of stream, we need to compose then flatMap the returned values. + */ + public <U> FluentFutureStream<U> thenFlatCompose(Function<T, CompletableFuture<Stream<U>>> function) { + return FluentFutureStream.of( + CompletableFutureUtil.thenComposeOnAll(completableFuture(), function)) + .flatMap(Function.identity()); + } + + /** + * Apply a transformation to all value of the underlying stream. + * + * As the supplied transformation produces futures of optionals, we need to compose then unbox the returned values. + */ + public <U> FluentFutureStream<U> thenFlatComposeOnOptional(Function<T, CompletableFuture<Optional<U>>> function) { + return FluentFutureStream.of( + CompletableFutureUtil.thenComposeOnAll(completableFuture(), function)) + .flatMapOptional(Function.identity()); + } + + /** + * Filter the values of the underlying stream. + */ + public FluentFutureStream<T> filter(Predicate<T> predicate) { + return FluentFutureStream.of(completableFuture + .thenApply(stream -> stream.filter(predicate))); + } + + /** + * Reduces the underlying stream. Reduced value is supplied as a Future of optional, as no empty value is supplied. + */ + public CompletableFuture<Optional<T>> reduce(BinaryOperator<T> combiner) { + return CompletableFutureUtil.reduce(combiner, completableFuture); + } + + /** + * educes the underlying stream. Reduced value is supplied as a Future, as an empty value is specified. + */ + public CompletableFuture<T> reduce(T emptyAccumulator, BinaryOperator<T> combiner) { + return CompletableFutureUtil.reduce(combiner, completableFuture, emptyAccumulator); + } + + /** + * Returns a future of the underlying stream. + */ + public CompletableFuture<Stream<T>> completableFuture() { + return this.completableFuture; + } + + /** + * Returns the future of the underlying collected stream. + */ + public <C> CompletableFuture<C> collect(Collector<T, ?, C> collector) { + return this.completableFuture + .thenApply(stream -> stream.collect(collector)); + } + + /** + * Join and returns the underlying stream. + */ + public Stream<T> join() { + return completableFuture().join(); + } +}
http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/GuavaUtils.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/GuavaUtils.java b/server/container/util/src/main/java/org/apache/james/util/GuavaUtils.java new file mode 100644 index 0000000..518ead1 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/GuavaUtils.java @@ -0,0 +1,37 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.tuple.Pair; + +import com.github.steveash.guavate.Guavate; +import com.google.common.collect.ImmutableListMultimap; + +public class GuavaUtils { + public static <K, V> ImmutableListMultimap<K, V> toMultimap(Map<K, List<V>> rights) { + return rights.entrySet() + .stream() + .flatMap(e -> e.getValue().stream().map(right -> Pair.of(e.getKey(), right))) + .collect(Guavate.toImmutableListMultimap(Pair::getKey, Pair::getValue)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/Host.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/Host.java b/server/container/util/src/main/java/org/apache/james/util/Host.java new file mode 100644 index 0000000..cb6acce --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/Host.java @@ -0,0 +1,137 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.List; +import java.util.Optional; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +public class Host { + + public static ImmutableList<Host> parseHosts(String hostsString) { + return parseHosts(hostsString, Optional.empty()); + } + + public static ImmutableList<Host> parseHosts(String hostsString, int defaultPort) { + return parseHosts(hostsString, Optional.of(defaultPort)); + } + + private static ImmutableList<Host> parseHosts(String hostsString, Optional<Integer> defaultPort) { + return Splitter.on(',') + .omitEmptyStrings() + .splitToList(hostsString) + .stream() + .map(string -> Host.parse(string, defaultPort)) + .distinct() + .collect(Guavate.toImmutableList()); + } + + public static Host from(String hostname, int port) { + return new Host(hostname, port); + } + + public static Host parseConfString(String ipAndPort, int defaultPort) { + return parse(ipAndPort, Optional.of(defaultPort)); + } + + public static Host parseConfString(String ipAndPort) { + return parse(ipAndPort, Optional.empty()); + } + + public static Host parse(String ipAndPort, Optional<Integer> defaultPort) { + Preconditions.checkNotNull(ipAndPort); + Preconditions.checkArgument(!ipAndPort.isEmpty()); + + List<String> parts = retrieveHostParts(ipAndPort); + + String ip = parts.get(0); + int port = getPortFromConfPart(parts, defaultPort); + + return new Host(ip, port); + } + + private static List<String> retrieveHostParts(String ipAndPort) { + List<String> parts = Splitter.on(':') + .trimResults() + .splitToList(ipAndPort); + + if (parts.size() < 1 || parts.size() > 2) { + throw new IllegalArgumentException(ipAndPort + " is not a valid cassandra node"); + } + return parts; + } + + private static int getPortFromConfPart(List<String> parts, Optional<Integer> defaultPort) { + if (parts.size() == 2) { + return Integer.valueOf(parts.get(1)); + } + if (parts.size() == 1) { + return defaultPort.orElseThrow(() -> new IllegalArgumentException("Host do not have port part but no default port provided")); + } + throw new RuntimeException("A host should be either a hostname or a hostname and a port separated by a ':'"); + } + + private final String hostName; + private final int port; + + @VisibleForTesting + Host(String hostName, int port) { + Preconditions.checkNotNull(hostName, "Hostname could not be null"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(hostName), "Hostname could not be empty"); + Port.assertValid(port); + this.hostName = hostName; + this.port = port; + } + + public String getHostName() { + return hostName; + } + + public int getPort() { + return port; + } + + @Override + public final int hashCode() { + return Objects.hashCode(hostName, port); + } + + @Override + public final boolean equals(Object object) { + if (object instanceof Host) { + Host that = (Host) object; + return Objects.equal(this.hostName, that.hostName) + && Objects.equal(this.port, that.port); + } + return false; + } + + @Override + public String toString() { + return this.hostName + ":" + this.port; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/MDCBuilder.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/MDCBuilder.java b/server/container/util/src/main/java/org/apache/james/util/MDCBuilder.java new file mode 100644 index 0000000..e6709c8 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/MDCBuilder.java @@ -0,0 +1,147 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class MDCBuilder { + + public interface VoidOperation { + void perform(); + } + + public static <T> T withMdc(MDCBuilder mdcBuilder, Supplier<T> answerSupplier) { + try (Closeable closeable = mdcBuilder.build()) { + try { + return answerSupplier.get(); + } catch (RuntimeException e) { + LOGGER.error("Got error, logging its context", e); + throw e; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void withMdc(MDCBuilder mdcBuilder, VoidOperation logOperation) { + withMdc(mdcBuilder, () -> { + logOperation.perform(); + return null; + }); + } + + public static final String HOST = "host"; + public static final String IP = "ip"; + public static final String PROTOCOL = "protocol"; + public static final String USER = "user"; + public static final String ACTION = "action"; + public static final String SESSION_ID = "sessionId"; + public static final String CHARSET = "charset"; + + private static final Logger LOGGER = LoggerFactory.getLogger(MDCBuilder.class); + + public static class Closeables implements Closeable { + private final List<Closeable> closeables; + + public Closeables(List<Closeable> closeables) { + Preconditions.checkNotNull(closeables); + this.closeables = ImmutableList.copyOf(closeables); + } + + @Override + public void close() throws IOException { + closeables.forEach(this::closeQuietly); + } + + private void closeQuietly(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + LOGGER.warn("Failed to close Closeable", e); + } + } + } + + public static MDCBuilder create() { + return new MDCBuilder(); + } + + private final ImmutableMap.Builder<String, String> contextMap = ImmutableMap.builder(); + private final ImmutableList.Builder<MDCBuilder> nestedBuilder = ImmutableList.builder(); + + private MDCBuilder() {} + + public MDCBuilder addContext(MDCBuilder nested) { + this.nestedBuilder.add(nested); + return this; + } + + public MDCBuilder addContext(String key, Object value) { + Preconditions.checkNotNull(key); + Optional.ofNullable(value) + .ifPresent(nonNullValue -> contextMap.put(key, nonNullValue.toString())); + return this; + } + + @VisibleForTesting + Map<String, String> buildContextMap() { + return ImmutableMap.<String, String>builder() + .putAll(nestedBuilder.build() + .stream() + .map(MDCBuilder::buildContextMap) + .flatMap(map -> map.entrySet().stream()) + .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))) + .putAll(contextMap.build()) + .build(); + } + + public <T> T execute(Supplier<T> supplier) { + return MDCBuilder.withMdc(this, supplier); + } + + public <T> Supplier<T> wrapArround(Supplier<T> supplier) { + return () -> execute(supplier); + } + + public Closeable build() { + return new Closeables( + buildContextMap() + .entrySet() + .stream() + .map(entry -> MDC.putCloseable(entry.getKey(), entry.getValue())) + .collect(Guavate.toImmutableList())); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/MDCStructuredLogger.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/MDCStructuredLogger.java b/server/container/util/src/main/java/org/apache/james/util/MDCStructuredLogger.java new file mode 100644 index 0000000..cf970ea --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/MDCStructuredLogger.java @@ -0,0 +1,50 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.function.Consumer; + +import org.slf4j.Logger; + +public class MDCStructuredLogger implements StructuredLogger { + + public static MDCStructuredLogger forLogger(Logger logger) { + return new MDCStructuredLogger(logger); + } + + private final Logger logger; + private final MDCBuilder mdcBuilder; + + public MDCStructuredLogger(Logger logger) { + this.logger = logger; + this.mdcBuilder = MDCBuilder.create(); + } + + @Override + public StructuredLogger addField(String name, Object value) { + mdcBuilder.addContext(name, value); + return this; + } + + @Override + public void log(Consumer<Logger> logOperation) { + MDCBuilder.withMdc(mdcBuilder, () -> logOperation.accept(logger)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/MemoizedSupplier.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/MemoizedSupplier.java b/server/container/util/src/main/java/org/apache/james/util/MemoizedSupplier.java new file mode 100644 index 0000000..d0b4e3b --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/MemoizedSupplier.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; + +public class MemoizedSupplier { + public static <T> Supplier<T> of(Supplier<T> originalSupplier) { + return Suppliers.memoize(originalSupplier::get)::get; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/OptionalUtils.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/OptionalUtils.java b/server/container/util/src/main/java/org/apache/james/util/OptionalUtils.java new file mode 100644 index 0000000..4a3449c --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/OptionalUtils.java @@ -0,0 +1,68 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.util; + +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class OptionalUtils { + + @FunctionalInterface + public interface Operation { + void perform(); + } + + public static <T> Optional<T> executeIfEmpty(Optional<T> optional, Operation operation) { + if (!optional.isPresent()) { + operation.perform(); + } + return optional; + } + + public static <T> Stream<T> toStream(Optional<T> optional) { + return optional.map(Stream::of) + .orElse(Stream.of()); + } + + @SafeVarargs + public static <T> Optional<T> or(Optional<T>... optionals) { + return orStream(Arrays.stream(optionals)); + } + + @SafeVarargs + public static <T> Optional<T> orSuppliers(Supplier<Optional<T>>... suppliers) { + return orStream(Arrays.stream(suppliers) + .map(Supplier::get)); + } + + private static <T> Optional<T> orStream(Stream<Optional<T>> stream) { + return stream + .filter(Optional::isPresent) + .findFirst() + .orElse(Optional.empty()); + } + + public static <T> boolean containsDifferent(Optional<T> requestValue, T storeValue) { + return requestValue + .filter(value -> !value.equals(storeValue)) + .isPresent(); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/Port.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/Port.java b/server/container/util/src/main/java/org/apache/james/util/Port.java new file mode 100644 index 0000000..016c4b4 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/Port.java @@ -0,0 +1,58 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.Random; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Range; + +public class Port { + public static final int MAX_PORT_VALUE = 65535; + public static final int PRIVILEGED_PORT_BOUND = 1024; + private static final Range<Integer> VALID_PORT_RANGE = Range.closed(1, MAX_PORT_VALUE); + + public static int generateValidUnprivilegedPort() { + return new Random().nextInt(Port.MAX_PORT_VALUE - PRIVILEGED_PORT_BOUND) + PRIVILEGED_PORT_BOUND; + } + + public static void assertValid(int port) { + Preconditions.checkArgument(isValid(port), "Port should be between 1 and 65535"); + } + + public static boolean isValid(int port) { + return VALID_PORT_RANGE.contains(port); + } + + private final int value; + + public Port(int value) { + validate(value); + this.value = value; + } + + protected void validate(int port) { + assertValid(port); + } + + public int getValue() { + return value; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/Runnables.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/Runnables.java b/server/container/util/src/main/java/org/apache/james/util/Runnables.java new file mode 100644 index 0000000..c199f31 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/Runnables.java @@ -0,0 +1,40 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class Runnables { + public static void runParallel(Runnable... runnables) { + FluentFutureStream.of( + Arrays.stream(runnables) + .map(runnable -> CompletableFuture.supplyAsync(toVoidSupplier(runnable)))) + .join(); + } + + private static Supplier<Void> toVoidSupplier(Runnable runnable) { + return () -> { + runnable.run(); + return null; + }; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java new file mode 100644 index 0000000..e8a71bd --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java @@ -0,0 +1,52 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +public class StreamUtils { + + public static <T> Stream<T> ofNullable(T[] array) { + return ofOptional(Optional.ofNullable(array)); + } + + public static <T> Stream<T> ofOptional(Optional<T[]> array) { + return array + .map(Arrays::stream) + .orElse(Stream.empty()); + } + + public static <T> Stream<T> flatten(Collection<Stream<T>> streams) { + return flatten(streams.stream()); + } + + public static <T> Stream<T> flatten(Stream<Stream<T>> streams) { + return streams.flatMap(Function.identity()); + } + + @SafeVarargs + public static <T> Stream<T> flatten(Stream<T>... streams) { + return flatten(Arrays.stream(streams)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/StructuredLogger.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/StructuredLogger.java b/server/container/util/src/main/java/org/apache/james/util/StructuredLogger.java new file mode 100644 index 0000000..f6fcec4 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/StructuredLogger.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.function.Consumer; + +import org.slf4j.Logger; + +public interface StructuredLogger { + StructuredLogger addField(String name, Object value); + + void log(Consumer<Logger> logOperation); +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/ValuePatch.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/ValuePatch.java b/server/container/util/src/main/java/org/apache/james/util/ValuePatch.java new file mode 100644 index 0000000..5c3d01a --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/ValuePatch.java @@ -0,0 +1,125 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import com.google.common.base.Preconditions; + +public class ValuePatch<T> { + + private enum State { + KEEP, + REMOVE, + MODIFY + } + + public static <T> ValuePatch<T> modifyTo(T value) { + Preconditions.checkNotNull(value); + return new ValuePatch<>(value, State.MODIFY); + } + + public static <T> ValuePatch<T> ofNullable(T value) { + return ofOptional(Optional.ofNullable(value)); + } + + public static <T> ValuePatch<T> ofOptional(Optional<T> value) { + Preconditions.checkNotNull(value); + return value.map(ValuePatch::modifyTo) + .orElse(ValuePatch.remove()); + } + + public static <T> ValuePatch<T> remove() { + return new ValuePatch<>(null, State.REMOVE); + } + + public static <T> ValuePatch<T> keep() { + return new ValuePatch<>(null, State.KEEP); + } + + private final T value; + private final State state; + + private ValuePatch(T value, State state) { + this.value = value; + this.state = state; + } + + public boolean isRemoved() { + return state == State.REMOVE; + } + + public boolean isModified() { + return state == State.MODIFY; + } + + public boolean isKept() { + return state == State.KEEP; + } + + public <S> Optional<S> mapNotKeptToOptional(Function<Optional<T>, S> updateTransformation) { + if (isKept()) { + return Optional.empty(); + } + return Optional.of(updateTransformation.apply(Optional.ofNullable(value))); + } + + public T get() { + if (isModified()) { + return value; + } else { + throw new NoSuchElementException(); + } + } + + public Optional<T> notKeptOrElse(Optional<T> replacement) { + if (isKept()) { + return replacement; + } + return Optional.ofNullable(value); + } + + public Optional<T> toOptional() { + return Optional.ofNullable(value); + } + + public T getOrElse(T replacement) { + return toOptional().orElse(replacement); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ValuePatch) { + ValuePatch<?> that = (ValuePatch<?>) o; + return Objects.equals(this.value, that.value) && + Objects.equals(this.state, that.state); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(value, state); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/date/DefaultZonedDateTimeProvider.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/date/DefaultZonedDateTimeProvider.java b/server/container/util/src/main/java/org/apache/james/util/date/DefaultZonedDateTimeProvider.java new file mode 100644 index 0000000..47b776a --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/date/DefaultZonedDateTimeProvider.java @@ -0,0 +1,31 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.date; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +public class DefaultZonedDateTimeProvider implements ZonedDateTimeProvider { + + @Override + public ZonedDateTime get() { + return ZonedDateTime.now(ZoneOffset.UTC); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/date/ImapDateTimeFormatter.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/date/ImapDateTimeFormatter.java b/server/container/util/src/main/java/org/apache/james/util/date/ImapDateTimeFormatter.java new file mode 100644 index 0000000..0a0d633 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/date/ImapDateTimeFormatter.java @@ -0,0 +1,94 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.util.date; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.SignStyle; + +import com.google.common.collect.ImmutableMap; + +public class ImapDateTimeFormatter { + + private static final int INITIAL_YEAR = 1970; + + public static DateTimeFormatter rfc5322() { + return new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .parseLenient() + .optionalStart() + .appendText(DAY_OF_WEEK, dayOfWeek()) + .appendLiteral(", ") + .optionalEnd() + .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) + .appendLiteral(' ') + .appendText(MONTH_OF_YEAR, monthOfYear()) + .appendLiteral(' ') + .appendValueReduced(YEAR, 2, 4, INITIAL_YEAR) + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalEnd() + .appendLiteral(' ') + .appendOffset("+HHMM", "GMT") + .toFormatter(); + } + + private static ImmutableMap<Long, String> monthOfYear() { + return ImmutableMap.<Long, String>builder() + .put(1L, "Jan") + .put(2L, "Feb") + .put(3L, "Mar") + .put(4L, "Apr") + .put(5L, "May") + .put(6L, "Jun") + .put(7L, "Jul") + .put(8L, "Aug") + .put(9L, "Sep") + .put(10L, "Oct") + .put(11L, "Nov") + .put(12L, "Dec") + .build(); + } + + private static ImmutableMap<Long, String> dayOfWeek() { + return ImmutableMap.<Long, String>builder() + .put(1L, "Mon") + .put(2L, "Tue") + .put(3L, "Wed") + .put(4L, "Thu") + .put(5L, "Fri") + .put(6L, "Sat") + .put(7L, "Sun") + .build(); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/date/ZonedDateTimeProvider.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/date/ZonedDateTimeProvider.java b/server/container/util/src/main/java/org/apache/james/util/date/ZonedDateTimeProvider.java new file mode 100644 index 0000000..6f80e76 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/date/ZonedDateTimeProvider.java @@ -0,0 +1,28 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.date; + +import java.time.ZonedDateTime; + +import javax.inject.Provider; + +public interface ZonedDateTimeProvider extends Provider<ZonedDateTime> { + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java b/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java new file mode 100644 index 0000000..74bb660 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java @@ -0,0 +1,233 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.mime; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.mail.internet.MimeMessage; + +import org.apache.commons.io.IOUtils; +import org.apache.james.mime4j.dom.Body; +import org.apache.james.mime4j.dom.Entity; +import org.apache.james.mime4j.dom.Multipart; +import org.apache.james.mime4j.dom.TextBody; + +import com.github.fge.lambdas.Throwing; +import com.github.fge.lambdas.functions.ThrowingFunction; + +public class MessageContentExtractor { + + public static final String CONTENT_ID = "Content-ID"; + public static final String MULTIPART_ALTERNATIVE = "multipart/alternative"; + public static final String TEXT_HTML = "text/html"; + public static final String TEXT_PLAIN = "text/plain"; + + public MessageContent extract(org.apache.james.mime4j.dom.Message message) throws IOException { + Body body = message.getBody(); + if (body instanceof TextBody) { + return parseTextBody(message, (TextBody)body); + } + if (body instanceof Multipart) { + return parseMultipart(message, (Multipart)body); + } + return MessageContent.empty(); + } + + private MessageContent parseTextBody(Entity entity, TextBody textBody) throws IOException { + Optional<String> bodyContent = asString(textBody); + if (TEXT_HTML.equals(entity.getMimeType())) { + return MessageContent.ofHtmlOnly(bodyContent); + } + return MessageContent.ofTextOnly(bodyContent); + } + + private MessageContent parseMultipart(Entity entity, Multipart multipart) throws IOException { + MessageContent messageContent = parseMultipartContent(entity, multipart); + if (!messageContent.isEmpty()) { + return messageContent; + } + return parseFirstFoundMultipart(multipart); + } + + private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException { + switch (entity.getMimeType()) { + case MULTIPART_ALTERNATIVE: + return retrieveHtmlAndPlainTextContent(multipart); + default: + return retrieveFirstReadablePart(multipart); + } + } + + private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOException { + ThrowingFunction<Entity, MessageContent> parseMultipart = firstPart -> parseMultipart(firstPart, (Multipart)firstPart.getBody()); + return multipart.getBodyParts() + .stream() + .filter(part -> part.getBody() instanceof Multipart) + .findFirst() + .map(Throwing.function(parseMultipart).sneakyThrow()) + .orElse(MessageContent.empty()); + } + + private Optional<String> asString(TextBody textBody) throws IOException { + return Optional.ofNullable(IOUtils.toString(textBody.getInputStream(), charset(Optional.ofNullable(textBody.getMimeCharset())))); + } + + private Charset charset(Optional<String> charset) { + return charset + .map(Charset::forName) + .orElse(org.apache.james.mime4j.Charsets.DEFAULT_CHARSET); + } + + private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) throws IOException { + Optional<String> textBody = getFirstMatchingTextBody(multipart, TEXT_PLAIN); + Optional<String> htmlBody = getFirstMatchingTextBody(multipart, TEXT_HTML); + MessageContent directChildTextBodies = new MessageContent(textBody, htmlBody); + if (!directChildTextBodies.isComplete()) { + MessageContent fromInnerMultipart = parseFirstFoundMultipart(multipart); + return directChildTextBodies.merge(fromInnerMultipart); + } + return directChildTextBodies; + } + + private MessageContent retrieveFirstReadablePart(Multipart multipart) throws IOException { + return retrieveFirstReadablePartMatching(multipart, this::isNotAttachment) + .orElseGet(() -> retrieveFirstReadablePartMatching(multipart, this::isInlinedWithoutCid) + .orElse(MessageContent.empty())); + } + + private Optional<MessageContent> retrieveFirstReadablePartMatching(Multipart multipart, Predicate<Entity> predicate) { + return multipart.getBodyParts() + .stream() + .filter(predicate) + .flatMap(Throwing.function(this::extractContentIfReadable).sneakyThrow()) + .findFirst(); + } + + private Stream<MessageContent> extractContentIfReadable(Entity entity) throws IOException { + if (TEXT_HTML.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) { + return Stream.of( + MessageContent.ofHtmlOnly(asString((TextBody)entity.getBody()))); + } + if (TEXT_PLAIN.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) { + return Stream.of( + MessageContent.ofTextOnly(asString((TextBody)entity.getBody()))); + } + if (entity.isMultipart() && entity.getBody() instanceof Multipart) { + MessageContent innerMultipartContent = parseMultipart(entity, (Multipart)entity.getBody()); + if (!innerMultipartContent.isEmpty()) { + return Stream.of(innerMultipartContent); + } + } + return Stream.empty(); + } + + private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType) throws IOException { + Optional<String> firstMatchingTextBody = getFirstMatchingTextBody(multipart, mimeType, this::isNotAttachment); + if (firstMatchingTextBody.isPresent()) { + return firstMatchingTextBody; + } + Optional<String> fallBackInlinedBodyWithoutCid = getFirstMatchingTextBody(multipart, mimeType, this::isInlinedWithoutCid); + return fallBackInlinedBodyWithoutCid; + } + + private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType, Predicate<Entity> condition) { + Function<TextBody, Optional<String>> textBodyOptionalFunction = Throwing + .function(this::asString).sneakyThrow(); + + return multipart.getBodyParts() + .stream() + .filter(part -> mimeType.equals(part.getMimeType())) + .filter(condition) + .map(Entity::getBody) + .filter(TextBody.class::isInstance) + .map(TextBody.class::cast) + .findFirst() + .flatMap(textBodyOptionalFunction); + } + + private boolean isNotAttachment(Entity part) { + return part.getDispositionType() == null; + } + + private boolean isInlinedWithoutCid(Entity part) { + return Objects.equals(part.getDispositionType(), MimeMessage.INLINE) + && part.getHeader().getField(CONTENT_ID) == null; + } + + public static class MessageContent { + private final Optional<String> textBody; + private final Optional<String> htmlBody; + + public MessageContent(Optional<String> textBody, Optional<String> htmlBody) { + this.textBody = textBody; + this.htmlBody = htmlBody; + } + + public static MessageContent ofTextOnly(Optional<String> textBody) { + return new MessageContent(textBody, Optional.empty()); + } + + public static MessageContent ofHtmlOnly(Optional<String> htmlBody) { + return new MessageContent(Optional.empty(), htmlBody); + } + + public static MessageContent empty() { + return new MessageContent(Optional.empty(), Optional.empty()); + } + + public Optional<String> getTextBody() { + return textBody; + } + + public Optional<String> getHtmlBody() { + return htmlBody; + } + + public boolean isEmpty() { + return equals(empty()); + } + + public boolean isComplete() { + return textBody.isPresent() && htmlBody.isPresent(); + } + + public MessageContent merge(MessageContent fromInnerMultipart) { + return new MessageContent( + textBody.map(Optional::of).orElse(fromInnerMultipart.getTextBody()), + htmlBody.map(Optional::of).orElse(fromInnerMultipart.getHtmlBody())); + } + + @Override + public boolean equals(Object other) { + if (other == null || !(other instanceof MessageContent)) { + return false; + } + MessageContent otherMessageContent = (MessageContent)other; + return Objects.equals(this.textBody, otherMessageContent.textBody) + && Objects.equals(this.htmlBody, otherMessageContent.htmlBody); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/streams/Iterators.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/streams/Iterators.java b/server/container/util/src/main/java/org/apache/james/util/streams/Iterators.java new file mode 100644 index 0000000..ba3a06b --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/streams/Iterators.java @@ -0,0 +1,32 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.streams; + +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class Iterators { + + public static <T> Stream<T> toStream(Iterator<T> iterator) { + Iterable<T> iterable = () -> iterator; + return StreamSupport.stream(iterable.spliterator(), false); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/streams/JamesCollectors.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/streams/JamesCollectors.java b/server/container/util/src/main/java/org/apache/james/util/streams/JamesCollectors.java new file mode 100644 index 0000000..e705063 --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/streams/JamesCollectors.java @@ -0,0 +1,80 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.streams; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Stream; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; + +public class JamesCollectors { + public static <D> Collector<D, ?, Stream<Collection<D>>> chunker(int chunkSize) { + return new ChunkCollector<>(chunkSize); + } + + public static class ChunkCollector<D> implements Collector<D, Multimap<Integer, D>, Stream<Collection<D>>> { + private final int chunkSize; + private final AtomicInteger counter; + + private ChunkCollector(int chunkSize) { + Preconditions.checkArgument(chunkSize > 0, "ChunkSize should be strictly positive"); + this.chunkSize = chunkSize; + this.counter = new AtomicInteger(-1); + } + + @Override + public Supplier<Multimap<Integer, D>> supplier() { + return ArrayListMultimap::create; + } + + @Override + public BiConsumer<Multimap<Integer, D>, D> accumulator() { + return (accumulator, value) -> accumulator.put(counter.incrementAndGet() / chunkSize, value); + } + + @Override + public BinaryOperator<Multimap<Integer, D>> combiner() { + return (accumulator1, accumulator2) -> { + accumulator1.putAll(accumulator2); + return accumulator1; + }; + } + + @Override + public Function<Multimap<Integer, D>, Stream<Collection<D>>> finisher() { + return accumulator -> accumulator.asMap().values().stream(); + } + + @Override + public Set<Characteristics> characteristics() { + return ImmutableSet.of(); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/streams/Limit.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/streams/Limit.java b/server/container/util/src/main/java/org/apache/james/util/streams/Limit.java new file mode 100644 index 0000000..268ed5e --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/streams/Limit.java @@ -0,0 +1,81 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.streams; + +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import com.google.common.base.Preconditions; + +public class Limit { + + public static Limit from(int limit) { + if (limit > 0) { + return new Limit(Optional.of(limit)); + } else { + return unlimited(); + } + } + + public static Limit from(Optional<Integer> limit) { + return limit.map(Limit::from) + .orElse(unlimited()); + } + + public static Limit unlimited() { + return new Limit(Optional.empty()); + } + + public static Limit limit(int limit) { + Preconditions.checkArgument(limit > 0, "limit should be positive"); + return new Limit(Optional.of(limit)); + } + + private final Optional<Integer> limit; + + private Limit(Optional<Integer> limit) { + this.limit = limit; + } + + public Optional<Integer> getLimit() { + return limit; + } + + public <T> Stream<T> applyOnStream(Stream<T> stream) { + return limit + .map(stream::limit) + .orElse(stream); + } + + @Override + public final boolean equals(Object o) { + if (o instanceof Limit) { + Limit other = (Limit) o; + return Objects.equals(limit, other.limit); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(limit); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/main/java/org/apache/james/util/streams/Offset.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/main/java/org/apache/james/util/streams/Offset.java b/server/container/util/src/main/java/org/apache/james/util/streams/Offset.java new file mode 100644 index 0000000..109ecae --- /dev/null +++ b/server/container/util/src/main/java/org/apache/james/util/streams/Offset.java @@ -0,0 +1,66 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util.streams; + +import java.util.Objects; +import java.util.Optional; + +import com.google.common.base.Preconditions; + +public class Offset { + + public static Offset from(Optional<Integer> offset) { + return offset.map(Offset::from) + .orElse(none()); + } + + public static Offset none() { + return new Offset(0); + } + + public static Offset from(int offset) { + Preconditions.checkArgument(offset >= 0, "offset should be positive"); + return new Offset(offset); + } + + private final int offset; + + private Offset(int offset) { + this.offset = offset; + } + + public int getOffset() { + return offset; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof Offset) { + Offset other = (Offset) o; + return Objects.equals(this.offset, other.offset); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(offset); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/test/java/org/apache/james/util/CommutativityChecker.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/test/java/org/apache/james/util/CommutativityChecker.java b/server/container/util/src/test/java/org/apache/james/util/CommutativityChecker.java new file mode 100644 index 0000000..15976e9 --- /dev/null +++ b/server/container/util/src/test/java/org/apache/james/util/CommutativityChecker.java @@ -0,0 +1,58 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import java.util.Set; +import java.util.function.BinaryOperator; + +import org.apache.commons.lang3.tuple.Pair; +import org.paukov.combinatorics3.Generator; + +import com.github.steveash.guavate.Guavate; +import com.google.common.base.Preconditions; + +public class CommutativityChecker<T> { + private final Set<T> valuesToTest; + private final BinaryOperator<T> operationToTest; + + public CommutativityChecker(Set<T> valuesToTest, BinaryOperator<T> operationToTest) { + Preconditions.checkNotNull(valuesToTest); + Preconditions.checkNotNull(operationToTest); + Preconditions.checkArgument(valuesToTest.size() > 1, "You must to pass more than one value to check commutativity"); + this.valuesToTest = valuesToTest; + this.operationToTest = operationToTest; + } + + public Set<Pair<T, T>> findNonCommutativeInput() { + return Generator.combination(valuesToTest) + .simple(2) + .stream() + .map(list -> Pair.of(list.get(0), list.get(1))) + .filter(this::isNotCommutative) + .collect(Guavate.toImmutableSet()); + } + + private boolean isNotCommutative(Pair<T, T> pair) { + T leftThenRight = operationToTest.apply(pair.getLeft(), pair.getRight()); + T rightThenLeft = operationToTest.apply(pair.getRight(), pair.getLeft()); + return !leftThenRight.equals(rightThenLeft); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/630dcab1/server/container/util/src/test/java/org/apache/james/util/CommutativityCheckerTest.java ---------------------------------------------------------------------- diff --git a/server/container/util/src/test/java/org/apache/james/util/CommutativityCheckerTest.java b/server/container/util/src/test/java/org/apache/james/util/CommutativityCheckerTest.java new file mode 100644 index 0000000..ec0802a --- /dev/null +++ b/server/container/util/src/test/java/org/apache/james/util/CommutativityCheckerTest.java @@ -0,0 +1,102 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Set; +import java.util.function.BinaryOperator; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; + +public class CommutativityCheckerTest { + @Test + public void constructorShouldThrowWhenNullValuesToTest() throws Exception { + BinaryOperator<Integer> binaryOperator = (a, b) -> a * a + b; + + assertThatThrownBy(() -> new CommutativityChecker<>(null, binaryOperator)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void constructorShouldThrowWhenEmptyValuesToTest() throws Exception { + BinaryOperator<Integer> binaryOperator = (a, b) -> a * a + b; + + assertThatThrownBy(() -> new CommutativityChecker<>(ImmutableSet.of(), binaryOperator)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorShouldThrowWhenSingleValueToTest() throws Exception { + BinaryOperator<Integer> binaryOperator = (a, b) -> a * a + b; + + assertThatThrownBy(() -> new CommutativityChecker<>(ImmutableSet.of(0), binaryOperator)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorShouldThrowWhenNullOperation() throws Exception { + assertThatThrownBy(() -> new CommutativityChecker<>(ImmutableSet.of(0, 1), null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void findNonCommutativeInputShouldReturnEmptyWhenCommutativeOperation() throws Exception { + Set<Integer> integers = ImmutableSet.of(5, 4, 3, 2, 1); + BinaryOperator<Integer> commutativeOperator = (a, b) -> a + b; + CommutativityChecker<Integer> commutativityChecker = new CommutativityChecker<>(integers, commutativeOperator); + + assertThat(commutativityChecker.findNonCommutativeInput()).isEmpty(); + } + + @Test + public void findNonCommutativeInputShouldReturnDataWhenNonCommutativeOperation() throws Exception { + Set<Integer> integers = ImmutableSet.of(2, 1); + BinaryOperator<Integer> nonCommutativeOperator = (a, b) -> 2 * a + b; + CommutativityChecker<Integer> commutativityChecker = new CommutativityChecker<>(integers, nonCommutativeOperator); + + assertThat(commutativityChecker.findNonCommutativeInput()) + .containsOnly(Pair.of(2, 1)); + } + + @Test + public void findNonCommutativeInputShouldNotReturnStableValues() throws Exception { + Set<Integer> integers = ImmutableSet.of(0, 1, 2); + BinaryOperator<Integer> nonCommutativeOperatorWithStableValues = (a, b) -> a * a + b; + CommutativityChecker<Integer> commutativityChecker = new CommutativityChecker<>(integers, nonCommutativeOperatorWithStableValues); + + assertThat(commutativityChecker.findNonCommutativeInput()) + .containsOnly(Pair.of(1, 2), + Pair.of(0, 2)); + } + + @Test + public void findNonCommutativeInputShouldReturnEmptyWhenNonCommutativeOperationButOnlyStableValues() throws Exception { + Set<Integer> stableValues = ImmutableSet.of(0, 1); + BinaryOperator<Integer> nonCommutativeOperatorWithStableValues = (a, b) -> a * a + b; + CommutativityChecker<Integer> commutativityChecker = new CommutativityChecker<>(stableValues, nonCommutativeOperatorWithStableValues); + + assertThat(commutativityChecker.findNonCommutativeInput()).isEmpty(); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org