CAMEL-6568: Initial version of LinkedIn component
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/b490a90c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/b490a90c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/b490a90c Branch: refs/heads/linkedin-component Commit: b490a90cd2d4ecd2d2382e0c09323db75b9c0918 Parents: 91e19c1 Author: Dhiraj Bokde <dhira...@yahoo.com> Authored: Thu Jul 10 17:46:47 2014 -0700 Committer: Dhiraj Bokde <dhira...@yahoo.com> Committed: Thu Jul 10 17:49:20 2014 -0700 ---------------------------------------------------------------------- .../camel-linkedin/camel-linkedin-api/pom.xml | 214 ++ .../component/linkedin/api/DoubleAdapter.java | 35 + .../linkedin/api/LinkedInException.java | 47 + .../api/LinkedInExceptionResponseFilter.java | 77 + .../api/LinkedInOAuthRequestFilter.java | 269 +++ .../component/linkedin/api/LongAdapter.java | 35 + .../component/linkedin/api/OAuthParams.java | 107 + .../component/linkedin/api/OAuthScope.java | 64 + .../linkedin/api/OAuthSecureStorage.java | 36 + .../component/linkedin/api/OAuthToken.java | 50 + .../src/main/resources/linkedin-api-schema.xjb | 447 ++++ .../src/main/resources/linkedin-api-schema.xsd | 2255 ++++++++++++++++++ .../src/main/resources/linkedin-api-wadl.xml | 1045 ++++++++ .../src/main/resources/wadl.xsd | 263 ++ .../api/AbstractResourceIntegrationTest.java | 125 + .../api/PeopleResourceIntegrationTest.java | 99 + .../api/SearchResourceIntegrationTest.java | 47 + .../camel-linkedin-component/pom.xml | 280 +++ .../component/linkedin/LinkedInComponent.java | 106 + .../linkedin/LinkedInConfiguration.java | 155 ++ .../component/linkedin/LinkedInConsumer.java | 58 + .../component/linkedin/LinkedInEndpoint.java | 174 ++ .../component/linkedin/LinkedInProducer.java | 59 + .../internal/CachingOAuthSecureStorage.java | 50 + .../linkedin/internal/LinkedInConstants.java | 29 + .../internal/LinkedInPropertiesHelper.java | 40 + .../org/apache/camel/component/linkedin | 1 + .../linkedin/AbstractLinkedInTestSupport.java | 73 + .../CommentsResourceIntegrationTest.java | 66 + .../CompaniesResourceIntegrationTest.java | 353 +++ .../linkedin/GroupsResourceIntegrationTest.java | 65 + .../linkedin/JobsResourceIntegrationTest.java | 93 + .../linkedin/PeopleResourceIntegrationTest.java | 636 +++++ .../linkedin/PostsResourceIntegrationTest.java | 162 ++ .../linkedin/SearchResourceIntegrationTest.java | 161 ++ .../src/test/resources/log4j.properties | 36 + .../src/test/resources/test-options.properties | 28 + components/camel-linkedin/pom.xml | 66 + components/pom.xml | 1 + 39 files changed, 7907 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/pom.xml b/components/camel-linkedin/camel-linkedin-api/pom.xml new file mode 100644 index 0000000..012d04f --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/pom.xml @@ -0,0 +1,214 @@ +<?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/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel.component.linkedin</groupId> + <artifactId>camel-linkedin-parent</artifactId> + <version>2.14-SNAPSHOT</version> + </parent> + + <artifactId>camel-linkedin-api</artifactId> + <name>Camel LinkedIn Component API</name> + <description>API for Camel LinkedIn Component</description> + <packaging>bundle</packaging> + + <properties> + <camel.osgi.export.pkg>org.apache.camel.component.linkedin.api*</camel.osgi.export.pkg> + <htmlunit-version>2.15</htmlunit-version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-core</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-frontend-jaxrs</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-security-oauth2</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-extension-providers</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-tools-wadlto-jaxrs</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>net.sourceforge.htmlunit</groupId> + <artifactId>htmlunit</artifactId> + <version>${htmlunit-version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson2-version}</version> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <scope>test</scope> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-client</artifactId> + <version>${cxf-version}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <testResources> + <testResource> + <directory>${project.basedir}/src/test/resources</directory> + </testResource> + <testResource> + <directory>${project.basedir}/../camel-linkedin-component/src/test/resources</directory> + </testResource> + </testResources> + + <plugins> + + <!-- uncomment to validate XSD since wadl2java doesn't report line numbers in errors --> +<!-- + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>jaxb2-maven-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <goals> + <goal>xjc</goal> + </goals> + <configuration> + <target>2.1</target> + <schemaDirectory>src/main/resources</schemaDirectory> + <schemaFiles>linkedin-api-schema.xsd</schemaFiles> + </configuration> + </execution> + </executions> + </plugin> +--> + + <!-- Generate API from WADL --> + <plugin> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-wadl2java-plugin</artifactId> + <version>${cxf-version}</version> + <executions> + <execution> + <id>generate-wadl-sources</id> + <phase>generate-sources</phase> + <goals> + <goal>wadl2java</goal> + </goals> + <configuration> + <sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot> + <wadlOptions> + <wadlOption> + <wadl>${project.basedir}/src/main/resources/linkedin-api-wadl.xml</wadl> + <packagename>org.apache.camel.component.linkedin.api</packagename> + <bindingFiles> + <bindingFile>${project.basedir}/src/main/resources/linkedin-api-schema.xjb</bindingFile> + </bindingFiles> + <extraargs> + <extraarg>-verbose</extraarg> + <extraarg>-generateEnums</extraarg> + <extraarg>-xjc-quiet</extraarg> + </extraargs> + </wadlOption> + </wadlOptions> + </configuration> + </execution> + </executions> + </plugin> + + <!-- add generated source to build --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>add-generated-sources</id> + <goals> + <goal>add-source</goal> + </goals> + <configuration> + <sources> + <source>${project.build.directory}/generated-sources/cxf</source> + </sources> + </configuration> + </execution> + </executions> + </plugin> + + <!-- to generate API Javadoc --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <executions> + <execution> + <id>add-javadoc</id> + <goals> + <goal>jar</goal> + </goals> + <configuration> + <attach>true</attach> + <source>1.6</source> + <quiet>true</quiet> + <detectOfflineLinks>false</detectOfflineLinks> + <javadocVersion>1.6</javadocVersion> + <encoding>UTF-8</encoding> + </configuration> + </execution> + </executions> + </plugin> + + </plugins> + </build> + +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/DoubleAdapter.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/DoubleAdapter.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/DoubleAdapter.java new file mode 100644 index 0000000..029681a --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/DoubleAdapter.java @@ -0,0 +1,35 @@ +/** + * 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.linkedin.api; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public class DoubleAdapter extends XmlAdapter<String, Double> +{ + + public Double unmarshal(String value) { + return javax.xml.bind.DatatypeConverter.parseDouble(value); + } + + public String marshal(Double value) { + if (value == null) { + return null; + } + return javax.xml.bind.DatatypeConverter.printDouble(value); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInException.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInException.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInException.java new file mode 100644 index 0000000..74b7520 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInException.java @@ -0,0 +1,47 @@ +/** + * 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.linkedin.api; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; + +import org.apache.camel.component.linkedin.api.model.Error; + +/** + * Exception wrapper for {@link org.apache.camel.component.linkedin.api.model.Error} + */ +public class LinkedInException extends WebApplicationException { + + private static final long serialVersionUID = -6570614972033527197L; + + private final Error error; + private final Response response; + + public LinkedInException(Error error, Response response) { + super(error.getMessage(), response); + this.error = error; + this.response = response; + } + + public Error getError() { + return error; + } + + public Response getResponse() { + return response; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInExceptionResponseFilter.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInExceptionResponseFilter.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInExceptionResponseFilter.java new file mode 100644 index 0000000..4b180d9 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInExceptionResponseFilter.java @@ -0,0 +1,77 @@ +/** + * 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.linkedin.api; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.client.ClientResponseFilter; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; + +import org.apache.camel.component.linkedin.api.model.Error; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Response filter for throwing {@link LinkedInException} + * when response contains {@link org.apache.camel.component.linkedin.api.model.Error} + */ +@Provider +@Priority(Priorities.USER) +public class LinkedInExceptionResponseFilter implements ClientResponseFilter { + + private static final Logger LOG = LoggerFactory.getLogger(LinkedInExceptionResponseFilter.class); + private final JAXBContext jaxbContext; + + public LinkedInExceptionResponseFilter() { + try { + jaxbContext = JAXBContext.newInstance(Error.class.getPackage().getName()); + } catch (JAXBException e) { + throw new IllegalArgumentException("Error initializing JAXB: " + e.getMessage(), e); + } + } + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + if (responseContext.getStatus() != Response.Status.OK.getStatusCode() && responseContext.hasEntity()) { + try { + final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + final Error error = (Error) unmarshaller.unmarshal(responseContext.getEntityStream()); + + final Response.ResponseBuilder builder = Response.status(responseContext.getStatusInfo()); + builder.entity(error); + // copy response headers + for (Map.Entry<String, List<String>> header : responseContext.getHeaders().entrySet()) { + builder.header(header.getKey(), header.getValue()); + } + + throw new LinkedInException(error, builder.build()); + } catch (JAXBException e) { + // log and ignore + LOG.warn("Unable to parse LinkedIn error: " + e.getMessage(), e); + } + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInOAuthRequestFilter.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInOAuthRequestFilter.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInOAuthRequestFilter.java new file mode 100644 index 0000000..4fd7194 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LinkedInOAuthRequestFilter.java @@ -0,0 +1,269 @@ +/** + * 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.linkedin.api; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.ext.Provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.ProxyConfig; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebClientOptions; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.conn.params.ConnRoutePNames; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * LinkedIn OAuth request filter to handle OAuth token. + */ +@Provider +@Priority(Priorities.AUTHENTICATION) +public final class LinkedInOAuthRequestFilter implements ClientRequestFilter { + + public static final String BASE_ADDRESS = "https://api.linkedin.com/v1"; + + private static final Logger LOG = LoggerFactory.getLogger(LinkedInOAuthRequestFilter.class); + + private static final String AUTHORIZATION_URL = "https://www.linkedin.com/uas/oauth2/authorization?" + + "response_type=code&client_id=%s&state=%s&redirect_uri=%s"; + private static final String AUTHORIZATION_URL_WITH_SCOPE = "https://www.linkedin.com/uas/oauth2/authorization?" + + "response_type=code&client_id=%s&state=%s&scope=%s&redirect_uri=%s"; + + private static final String ACCESS_TOKEN_URL = "https://www.linkedin.com/uas/oauth2/accessToken?" + + "grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s"; + + private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("&?([^=]+)=([^&]+)"); + + private final WebClient webClient; + + private final OAuthParams oAuthParams; + + private OAuthToken oAuthToken; + + @SuppressWarnings("deprecation") + public LinkedInOAuthRequestFilter(OAuthParams oAuthParams, Map<String, Object> httpParams, + boolean lazyAuth) { + + this.oAuthParams = oAuthParams; + this.oAuthToken = null; + + // create HtmlUnit client + webClient = new WebClient(BrowserVersion.FIREFOX_24); + final WebClientOptions options = webClient.getOptions(); + options.setRedirectEnabled(true); + options.setJavaScriptEnabled(false); + options.setThrowExceptionOnFailingStatusCode(true); + options.setThrowExceptionOnScriptError(true); + options.setPrintContentOnFailingStatusCode(LOG.isDebugEnabled()); + + // add HTTP proxy if set + if (httpParams != null && httpParams.get(ConnRoutePNames.DEFAULT_PROXY) != null) { + final HttpHost proxyHost = (HttpHost) httpParams.get(ConnRoutePNames.DEFAULT_PROXY); + final Boolean socksProxy = (Boolean) httpParams.get("http.route.socks-proxy"); + final ProxyConfig proxyConfig = new ProxyConfig(proxyHost.getHostName(), proxyHost.getPort(), + socksProxy != null ? socksProxy : false); + options.setProxyConfig(proxyConfig); + } + + if (!lazyAuth) { + try { + updateOAuthToken(); + } catch (IOException e) { + throw new IllegalArgumentException( + String.format("Error authorizing user %s: %s", oAuthParams.getUserName(), e.getMessage()), e); + } + } + } + + @SuppressWarnings("deprecation") + private String getRefreshToken() { + // authorize application on user's behalf + webClient.getOptions().setRedirectEnabled(true); + + try { + final String csrfId = String.valueOf(new SecureRandom().nextLong()); + + final String encodedRedirectUri = URLEncoder.encode(oAuthParams.getRedirectUri(), "UTF-8"); + final OAuthScope[] scopes = oAuthParams.getScopes(); + + final String url; + if (scopes == null || scopes.length == 0) { + url = String.format(AUTHORIZATION_URL, oAuthParams.getClientId(), + csrfId, encodedRedirectUri); + } else { + final int nScopes = scopes.length; + final StringBuilder builder = new StringBuilder(); + int i = 0; + for (OAuthScope scope : scopes) { + builder.append(scope.getValue()); + if (++i < nScopes) { + builder.append("%20"); + } + } + url = String.format(AUTHORIZATION_URL_WITH_SCOPE, oAuthParams.getClientId(), csrfId, + builder.toString(), encodedRedirectUri); + } + final HtmlPage authPage = webClient.getPage(url); + + // submit login credentials + final HtmlForm loginForm = authPage.getFormByName("oauth2SAuthorizeForm"); + final HtmlTextInput login = loginForm.getInputByName("session_key"); + login.setText(oAuthParams.getUserName()); + final HtmlPasswordInput password = loginForm.getInputByName("session_password"); + password.setText(oAuthParams.getUserPassword()); + final HtmlSubmitInput submitInput = loginForm.getInputByName("authorize"); + + // disable redirect to avoid loading redirect URL + webClient.getOptions().setRedirectEnabled(false); + + // validate CSRF and get authorization code + String redirectQuery; + try { + final Page redirectPage = submitInput.click(); + redirectQuery = redirectPage.getUrl().getQuery(); + } catch (FailingHttpStatusCodeException e) { + // escalate non redirect errors + if (e.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { + throw e; + } + final String location = e.getResponse().getResponseHeaderValue("Location"); + redirectQuery = location.substring(location.indexOf('?') + 1); + } + final Map<String, String> params = new HashMap<String, String>(); + final Matcher matcher = QUERY_PARAM_PATTERN.matcher(redirectQuery); + while (matcher.find()) { + params.put(matcher.group(1), matcher.group(2)); + } + final String state = params.get("state"); + if (!csrfId.equals(state)) { + throw new SecurityException("Invalid CSRF code!"); + } else { + // return authorization code + // TODO check results?? + return params.get("code"); + } + + } catch (IOException e) { + throw new IllegalArgumentException("Error authorizing application: " + e.getMessage(), e); + } + } + + public void close() { + webClient.closeAllWindows(); + } + + private OAuthToken getAccessToken(String refreshToken) throws IOException { + final String tokenUrl = String.format(ACCESS_TOKEN_URL, refreshToken, + oAuthParams.getRedirectUri(), oAuthParams.getClientId(), oAuthParams.getClientSecret()); + final WebRequest webRequest = new WebRequest(new URL(tokenUrl), HttpMethod.POST); + + final WebResponse webResponse = webClient.loadWebResponse(webRequest); + if (webResponse.getStatusCode() != HttpStatus.SC_OK) { + throw new IOException(String.format("Error getting access token: [%s: %s]", + webResponse.getStatusCode(), webResponse.getStatusMessage())); + } + final long currentTime = System.currentTimeMillis(); + final ObjectMapper mapper = new ObjectMapper(); + final Map map = mapper.readValue(webResponse.getContentAsStream(), Map.class); + final String accessToken = map.get("access_token").toString(); + final Integer expiresIn = Integer.valueOf(map.get("expires_in").toString()); + return new OAuthToken(refreshToken, accessToken, + currentTime + TimeUnit.MILLISECONDS.convert(expiresIn, TimeUnit.SECONDS)); + } + + public synchronized OAuthToken getOAuthToken() { + return oAuthToken; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + updateOAuthToken(); + + // add OAuth query param + final String requestUri = requestContext.getUri().toString(); + final StringBuilder builder = new StringBuilder(requestUri); + if (requestUri.contains("?")) { + builder.append('&'); + } else { + builder.append('?'); + } + builder.append("oauth2_access_token=").append(oAuthToken.getAccessToken()); + requestContext.setUri(URI.create(builder.toString())); + } + + private synchronized void updateOAuthToken() throws IOException { + + // check whether an update is needed + final long currentTime = System.currentTimeMillis(); + if (oAuthToken == null || oAuthToken.getExpiryTime() < currentTime) { + LOG.info("OAuth token doesn't exist or has expired"); + + // check whether a secure store is provided + final OAuthSecureStorage secureStorage = oAuthParams.getSecureStorage(); + if (secureStorage != null) { + + oAuthToken = secureStorage.getOAuthToken(); + // if it returned a valid token, we are done, otherwise fall through and generate a new token + if (oAuthToken != null && oAuthToken.getExpiryTime() > currentTime) { + return; + } + LOG.info("OAuth secure storage returned a null or expired token, creating a new token..."); + + // throw an exception if a user password is not set for authorization + if (oAuthParams.getUserPassword() == null || oAuthParams.getUserPassword().isEmpty()) { + throw new IllegalArgumentException("Missing password for LinkedIn authorization"); + } + } + + // need new OAuth token, authorize user, LinkedIn does not support OAuth2 grant_type=refresh_token + final String refreshToken = getRefreshToken(); + this.oAuthToken = getAccessToken(refreshToken); + LOG.info("OAuth token created!"); + + // notify secure storage + if (secureStorage != null) { + secureStorage.saveOAuthToken(this.oAuthToken); + } + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LongAdapter.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LongAdapter.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LongAdapter.java new file mode 100644 index 0000000..3fe63cc --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/LongAdapter.java @@ -0,0 +1,35 @@ +/** + * 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.linkedin.api; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +public class LongAdapter extends XmlAdapter<String, Long> +{ + + public Long unmarshal(String value) { + return new Long(value); + } + + public String marshal(Long value) { + if (value == null) { + return null; + } + return value.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthParams.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthParams.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthParams.java new file mode 100644 index 0000000..0e73a13 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthParams.java @@ -0,0 +1,107 @@ +/** + * 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.linkedin.api; + +import java.util.Arrays; + +/** + * Parameters for OAuth 2.0 flow used by {@link LinkedInOAuthRequestFilter}. +*/ +public class OAuthParams { + + private String userName; + private String userPassword; + + private OAuthSecureStorage secureStorage; + + private String clientId; + private String clientSecret; + + private OAuthScope[] scopes; + private String redirectUri; + + public OAuthParams() { + } + + public OAuthParams(String userName, String userPassword, OAuthSecureStorage secureStorage, + String clientId, String clientSecret, + String redirectUri, OAuthScope... scopes) { + this.userName = userName; + this.userPassword = userPassword; + this.secureStorage = secureStorage; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.scopes = scopes != null ? Arrays.copyOf(scopes, scopes.length) : null; + this.redirectUri = redirectUri; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getUserPassword() { + return userPassword; + } + + public void setUserPassword(String userPassword) { + this.userPassword = userPassword; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public OAuthScope[] getScopes() { + return scopes; + } + + public void setScopes(OAuthScope[] scopes) { + this.scopes = scopes; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public OAuthSecureStorage getSecureStorage() { + return secureStorage; + } + + public void setSecureStorage(OAuthSecureStorage secureStorage) { + this.secureStorage = secureStorage; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthScope.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthScope.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthScope.java new file mode 100644 index 0000000..aadf353 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthScope.java @@ -0,0 +1,64 @@ +/** + * 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.linkedin.api; + +/** + * OAuth scope for use in {@link LinkedInOAuthRequestFilter} + */ +public enum OAuthScope { + + R_BASICPROFILE("r_basicprofile"), + R_FULLPROFILE("r_fullprofile"), + R_EMAILADDRESS("r_emailaddress"), + R_NETWORK("r_network"), + R_CONTACTINFO("r_contactinfo"), + RW_NUS("rw_nus"), + RW_COMPANY_ADMIN("rw_company_admin"), + RW_GROUPS("rw_groups"), + W_MESSAGES("w_messages"); + + private final String value; + + private OAuthScope(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static OAuthScope fromValue(String value) { + for (OAuthScope scope : values()) { + if (scope.value.equals(value)) { + return scope; + } + } + throw new IllegalArgumentException(value); + } + + public static OAuthScope[] fromValues(String... values) { + if (values == null || values.length == 0) { + return new OAuthScope[0]; + } + final OAuthScope[] result = new OAuthScope[values.length]; + int i = 0; + for (String value : values) { + result[i++] = fromValue(value); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthSecureStorage.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthSecureStorage.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthSecureStorage.java new file mode 100644 index 0000000..cf5542e --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthSecureStorage.java @@ -0,0 +1,36 @@ +/** + * 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.linkedin.api; + +/** + * Secure token storage for {@link OAuthToken} + */ +public interface OAuthSecureStorage { + + /** + * Get token from secure storage. + * @return null if a secure token doesn't exist and {@link LinkedInOAuthRequestFilter} must create one. + */ + OAuthToken getOAuthToken(); + + /** + * Save token to secure storage. + * Only called when {@link LinkedInOAuthRequestFilter} creates one. + * @param newToken + */ + void saveOAuthToken(OAuthToken newToken); +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthToken.java ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthToken.java b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthToken.java new file mode 100644 index 0000000..cf04380 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/java/org/apache/camel/component/linkedin/api/OAuthToken.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.camel.component.linkedin.api; + +/** +* LinkedIn OAuth Token +*/ +public final class OAuthToken { + + private final String refreshToken; + private final String accessToken; + private long expiryTime; + + public OAuthToken(String refreshToken, String accessToken, long expiryTime) { + this.refreshToken = refreshToken; + this.accessToken = accessToken; + this.expiryTime = expiryTime; + } + + public String getRefreshToken() { + return refreshToken; + } + + public String getAccessToken() { + return accessToken; + } + + public long getExpiryTime() { + return expiryTime; + } + + // package method for testing only + void setExpiryTime(long expiryTime) { + this.expiryTime = expiryTime; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/b490a90c/components/camel-linkedin/camel-linkedin-api/src/main/resources/linkedin-api-schema.xjb ---------------------------------------------------------------------- diff --git a/components/camel-linkedin/camel-linkedin-api/src/main/resources/linkedin-api-schema.xjb b/components/camel-linkedin/camel-linkedin-api/src/main/resources/linkedin-api-schema.xjb new file mode 100644 index 0000000..778a4f5 --- /dev/null +++ b/components/camel-linkedin/camel-linkedin-api/src/main/resources/linkedin-api-schema.xjb @@ -0,0 +1,447 @@ +<?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. + --> +<bindings version="2.0" xmlns="http://java.sun.com/xml/ns/jaxb" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" + extensionBindingPrefixes="xjc" + schemaLocation="linkedin-api-schema.xsd" node="/xs:schema"> + + <!-- Copied with permission from the linkedin-j library https://code.google.com/p/linkedin-j/ --> + <globalBindings localScoping="toplevel" generateValueClass="true"> + <xjc:javaType name="Double" xmlType="xs:double" + adapter="org.apache.camel.component.linkedin.api.DoubleAdapter"/> + <xjc:javaType name="Long" xmlType="xs:integer" + adapter="org.apache.camel.component.linkedin.api.LongAdapter"/> + <!-- <xjc:superInterface name="SchemaEntity"/> --> + <!-- <xjc:superClass name="BaseSchemaEntity"/> --> + <!-- <xjc:serializable uid="1L"/> --> + <!-- <xjc:serializable uid="6877416375268387499L"/> --> + </globalBindings> + + <schemaBindings> + <package name="org.apache.camel.component.linkedin.api.model" /> + </schemaBindings> + + <bindings node="//xs:element[@name='content-type']/xs:simpleType"> + <typesafeEnumClass name="NetworkUpdateContentType"> + <typesafeEnumMember name="LINKED_IN_HTML" value="linkedin-html"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='connect-type']/xs:simpleType"> + <typesafeEnumClass name="InviteConnectType" > + <typesafeEnumMember name="FRIEND" value="friend"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='update-type']/xs:simpleType"> + <typesafeEnumClass name="NetworkUpdateReturnType"> + <typesafeEnumMember name="ANSWER_UPDATE" value="ANSW"/> + <typesafeEnumMember name="APPLICATION_CONNECTION_UPDATE" value="APPM"/> + <typesafeEnumMember name="APPLICATION_TO_MEMBER_UPDATE" value="APPS"/> + <typesafeEnumMember name="CONNECTION_ADDED_CONNECTIONS" value="CONN"/> + <typesafeEnumMember name="NEW_CONNECTIONS" value="NCON"/> + <typesafeEnumMember name="CONTACT_JOINED" value="CCEM"/> + <typesafeEnumMember name="JOB_POSTED" value="JOBP"/> + <typesafeEnumMember name="CONNECTION_JOINED_GROUP" value="JGRP"/> + <typesafeEnumMember name="CONNECTION_UPDATED_PICTURE" value="PICU"/> + <typesafeEnumMember name="CONNECTION_RECOMMENDED" value="PREC"/> + <typesafeEnumMember name="CONNECTION_UPDATED_PROFILE" value="PROF"/> + <typesafeEnumMember name="QUESTION_UPDATED" value="QSTN"/> + <typesafeEnumMember name="STATUS_UPDATED" value="STAT"/> + <typesafeEnumMember name="SHARED_ITEM" value="SHAR"/> + <typesafeEnumMember name="EXTENDED_PROFILE_UPDATED" value="PRFX"/> + <typesafeEnumMember name="COMPANY_UPDATED" value="CMPY"/> + <typesafeEnumMember name="VIRAL_UPDATE" value="VIRL"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='recommendation-type']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="RecommendationCode"> + <typesafeEnumMember name="COLLEAGUE" value="colleague"/> + <typesafeEnumMember name="BUSINESS_PARTNER" value="business-partner"/> + <typesafeEnumMember name="SERVICE_PROVIDER" value="service-provider"/> + <typesafeEnumMember name="EDUCATION" value="education"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='im-account-type']/xs:simpleType"> + <typesafeEnumClass name="ImAccountType"> + <typesafeEnumMember name="AIM" value="aim"/> + <typesafeEnumMember name="GTALK" value="gtalk"/> + <typesafeEnumMember name="ICQ" value="icq"/> + <typesafeEnumMember name="MSN" value="msn"/> + <typesafeEnumMember name="SKYPE" value="skype"/> + <typesafeEnumMember name="YAHOO" value="yahoo"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='phone-type']/xs:simpleType"> + <typesafeEnumClass name="PhoneType"> + <typesafeEnumMember name="HOME" value="home"/> + <typesafeEnumMember name="WORK" value="work"/> + <typesafeEnumMember name="MOBILE" value="mobile"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='level']/xs:simpleType"> + <typesafeEnumClass name="ProficiencyLevelType"> + <typesafeEnumMember name="ELEMENTARY" value="elementary"/> + <typesafeEnumMember name="LIMITED_WORKING" value="limited_working"/> + <typesafeEnumMember name="PROFESSIONAL_WORKING" value="professional_working"/> + <typesafeEnumMember name="FULL_PROFESSIONAL" value="full_professional"/> + <typesafeEnumMember name="NATIVE_BILINGUAL" value="native_or_bilingual"/> + <typesafeEnumMember name="BEGINNER" value="beginner"/> + <typesafeEnumMember name="INTERMEDIATE" value="intermediate"/> + <typesafeEnumMember name="ADVANCED" value="advanced"/> + <typesafeEnumMember name="EXPERT" value="expert"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='facet']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="FacetType"> + <typesafeEnumMember name="LOCATION" value="location"/> + <typesafeEnumMember name="INDUSTRY" value="industry"/> + <typesafeEnumMember name="NETWORK" value="network"/> + <typesafeEnumMember name="LANGUAGE" value="language"/> + <typesafeEnumMember name="CURRENT_COMPANY" value="current-company"/> + <typesafeEnumMember name="PAST_COMPANY" value="past-company"/> + <typesafeEnumMember name="SCHOOL" value="school"/> + <typesafeEnumMember name="COMPANY_SIZE" value="company-size"/> + <typesafeEnumMember name="NUM_FOLLOWERS_RANGE" value="num-followers-range"/> + <typesafeEnumMember name="FORTUNE" value="fortune"/> + <typesafeEnumMember name="COMPANY" value="company"/> + <typesafeEnumMember name="DATE_POSTED" value="date-posted"/> + <typesafeEnumMember name="JOB_FUNCTION" value="job-function"/> + <typesafeEnumMember name="EXPERIENCE_LEVEL" value="experience-level"/> + <typesafeEnumMember name="SALARY" value="salary"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='visibility']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="VisibilityType"> + <typesafeEnumMember name="ANYONE" value="anyone"/> + <typesafeEnumMember name="ALL_MEMBERS" value="all-members"/> + <typesafeEnumMember name="CONNECTIONS_ONLY" value="connections-only"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='role']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="RoleCode"> + <typesafeEnumMember name="HIRING_MANAGER" value="H"/> + <typesafeEnumMember name="COMPANY_RECRUITER" value="R"/> + <typesafeEnumMember name="STAFFING_FIRM" value="S"/> + <typesafeEnumMember name="COMPANY_EMPLOYEE" value="W"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='profile-field']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="ProfileFieldCode"> + <typesafeEnumMember name="DESCRIPTION" value="description"/> + <typesafeEnumMember name="SPECIALITY" value="speciality"/> + <typesafeEnumMember name="LOGO" value="logo"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='job-type']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="JobTypeCode"> + <typesafeEnumMember name="FULL_TIME" value="F"/> + <typesafeEnumMember name="PART_TIME" value="P"/> + <typesafeEnumMember name="CONTRACT" value="C"/> + <typesafeEnumMember name="TEMPORARY" value="T"/> + <typesafeEnumMember name="OTHER" value="O"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='experience-level']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="ExperienceLevelCode"> + <typesafeEnumMember name="NOT_APPLICABLE" value="0"/> + <typesafeEnumMember name="INTERNSHIP" value="1"/> + <typesafeEnumMember name="ENTRY_LEVEL" value="2"/> + <typesafeEnumMember name="ASSOCIATE" value="3"/> + <typesafeEnumMember name="MID_SENIOR_LEVEL" value="4"/> + <typesafeEnumMember name="DIRECTOR" value="5"/> + <typesafeEnumMember name="EXECUTIVE" value="6"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:complexType[@name='company-status']/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="CompanyStatusCode"> + <typesafeEnumMember name="OPERATING" value="OPR"/> + <typesafeEnumMember name="OPERATING_SUBSIDIARY" value="OPS"/> + <typesafeEnumMember name="REORGANIZING" value="RRG"/> + <typesafeEnumMember name="OUT_OF_BUSINESS" value="OOB"/> + <typesafeEnumMember name="ACQUIRED" value="ACQ"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='company-type']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="CompanyTypeCode"> + <typesafeEnumMember name="PUBLIC_COMPANY" value="C"/> + <typesafeEnumMember name="EDUCATIONAL" value="D"/> + <typesafeEnumMember name="SELF_EMPLOYED" value="E"/> + <typesafeEnumMember name="GOVT_AGENCY" value="G"/> + <typesafeEnumMember name="NON_PROFIT" value="N"/> + <typesafeEnumMember name="SELF_OWNED" value="O"/> + <typesafeEnumMember name="PRIVATELY_HELD" value="P"/> + <typesafeEnumMember name="PARTNERSHIP" value="S"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='stock-exchange']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="StockExchangeCode"> + <typesafeEnumMember name="AMERICAN_STOCK_EXCHANGE" value="ASE"/> + <typesafeEnumMember name="NEWYORK_STOCK_EXCHANGE" value="NYS"/> + <typesafeEnumMember name="NASDAQ" value="NMS"/> + <typesafeEnumMember name="LONDON_STOCK_EXCHANGE" value="LSE"/> + <typesafeEnumMember name="FRANKFURT_STOCK_EXCHANGE" value="FRA"/> + <typesafeEnumMember name="XETRA_TRADING_PLATFORM" value="GER"/> + <typesafeEnumMember name="EURONEXT_PARIS" value="PAR"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='job-function']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="JobFunctionCode"> + <typesafeEnumMember name="ACCOUNTING_AUDITING" value="acct"/> + <typesafeEnumMember name="ADMINISTRATIVE" value="adm"/> + <typesafeEnumMember name="ADVERTISING" value="advr"/> + <typesafeEnumMember name="ANALYST" value="anls"/> + <typesafeEnumMember name="ART_CREATIVE" value="art"/> + <typesafeEnumMember name="BUSINESS_DEVELOPMENT" value="bd"/> + <typesafeEnumMember name="CONSULTING" value="cnsl"/> + <typesafeEnumMember name="CUSTOMER_SERVICE" value="cust"/> + <typesafeEnumMember name="DISTRIBUTION" value="dist"/> + <typesafeEnumMember name="DESIGN" value="dsgn"/> + <typesafeEnumMember name="EDUCATION" value="edu"/> + <typesafeEnumMember name="ENGINEERING" value="eng"/> + <typesafeEnumMember name="FINANCE" value="fin"/> + <typesafeEnumMember name="GENERAL_BUSINESS" value="genb"/> + <typesafeEnumMember name="HUMAN_RESOURCES" value="hr"/> + <typesafeEnumMember name="INFORMATION_TECHNOLOGY" value="it"/> + <typesafeEnumMember name="LEGAL" value="lgl"/> + <typesafeEnumMember name="MANAGEMENT" value="mgmt"/> + <typesafeEnumMember name="MANUFACTURING" value="mnfc"/> + <typesafeEnumMember name="MARKETING" value="mrkt"/> + <typesafeEnumMember name="OTHER" value="othr"/> + <typesafeEnumMember name="PUBLIC_RELATIONS" value="pr"/> + <typesafeEnumMember name="PURCHASING" value="prch"/> + <typesafeEnumMember name="PRODUCT_MANAGEMENT" value="prdm"/> + <typesafeEnumMember name="PROJECT_MANAGEMENT" value="prjm"/> + <typesafeEnumMember name="PRODUCTION" value="prod"/> + <typesafeEnumMember name="QUALITY_ASSURANCE" value="qa"/> + <typesafeEnumMember name="RESEARCH" value="rsch"/> + <typesafeEnumMember name="SALES" value="sale"/> + <typesafeEnumMember name="SCIENCE" value="sci"/> + <typesafeEnumMember name="STRATEGY_PLANNING" value="stra"/> + <typesafeEnumMember name="SUPPLY_CHAIN" value="supl"/> + <typesafeEnumMember name="TRAINING" value="trng"/> + <typesafeEnumMember name="WRITING_EDITING" value="wrt"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='membership-state']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="MembershipStateCode"> + <typesafeEnumMember name="BLOCKED" value="blocked"/> + <typesafeEnumMember name="NON_MEMBER" value="non-member"/> + <typesafeEnumMember name="AWAITING_CONFIRMATION" value="awaiting-confirmation"/> + <typesafeEnumMember name="AWAITING_PARENT_GROUP_CONFIRMATION" value="awaiting-parent-group-confirmation"/> + <typesafeEnumMember name="MEMBER" value="member"/> + <typesafeEnumMember name="MODERATOR" value="moderator"/> + <typesafeEnumMember name="MANAGER" value="manager"/> + <typesafeEnumMember name="OWNER" value="owner"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='email-digest-frequency']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="EmailDigestFrequencyCode"> + <typesafeEnumMember name="NONE" value="none"/> + <typesafeEnumMember name="DAILY" value="daily"/> + <typesafeEnumMember name="WEEKLY" value="weekly"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='category']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="PostCategoryCode"> + <typesafeEnumMember name="DISCUSSION" value="discussion"/> + <typesafeEnumMember name="JOB" value="job"/> + <typesafeEnumMember name="PROMOTION" value="promotion"/> + <typesafeEnumMember name="LINKEDIN_JOB" value="linkedin-job"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:complexType[@name='GroupCategory']/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="GroupCategoryCode"> + <typesafeEnumMember name="ALUMNI" value="alumni"/> + <typesafeEnumMember name="CORPORATE" value="corporate"/> + <typesafeEnumMember name="CONFERENCE" value="conference"/> + <typesafeEnumMember name="NETWORK" value="network"/> + <typesafeEnumMember name="PHILANTHROPIC" value="philanthropic"/> + <typesafeEnumMember name="PROFESSIONAL" value="professional"/> + <typesafeEnumMember name="OTHER" value="other"/> + </typesafeEnumClass> + </bindings> + <bindings node="//xs:element[@name='post']/xs:complexType/xs:sequence/xs:element[@name='type']/xs:complexType/xs:sequence/xs:element[@name='code']/xs:simpleType"> + <typesafeEnumClass name="PostTypeCode"> + <typesafeEnumMember name="STANDARD" value="standard"/> + <typesafeEnumMember name="NEWS" value="news"/> + </typesafeEnumClass> + </bindings> + + <bindings node="//xs:element[@name='updates']/xs:complexType/xs:sequence/xs:element[@ref='update']"> + <property name="updateList"/> + </bindings> + <bindings node="//xs:element[@name='recipients']/xs:complexType/xs:sequence/xs:element[@ref='recipient']"> + <property name="recipientList"/> + </bindings> + <bindings node="//xs:element[@name='network-stats']/xs:complexType/xs:sequence/xs:element[@ref='property']"> + <property name="propertyList"/> + </bindings> + <bindings node="//xs:element[@name='question-categories']/xs:complexType/xs:sequence/xs:element[@ref='question-category']"> + <property name="questionCategoryList"/> + </bindings> + <bindings node="//xs:element[@name='answers']/xs:complexType/xs:sequence/xs:element[@ref='answer']"> + <property name="answerList"/> + </bindings> + <bindings node="//xs:element[@name='update-comments']/xs:complexType/xs:sequence/xs:element[@ref='update-comment']"> + <property name="updateCommentList"/> + </bindings> + <bindings node="//xs:element[@name='people']/xs:complexType/xs:sequence/xs:element[@ref='person']"> + <property name="personList"/> + </bindings> + <bindings node="//xs:element[@name='positions']/xs:complexType/xs:sequence/xs:element[@ref='position']"> + <property name="positionList"/> + </bindings> + <bindings node="//xs:element[@name='three-current-positions']/xs:complexType/xs:sequence/xs:element[@ref='position']"> + <property name="positionList"/> + </bindings> + <bindings node="//xs:element[@name='three-past-positions']/xs:complexType/xs:sequence/xs:element[@ref='position']"> + <property name="positionList"/> + </bindings> + <bindings node="//xs:element[@name='educations']/xs:complexType/xs:sequence/xs:element[@ref='education']"> + <property name="educationList"/> + </bindings> + <bindings node="//xs:element[@name='member-groups']/xs:complexType/xs:sequence/xs:element[@ref='member-group']"> + <property name="memberGroupList"/> + </bindings> + <bindings node="//xs:element[@name='person-activities']/xs:complexType/xs:sequence/xs:element[@ref='activity']"> + <property name="activityList"/> + </bindings> + <bindings node="//xs:element[@name='recommendations-given']/xs:complexType/xs:sequence/xs:element[@ref='recommendation']"> + <property name="recommendationList"/> + </bindings> + <bindings node="//xs:element[@name='recommendations-received']/xs:complexType/xs:sequence/xs:element[@ref='recommendation']"> + <property name="recommendationList"/> + </bindings> + <bindings node="//xs:element[@name='connections']/xs:complexType/xs:sequence/xs:element[@ref='person']"> + <property name="personList"/> + </bindings> + <bindings node="//xs:element[@name='headers']/xs:complexType/xs:sequence/xs:element[@ref='http-header']"> + <property name="httpHeaderList"/> + </bindings> + <bindings node="//xs:element[@name='im-accounts']/xs:complexType/xs:sequence/xs:element[@ref='im-account']"> + <property name="imAccountList"/> + </bindings> + <bindings node="//xs:element[@name='twitter-accounts']/xs:complexType/xs:sequence/xs:element[@ref='twitter-account']"> + <property name="twitterAccountList"/> + </bindings> + <bindings node="//xs:element[@name='phone-numbers']/xs:complexType/xs:sequence/xs:element[@ref='phone-number']"> + <property name="phoneNumberList"/> + </bindings> + <bindings node="//xs:element[@name='member-url-resources']/xs:complexType/xs:sequence/xs:element[@ref='member-url']"> + <property name="memberUrlList"/> + </bindings> + <bindings node="//xs:element[@name='facets']/xs:complexType/xs:sequence/xs:element[@ref='facet']"> + <property name="facetList"/> + </bindings> + <bindings node="//xs:element[@name='buckets']/xs:complexType/xs:sequence/xs:element[@ref='bucket']"> + <property name="bucketList"/> + </bindings> + <bindings node="//xs:element[@name='likes']/xs:complexType/xs:sequence/xs:element[@ref='like']"> + <property name="likeList"/> + </bindings> + <bindings node="//xs:element[@name='certifications']/xs:complexType/xs:sequence/xs:element[@ref='certification']"> + <property name="certificationList"/> + </bindings> + <bindings node="//xs:element[@name='patents']/xs:complexType/xs:sequence/xs:element[@ref='patent']"> + <property name="patentList"/> + </bindings> + <bindings node="//xs:element[@name='publications']/xs:complexType/xs:sequence/xs:element[@ref='publication']"> + <property name="publicationList"/> + </bindings> + <bindings node="//xs:element[@name='skills']/xs:complexType/xs:sequence/xs:element[@ref='skill']"> + <property name="skillList"/> + </bindings> + <bindings node="//xs:element[@name='languages']/xs:complexType/xs:sequence/xs:element[@ref='language']"> + <property name="languageList"/> + </bindings> + <bindings node="//xs:element[@name='inventors']/xs:complexType/xs:sequence/xs:element[@ref='inventor']"> + <property name="inventorList"/> + </bindings> + <bindings node="//xs:element[@name='authors']/xs:complexType/xs:sequence/xs:element[@name='author']"> + <property name="authorList"/> + </bindings> + <bindings node="//xs:element[@name='related-connections']/xs:complexType/xs:sequence/xs:element[@ref='person']"> + <property name="personList"/> + </bindings> + <bindings node="//xs:element[@name='companies']/xs:complexType/xs:sequence/xs:element[@ref='company']"> + <property name="companyList"/> + </bindings> + <bindings node="//xs:element[@name='products']/xs:complexType/xs:sequence/xs:element[@ref='product']"> + <property name="productList"/> + </bindings> + <bindings node="//xs:element[@name='job-bookmarks']/xs:complexType/xs:sequence/xs:element[@ref='job-bookmark']"> + <property name="jobBookmarkList"/> + </bindings> + <bindings node="//xs:element[@name='jobs']/xs:complexType/xs:sequence/xs:element[@ref='job']"> + <property name="jobList"/> + </bindings> + <bindings node="//xs:element[@name='email-domains']/xs:complexType/xs:sequence/xs:element[@ref='email-domain']"> + <property name="emailDomainList"/> + </bindings> + <bindings node="//xs:element[@name='locations']/xs:complexType/xs:sequence/xs:element[@ref='location']"> + <property name="locationList"/> + </bindings> + <bindings node="//xs:element[@name='recommendations']/xs:complexType/xs:sequence/xs:element[@ref='recommendation']"> + <property name="recommendationList"/> + </bindings> + <bindings node="//xs:element[@name='job-functions']/xs:complexType/xs:sequence/xs:element[@ref='job-function']"> + <property name="jobFunctionList"/> + </bindings> + <bindings node="//xs:element[@name='industries']/xs:complexType/xs:sequence/xs:element[@ref='industry']"> + <property name="industryList"/> + </bindings> + <bindings node="//xs:element[@name='specialties']/xs:complexType/xs:sequence/xs:element[@ref='specialty']"> + <property name="specialtyList"/> + </bindings> + <bindings node="//xs:element[@name='features']/xs:complexType/xs:sequence/xs:element[@ref='feature']"> + <property name="featureList"/> + </bindings> + <bindings node="//xs:element[@name='sales-persons']/xs:complexType/xs:sequence/xs:element[@ref='person']"> + <property name="personList"/> + </bindings> + <bindings node="//xs:element[@name='posts']/xs:complexType/xs:sequence/xs:element[@ref='post']"> + <property name="postList"/> + </bindings> + <bindings node="//xs:element[@name='comments']/xs:complexType/xs:sequence/xs:element[@ref='comment']"> + <property name="commentList"/> + </bindings> + <bindings node="//xs:element[@name='available-actions']/xs:complexType/xs:sequence/xs:element[@ref='action']"> + <property name="actionList"/> + </bindings> + <bindings node="//xs:element[@name='group-memberships']/xs:complexType/xs:sequence/xs:element[@ref='group-membership']"> + <property name="groupMembershipList"/> + </bindings> + <bindings node="//xs:element[@name='groups']/xs:complexType/xs:sequence/xs:element[@ref='group']"> + <property name="groupList"/> + </bindings> + <bindings node="//xs:element[@name='historical-follow-statistics']/xs:complexType/xs:sequence/xs:element[@name='statistic']"> + <property name="statisticList"/> + </bindings> + <bindings node="//xs:element[@name='historical-status-update-statistics']/xs:complexType/xs:sequence/xs:element[@name='statistic']"> + <property name="statisticList"/> + </bindings> + <bindings node="//xs:element[@name='historical-follow-statistics']/xs:complexType/xs:sequence/xs:element[@name='statistic']/xs:complexType"> + <class name="HistoricalFollowStatistic"/> + </bindings> + <bindings node="//xs:element[@name='historical-status-update-statistics']/xs:complexType/xs:sequence/xs:element[@name='statistic']/xs:complexType"> + <class name="HistoricalStatusUpdateStatistic"/> + </bindings> + <bindings node="//xs:element[@name='share-target-reach']/xs:complexType/xs:sequence/xs:element[@name='share-targets']/xs:complexType/xs:sequence/xs:element[@name='share-target']"> + <property name="shareTargetList"/> + </bindings> +</bindings> \ No newline at end of file