[SYNCOPE-641] Merging provided patch + test class
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/ce43e695 Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/ce43e695 Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/ce43e695 Branch: refs/heads/master Commit: ce43e69504e23ce9d73f907a9944671d8b5bb6f0 Parents: 5886fdc a111a9e Author: Francesco Chicchiriccò <[email protected]> Authored: Fri Feb 13 16:03:36 2015 +0100 Committer: Francesco Chicchiriccò <[email protected]> Committed: Fri Feb 13 16:03:36 2015 +0100 ---------------------------------------------------------------------- client/lib/pom.xml | 14 +++- .../client/lib/RestClientFactoryBean.java | 4 +- .../syncope/client/lib/SyncopeClient.java | 4 +- .../syncope/client/lib/ConcurrencyTest.java | 71 ++++++++++++++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/ce43e695/client/lib/pom.xml ---------------------------------------------------------------------- diff --cc client/lib/pom.xml index c4886be,0000000..a834ca8 mode 100644,000000..100644 --- a/client/lib/pom.xml +++ b/client/lib/pom.xml @@@ -1,79 -1,0 +1,91 @@@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.syncope</groupId> + <artifactId>syncope-client</artifactId> + <version>2.0.0-SNAPSHOT</version> + </parent> + + <name>Apache Syncope Client Lib</name> + <description>Apache Syncope Client Lib</description> + <groupId>org.apache.syncope.client</groupId> + <artifactId>syncope-client-lib</artifactId> + <packaging>jar</packaging> + + <properties> + <rootpom.basedir>${basedir}/../..</rootpom.basedir> + </properties> + + <dependencies> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-client</artifactId> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.jaxrs</groupId> + <artifactId>jackson-jaxrs-json-provider</artifactId> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.syncope.common</groupId> + <artifactId>syncope-common-rest-api</artifactId> + <version>${project.version}</version> + </dependency> ++ ++ <dependency> ++ <groupId>org.slf4j</groupId> ++ <artifactId>slf4j-simple</artifactId> ++ <version>${slf4j.version}</version> ++ <scope>test</scope> ++ </dependency> ++ <dependency> ++ <groupId>junit</groupId> ++ <artifactId>junit</artifactId> ++ <scope>test</scope> ++ </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + </plugin> + </plugins> + </build> - </project> ++</project> http://git-wip-us.apache.org/repos/asf/syncope/blob/ce43e695/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java ---------------------------------------------------------------------- diff --cc client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java index 4d77f2f,0000000..ca1c5d2 mode 100644,000000..100644 --- a/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java +++ b/client/lib/src/main/java/org/apache/syncope/client/lib/RestClientFactoryBean.java @@@ -1,67 -1,0 +1,67 @@@ +/* + * 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.syncope.client.lib; + +import javax.ws.rs.core.MediaType; +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean; +import org.apache.cxf.jaxrs.client.WebClient; + +/** + * Provides shortcuts for creating JAX-RS service instances via CXF's <tt>JAXRSClientFactoryBean</tt>. + */ +public class RestClientFactoryBean extends JAXRSClientFactoryBean { + + /** + * Creates an anonymous instance of the given service class, for the given content type. + * + * @param <T> any service class + * @param serviceClass service class reference - * @param mediaType XML or JSON are suppoorted ++ * @param mediaType XML or JSON are supported + * @return anonymous service instance of the given reference class + */ + public <T> T createServiceInstance(final Class<T> serviceClass, final MediaType mediaType) { + return createServiceInstance(serviceClass, mediaType, null, null); + } + + /** + * Creates an authenticated instance of the given service class, for the given content type. + * + * @param <T> any service class + * @param serviceClass service class reference - * @param mediaType XML or JSON are suppoorted ++ * @param mediaType XML or JSON are supported + * @param username username for REST authentication + * @param password password for REST authentication + * @return anonymous service instance of the given reference class + */ + public <T> T createServiceInstance( + final Class<T> serviceClass, final MediaType mediaType, final String username, final String password) { + + if (StringUtils.isNotBlank(username)) { + setUsername(username); + } + if (StringUtils.isNotBlank(password)) { + setPassword(password); + } + setServiceClass(serviceClass); + final T serviceInstance = create(serviceClass); + WebClient.client(serviceInstance).type(mediaType).accept(mediaType); + return serviceInstance; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/ce43e695/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java ---------------------------------------------------------------------- diff --cc client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java index 43c2fea,0000000..2034ce6 mode 100644,000000..100644 --- a/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java +++ b/client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java @@@ -1,214 -1,0 +1,216 @@@ +/* + * 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.syncope.client.lib; + +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.MediaType; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.syncope.common.lib.search.OrderByClauseBuilder; +import org.apache.syncope.common.lib.search.RoleFiqlSearchConditionBuilder; +import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder; +import org.apache.syncope.common.rest.api.Preference; +import org.apache.syncope.common.rest.api.RESTHeaders; + +/** + * Entry point for client access to all REST services exposed by Syncope core; obtain instances via + * {@link SyncopeClientFactoryBean}. + */ +public class SyncopeClient { + + private final MediaType mediaType; + + private final RestClientFactoryBean restClientFactory; + + private final String username; + + private final String password; + + public SyncopeClient(final MediaType mediaType, final RestClientFactoryBean restClientFactory, + final String username, final String password) { + + this.mediaType = mediaType; + this.restClientFactory = restClientFactory; + this.username = username; + this.password = password; + } + + /** + * Returns a new instance of <tt>UserFiqlSearchConditionBuilder</tt>, for assisted building of FIQL queries. + * + * @return default instance of <tt>UserFiqlSearchConditionBuilder</tt> + */ + public static UserFiqlSearchConditionBuilder getUserSearchConditionBuilder() { + return new UserFiqlSearchConditionBuilder(); + } + + /** + * Returns a new instance of <tt>RoleFiqlSearchConditionBuilder</tt>, for assisted building of FIQL queries. + * + * @return default instance of <tt>RoleFiqlSearchConditionBuilder</tt> + */ + public static RoleFiqlSearchConditionBuilder getRoleSearchConditionBuilder() { + return new RoleFiqlSearchConditionBuilder(); + } + + /** + * Returns a new instance of <tt>OrderByClauseBuilder</tt>, for assisted building of <tt>orderby</tt> clauses. + * + * @return default instance of <tt>OrderByClauseBuilder</tt> + */ + public static OrderByClauseBuilder getOrderByClauseBuilder() { + return new OrderByClauseBuilder(); + } + + /** + * Creates an instance of the given service class, with configured content type and authentication. + * + * @param <T> any service class + * @param serviceClass service class reference + * @return service instance of the given reference class + */ + public <T> T getService(final Class<T> serviceClass) { - return restClientFactory.createServiceInstance(serviceClass, mediaType, username, password); ++ synchronized (restClientFactory) { ++ return restClientFactory.createServiceInstance(serviceClass, mediaType, username, password); ++ } + } + + /** + * Sets the given header on the give service instance. + * + * @param <T> any service class + * @param service service class instance + * @param key HTTP header key + * @param values HTTP header values + * @return given service instance, with given header set + */ + public <T> T header(final T service, final String key, final Object... values) { + WebClient.client(service).header(key, values); + return service; + } + + /** + * Creates an instance of the given service class and sets the given header. + * + * @param <T> any service class + * @param serviceClass service class reference + * @param key HTTP header key + * @param values HTTP header values + * @return service instance of the given reference class, with given header set + */ + public <T> T header(final Class<T> serviceClass, final String key, final Object... values) { + return header(getService(serviceClass), key, values); + } + + /** + * Sets the <tt>Prefer</tt> header on the give service instance. + * + * @param <T> any service class + * @param service service class instance + * @param preference preference to be set via <tt>Prefer</tt> header + * @return given service instance, with <tt>Prefer</tt> header set + */ + public <T> T prefer(final T service, final Preference preference) { + return header(service, RESTHeaders.PREFER, preference.toString()); + } + + /** + * Creates an instance of the given service class, with <tt>Prefer</tt> header set. + * + * @param <T> any service class + * @param serviceClass service class reference + * @param preference preference to be set via <tt>Prefer</tt> header + * @return service instance of the given reference class, with <tt>Prefer</tt> header set + */ + public <T> T prefer(final Class<T> serviceClass, final Preference preference) { + return header(serviceClass, RESTHeaders.PREFER, preference.toString()); + } + + /** + * Sets the <tt>If-Match</tt> or <tt>If-None-Match</tt> header on the given service instance. + * + * @param <T> any service class + * @param service service class instance + * @param etag ETag value + * @param ifNot if true then <tt>If-None-Match</tt> is set, <tt>If-Match</tt> otherwise + * @return given service instance, with <tt>If-Match</tt> or <tt>If-None-Match</tt> set + */ + private <T> T match(final T service, final EntityTag etag, final boolean ifNot) { + WebClient.client(service).match(etag, ifNot); + return service; + } + + /** + * Sets the <tt>If-Match</tt> header on the given service instance. + * + * @param <T> any service class + * @param service service class instance + * @param etag ETag value + * @return given service instance, with <tt>If-Match</tt> set + */ + public <T> T ifMatch(final T service, final EntityTag etag) { + return match(service, etag, false); + } + + /** + * Creates an instance of the given service class, with <tt>If-Match</tt> header set. + * + * @param <T> any service class + * @param serviceClass service class reference + * @param etag ETag value + * @return given service instance, with <tt>If-Match</tt> set + */ + public <T> T ifMatch(final Class<T> serviceClass, final EntityTag etag) { + return match(getService(serviceClass), etag, false); + } + + /** + * Sets the <tt>If-None-Match</tt> header on the given service instance. + * + * @param <T> any service class + * @param service service class instance + * @param etag ETag value + * @return given service instance, with <tt>If-None-Match</tt> set + */ + public <T> T ifNoneMatch(final T service, final EntityTag etag) { + return match(service, etag, true); + } + + /** + * Creates an instance of the given service class, with <tt>If-None-Match</tt> header set. + * + * @param <T> any service class + * @param serviceClass service class reference + * @param etag ETag value + * @return given service instance, with <tt>If-None-Match</tt> set + */ + public <T> T ifNoneMatch(final Class<T> serviceClass, final EntityTag etag) { + return match(getService(serviceClass), etag, true); + } + + /** + * Fetches <tt>ETag</tt> header value from latest service run (if available). + * + * @param <T> any service class + * @param service service class instance + * @return <tt>ETag</tt> header value from latest service run (if available) + */ + public <T> EntityTag getLatestEntityTag(final T service) { + return WebClient.client(service).getResponse().getEntityTag(); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/ce43e695/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java ---------------------------------------------------------------------- diff --cc client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java index 0000000,0000000..4657b2f new file mode 100644 --- /dev/null +++ b/client/lib/src/test/java/org/apache/syncope/client/lib/ConcurrencyTest.java @@@ -1,0 -1,0 +1,71 @@@ ++/* ++ * 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.syncope.client.lib; ++ ++import static org.junit.Assert.fail; ++ ++import org.apache.commons.lang3.StringUtils; ++import org.apache.syncope.common.rest.api.service.ResourceService; ++import org.junit.Test; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++public class ConcurrencyTest { ++ ++ private static final Logger LOG = LoggerFactory.getLogger(ConcurrencyTest.class); ++ ++ private static final SyncopeClient client = ++ new SyncopeClientFactoryBean().setAddress("http://url").create("username", "password"); ++ ++ @Test ++ public void multiThreadTest() ++ throws InterruptedException { ++ ++ for (int i = 0; i < 10000; i++) { ++ Thread execution = new Thread("Th-" + StringUtils.leftPad(String.valueOf(i), 5, '0')) { ++ ++ @Override ++ public void run() { ++ ++ try { ++ client.getService(ResourceService.class); ++ ++ LOG.info(getName() + " completed successfully!"); ++ } catch (Exception e) { ++ LOG.error(getName() + " did not complete", e); ++ } ++ } ++ }; ++ execution.start(); ++ } ++ ++ Thread.sleep(10000); ++ } ++ ++ @Test ++ public void multiCallTest() { ++ try { ++ for (int i = 0; i < 10000; i++) { ++ client.getService(ResourceService.class); ++ } ++ } catch (Exception e) { ++ fail(e.getMessage()); ++ } ++ } ++}
