CAMEL-9624 Add Infinispan remote query operation - Also add full integration tests with Infinispan server (optionally enabled with the "infinispan-itests" profile
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/8a7aa470 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/8a7aa470 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/8a7aa470 Branch: refs/heads/master Commit: 8a7aa4702182b4268920ab055d3cc171777ffa48 Parents: c608450 Author: Tristan Tarrant <[email protected]> Authored: Thu Feb 18 20:51:07 2016 +0100 Committer: Andrea Cosentino <[email protected]> Committed: Mon Feb 22 15:21:13 2016 +0100 ---------------------------------------------------------------------- components/camel-infinispan/README.md | 12 ++ components/camel-infinispan/pom.xml | 170 +++++++++++++++++- .../infinispan/InfinispanConstants.java | 2 + .../infinispan/InfinispanOperation.java | 21 +++ .../infinispan/InfinispanQueryBuilder.java | 24 +++ .../remote/InfinispanRemoteOperation.java | 40 +++++ .../InfinispanRemoteQueryProducerIT.java | 180 +++++++++++++++++++ .../camel/component/infinispan/UserUtils.java | 74 ++++++++ 8 files changed, 521 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/README.md ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/README.md b/components/camel-infinispan/README.md new file mode 100644 index 0000000..47b2631 --- /dev/null +++ b/components/camel-infinispan/README.md @@ -0,0 +1,12 @@ +# Camel Infinispan Component + +This component aims at integrating Infinispan 8+ for both embedded and +remote (HotRod) usage. + +## Integration testing + +Please note: the integration tests are disabled in the default Maven profile. +If you wish to run them, enable the 'infinispan-itests' profile as follows: + + mvn clean verify -Pinfinispan-itests + http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/pom.xml b/components/camel-infinispan/pom.xml index 7dd09bd..36bc87e 100644 --- a/components/camel-infinispan/pom.xml +++ b/components/camel-infinispan/pom.xml @@ -42,12 +42,18 @@ </dependency> <dependency> <groupId>org.infinispan</groupId> - <artifactId>infinispan-embedded</artifactId> + <artifactId>infinispan-core</artifactId> + <version>${infinispan-version}</version> + </dependency> + <dependency> + <groupId>org.infinispan</groupId> + <artifactId>infinispan-query-dsl</artifactId> <version>${infinispan-version}</version> + <optional>true</optional> </dependency> <dependency> <groupId>org.infinispan</groupId> - <artifactId>infinispan-remote</artifactId> + <artifactId>infinispan-client-hotrod</artifactId> <version>${infinispan-version}</version> </dependency> @@ -65,6 +71,35 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.infinispan.server</groupId> + <artifactId>infinispan-server-testsuite</artifactId> + <version>${infinispan-version}</version> + <classifier>tests</classifier> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.jboss.remoting3</groupId> + <artifactId>remoting-jmx</artifactId> + </exclusion> + <exclusion> + <groupId>org.infinispan</groupId> + <artifactId>infinispan-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.infinispan</groupId> + <artifactId>infinispan-client-hotrod</artifactId> + </exclusion> + <exclusion> + <groupId>org.infinispan</groupId> + <artifactId>infinispan-query-dsl</artifactId> + </exclusion> + <exclusion> + <groupId>org.infinispan</groupId> + <artifactId>infinispan-remote-query-client</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <scope>test</scope> @@ -100,5 +135,136 @@ </plugins> </build> </profile> + <profile> + <id>infinispan-itests</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <properties> + <server.dir.parent>${project.build.directory}</server.dir.parent> + <server.dir.name>infinispan-server-${infinispan-version}</server.dir.name> + <server.dir>${server.dir.parent}/${server.dir.name}</server.dir> + </properties> + <dependencies> + <dependency> + <groupId>org.infinispan.server</groupId> + <artifactId>infinispan-server</artifactId> + <version>${infinispan-version}</version> + <type>zip</type> + <classifier>bin</classifier> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>unpack-server</id> + <phase>generate-test-resources</phase> + <goals> + <goal>unpack</goal> + </goals> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.infinispan.server</groupId> + <artifactId>infinispan-server</artifactId> + <version>${infinispan-version}</version> + <classifier>bin</classifier> + <type>zip</type> + <outputDirectory>${server.dir.parent}</outputDirectory> + </artifactItem> + </artifactItems> + </configuration> + </execution> + <execution> + <id>deploy-converter-factory</id> + <phase>generate-test-resources</phase> + <goals> + <goal>copy</goal> + </goals> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.infinispan.server</groupId> + <artifactId>infinispan-server-testsuite</artifactId> + <classifier>tests</classifier> + <version>${infinispan-version}</version> + <outputDirectory>${server.dir}/standalone/deployments</outputDirectory> + </artifactItem> + </artifactItems> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.wildfly.plugins</groupId> + <artifactId>wildfly-maven-plugin</artifactId> + <version>1.1.0.Alpha6</version> + <executions> + <execution> + <id>start-server</id> + <phase>pre-integration-test</phase> + <goals> + <goal>start</goal> + </goals> + <configuration> + <skip>${skipTests}</skip> + <jbossHome>${server.dir}</jbossHome> + </configuration> + </execution> + <execution> + <id>configure-caches</id> + <phase>pre-integration-test</phase> + <goals> + <goal>execute-commands</goal> + </goals> + <configuration> + <skip>${skipTests}</skip> + <executeCommands> + <commands> + <!-- Separate cache for protobuf serialized objects. --> + <command>/subsystem=datagrid-infinispan/cache-container=local/local-cache=remote_query:add(configuration=default)</command> + <!-- Separate cache for converter factory which uses Int keys --> + <command>/subsystem=datagrid-infinispan/cache-container=local/local-cache=static_converter_factory:add(configuration=default)</command> + <!-- Separate cache for converter factory which uses Int keys --> + <command>/subsystem=datagrid-infinispan/cache-container=local/local-cache=static_filter_factory:add(configuration=default)</command> + <!-- Separate cache for @ClientListener(includeCurrentState=true) --> + <command>/subsystem=datagrid-infinispan/cache-container=local/local-cache=include_current_state:add(configuration=default)</command> + </commands> + </executeCommands> + </configuration> + </execution> + <execution> + <id>stop-server</id> + <phase>post-integration-test</phase> + <goals> + <goal>shutdown</goal> + </goals> + <configuration> + <skip>${skipTests}</skip> + <jbossHome>${server.dir}</jbossHome> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> </profiles> </project> http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanConstants.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanConstants.java b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanConstants.java index 681ac65..d2a5f95 100644 --- a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanConstants.java +++ b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanConstants.java @@ -48,4 +48,6 @@ public interface InfinispanConstants { String MAX_IDLE_TIME_UNIT = "CamelInfinispanMaxIdleTimeUnit"; String IGNORE_RETURN_VALUES = "CamelInfinispanIgnoreReturnValues"; String EVENT_DATA = "CamelInfinispanEventData"; + String QUERY = "CamelInfinispanOperationQuery"; + String QUERY_BUILDER = "CamelInfinispanQueryBuilder"; } http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanOperation.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanOperation.java b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanOperation.java index f4f7184..c51cc6f 100644 --- a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanOperation.java +++ b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanOperation.java @@ -20,9 +20,11 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.camel.Exchange; +import org.apache.camel.component.infinispan.remote.InfinispanRemoteOperation; import org.apache.camel.util.ObjectHelper; import org.infinispan.commons.api.BasicCache; import org.infinispan.commons.util.concurrent.NotifyingFuture; +import org.infinispan.query.dsl.Query; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -315,8 +317,18 @@ public final class InfinispanOperation { result = cache.clearAsync(); setResult(result, exchange); } + }, QUERY { + @Override + void execute(BasicCache<Object, Object> cache, Exchange exchange) { + Query query = getQuery(cache, exchange); + if (query == null) { + return; + } + setResult(query.list(), exchange); + } }; + void setResult(Object result, Exchange exchange) { exchange.getIn().setHeader(InfinispanConstants.RESULT, result); } @@ -337,6 +349,15 @@ public final class InfinispanOperation { return (Map<? extends Object, ? extends Object>) exchange.getIn().getHeader(InfinispanConstants.MAP); } + Query getQuery(BasicCache<Object, Object> cache, Exchange exchange) { + if (InfinispanUtil.isRemote(cache)) { + return InfinispanRemoteOperation.buildQuery(cache, exchange); + } else { + return null; + } + + } + abstract void execute(BasicCache<Object, Object> cache, Exchange exchange); } http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanQueryBuilder.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanQueryBuilder.java b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanQueryBuilder.java new file mode 100644 index 0000000..828b470 --- /dev/null +++ b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/InfinispanQueryBuilder.java @@ -0,0 +1,24 @@ +/** + * 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.camel.component.infinispan; + +import org.infinispan.query.dsl.Query; +import org.infinispan.query.dsl.QueryFactory; + +public interface InfinispanQueryBuilder { + Query build(QueryFactory<Query> queryFactory); +} http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteOperation.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteOperation.java b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteOperation.java new file mode 100644 index 0000000..38c1856 --- /dev/null +++ b/components/camel-infinispan/src/main/java/org/apache/camel/component/infinispan/remote/InfinispanRemoteOperation.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.camel.component.infinispan.remote; + +import org.apache.camel.Exchange; +import org.apache.camel.component.infinispan.InfinispanConstants; +import org.apache.camel.component.infinispan.InfinispanQueryBuilder; +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.Search; +import org.infinispan.commons.api.BasicCache; +import org.infinispan.query.dsl.Query; + +public final class InfinispanRemoteOperation { + private InfinispanRemoteOperation() { + } + + public static Query buildQuery(BasicCache<Object, Object> cache, Exchange exchange) { + InfinispanQueryBuilder queryBuilder = (InfinispanQueryBuilder) exchange + .getIn().getHeader(InfinispanConstants.QUERY_BUILDER); + if (queryBuilder == null) { + return null; + } + RemoteCache<Object, Object> remoteCache = (RemoteCache<Object, Object>) cache; + return queryBuilder.build(Search.getQueryFactory(remoteCache)); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/InfinispanRemoteQueryProducerIT.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/InfinispanRemoteQueryProducerIT.java b/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/InfinispanRemoteQueryProducerIT.java new file mode 100644 index 0000000..cd19aed --- /dev/null +++ b/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/InfinispanRemoteQueryProducerIT.java @@ -0,0 +1,180 @@ +/** + * 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.camel.component.infinispan; + +import java.io.IOException; +import java.util.List; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.JndiRegistry; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.RemoteCacheManager; +import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; +import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller; +import org.infinispan.commons.util.Util; +import org.infinispan.protostream.FileDescriptorSource; +import org.infinispan.protostream.SerializationContext; +import org.infinispan.protostream.sampledomain.User; +import org.infinispan.protostream.sampledomain.marshallers.GenderMarshaller; +import org.infinispan.protostream.sampledomain.marshallers.UserMarshaller; +import org.infinispan.query.dsl.Query; +import org.infinispan.query.dsl.QueryFactory; +import org.infinispan.query.remote.client.MarshallerRegistration; +import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; +import org.junit.Test; + +import static org.apache.camel.component.infinispan.InfinispanConstants.KEY; +import static org.apache.camel.component.infinispan.InfinispanConstants.OPERATION; +import static org.apache.camel.component.infinispan.InfinispanConstants.QUERY; +import static org.apache.camel.component.infinispan.InfinispanConstants.QUERY_BUILDER; +import static org.apache.camel.component.infinispan.InfinispanConstants.RESULT; +import static org.apache.camel.component.infinispan.InfinispanConstants.VALUE; +import static org.apache.camel.component.infinispan.UserUtils.USERS; +import static org.apache.camel.component.infinispan.UserUtils.createKey; +import static org.apache.camel.component.infinispan.UserUtils.hasUser; + +public class InfinispanRemoteQueryProducerIT extends CamelTestSupport { + + private RemoteCacheManager manager; + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("myCustomContainer", manager); + return registry; + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").to( + "infinispan://?cacheContainer=#myCustomContainer&cacheName=remote_query"); + } + }; + } + + @Override + protected void doPreSetup() throws IOException { + ConfigurationBuilder builder = new ConfigurationBuilder().addServer() + .host("localhost").port(11222) + .marshaller(new ProtoStreamMarshaller()); + + manager = new RemoteCacheManager(builder.build()); + + RemoteCache<String, String> metadataCache = manager + .getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); + metadataCache + .put("sample_bank_account/bank.proto", + Util.read(InfinispanRemoteQueryProducerIT.class + .getResourceAsStream("/sample_bank_account/bank.proto"))); + MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller + .getSerializationContext(manager)); + + SerializationContext serCtx = ProtoStreamMarshaller + .getSerializationContext(manager); + serCtx.registerProtoFiles(FileDescriptorSource + .fromResources("/sample_bank_account/bank.proto")); + serCtx.registerMarshaller(new UserMarshaller()); + serCtx.registerMarshaller(new GenderMarshaller()); + } + + @Override + protected void doPostSetup() throws Exception { + /* Preload data. */ + for (final User user : USERS) { + Exchange request = template.request("direct:start", + new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + Message in = exchange.getIn(); + in.setHeader(KEY, createKey(user)); + in.setHeader(VALUE, user); + } + }); + assertNull(request.getException()); + } + } + + @Test + public void producerQueryOperationWithoutQueryBuilder() throws Exception { + Exchange request = template.request("direct:start", new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getIn().setHeader(OPERATION, QUERY); + } + }); + assertNull(request.getException()); + + @SuppressWarnings("unchecked") + List<User> queryResult = (List<User>) request.getIn().getHeader(RESULT); + assertNull(queryResult); + } + + @Test + public void producerQueryWithoutResult() throws Exception { + Exchange request = template.request("direct:start", new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + exchange.getIn().setHeader(OPERATION, QUERY); + exchange.getIn().setHeader(QUERY_BUILDER, + new InfinispanQueryBuilder() { + public Query build(QueryFactory<Query> queryFactory) { + return queryFactory.from(User.class) + .having("name").like("%abc%") + .toBuilder().build(); + } + }); + } + }); + assertNull(request.getException()); + + @SuppressWarnings("unchecked") + List<User> queryResult = (List<User>) request.getIn().getHeader(RESULT); + assertNotNull(queryResult); + assertEquals(0, queryResult.size()); + } + + @Test + public void producerQueryWithResult() throws Exception { + Exchange request = template.request("direct:start", new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + Message in = exchange.getIn(); + in.setHeader(OPERATION, QUERY); + in.setHeader(QUERY_BUILDER, new InfinispanQueryBuilder() { + public Query build(QueryFactory<Query> queryFactory) { + return queryFactory.from(User.class).having("name") + .like("%A").toBuilder().build(); + } + }); + } + }); + assertNull(request.getException()); + + @SuppressWarnings("unchecked") + List<User> queryResult = (List<User>) request.getIn().getHeader(RESULT); + assertNotNull(queryResult); + assertEquals(2, queryResult.size()); + assertTrue(hasUser(queryResult, "nameA", "surnameA")); + assertTrue(hasUser(queryResult, "nameA", "surnameB")); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/8a7aa470/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/UserUtils.java ---------------------------------------------------------------------- diff --git a/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/UserUtils.java b/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/UserUtils.java new file mode 100644 index 0000000..4126b83 --- /dev/null +++ b/components/camel-infinispan/src/test/java/org/apache/camel/component/infinispan/UserUtils.java @@ -0,0 +1,74 @@ +/** + * 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.camel.component.infinispan; + +import java.util.List; +import org.infinispan.protostream.sampledomain.User; + +public final class UserUtils { + public static final User[] USERS = new User[]{ + createUser("nameA", "surnameA"), + createUser("nameA", "surnameB"), + createUser("nameB", "surnameB")}; + + private UserUtils() { + } + + public static String createKey(User user) { + return String.format("%s+%s", user.getName(), user.getSurname()); + } + + public static User createUser(String name, String surname) { + User user = new User(); + user.setName(name); + user.setSurname(surname); + return user; + } + + public static boolean eq(String str1, String str2) { + if (str1 == null) { + return str2 == null; + } else { + return str1.equals(str2); + } + } + + public static boolean eq(User user, String name, String surname) { + if (user == null) { + return false; + } + if (!eq(user.getName(), name)) { + return false; + } + if (!eq(user.getSurname(), surname)) { + return false; + } + return true; + } + + public static boolean hasUser(List<User> users, String name, String surname) { + if (users == null) { + return false; + } + for (User user : users) { + if (eq(user, name, surname)) { + return true; + } + } + return false; + } +}
