diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8aa7b14
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+.classpath
+.project
+.settings/
+scratch/
+test-output/
+target/
+src/test/java/com/evolveum/polygon/connector/jira/test/JiraConnectorSimpleTests
+sample/
+.idea
+*.iml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..958c9dc
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Grouper Rest Connector - documentation available at ... (TODO)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..7cf04c6
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ 4.0.0
+
+
+ connector-parent
+ com.evolveum.polygon
+ 1.4.2.14
+
+
+
+ connector-grouper-rest
+ 0.1
+ jar
+
+ Grouper REST Connector
+
+
+ com.evolveum.polygon.connector.grouper.rest
+ GrouperRestConnector
+
+
+
+
+ evolveum-nexus-releases
+ Internal Releases
+ http://nexus.evolveum.com/nexus/content/repositories/releases/
+
+
+ evolveum-nexus-snapshots
+ Internal Releases
+ http://nexus.evolveum.com/nexus/content/repositories/snapshots/
+
+
+ apache-snapshots
+ Apache Snapshots
+ http://repository.apache.org/snapshots/
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.19.1
+
+
+
+
+
+
+
+ connector-rest
+ com.evolveum.polygon
+ 1.4.2.14-SNAPSHOT
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.1
+
+
+ org.json
+ json
+ 20160810
+
+
+ org.testng
+ testng
+ 6.8
+ test
+
+
+
+ batik
+ batik-swing
+ 1.6
+
+
+ batik
+ batik-rasterizer
+ 1.6
+
+
+ xml-apis
+ xml-apis
+ 1.3.04
+
+
+ xml-apis
+ xml-apis-ext
+ 1.3.04
+
+
+
+
+
diff --git a/src/main/assembly/connector.xml b/src/main/assembly/connector.xml
new file mode 100644
index 0000000..efca6d1
--- /dev/null
+++ b/src/main/assembly/connector.xml
@@ -0,0 +1,47 @@
+
+
+
+
+ connector
+
+
+ jar
+
+
+ false
+
+
+
+ target/classes
+
+
+
+
+
+
+ lib
+ false
+ runtime
+
+ net.tirasa.connid:connector-framework
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java
new file mode 100644
index 0000000..c33afc4
--- /dev/null
+++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/AccountProcessor.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright 2017 Evolveum
+ *
+ * Licensed 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 com.evolveum.polygon.connector.grouper.rest;
+
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.evolveum.polygon.connector.grouper.rest.Processor.*;
+
+/**
+ * @author surmanek
+ * @author mederly
+ *
+ */
+public class AccountProcessor {
+
+ private final Processor processor;
+
+ public static final String ATTR_GROUP = "group";
+
+ public AccountProcessor(Processor processor) {
+ this.processor = processor;
+ }
+
+ ObjectClassInfoBuilder buildSchema() {
+ ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder();
+ AttributeInfoBuilder groups = new AttributeInfoBuilder(ATTR_GROUP, String.class);
+ groups.setMultiValued(true);
+ builder.addAttributeInfo(groups.build());
+ return builder;
+ }
+
+ void read(Filter filter, ResultsHandler handler, OperationOptions options) {
+ if (filter == null) {
+ getAllUsers(handler);
+ } else if (filter instanceof EqualsFilter &&
+ (((EqualsFilter) filter).getAttribute() instanceof Name || ((EqualsFilter) filter).getAttribute() instanceof Uid)) {
+ Attribute name = ((EqualsFilter) filter).getAttribute();
+ if (name != null) {
+ if (name.getValue() == null || name.getValue().isEmpty()) {
+ throw new IllegalArgumentException("No ID to look for");
+ } else if (name.getValue().size() > 1) {
+ throw new IllegalArgumentException("More than one ID to look for: " + name.getValue());
+ } else {
+ getUser((String) name.getValue().get(0), handler);
+ }
+ } else {
+ processor.throwNullAttrException(filter);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported filter: " + filter);
+ }
+ }
+
+ private void getAllUsers(ResultsHandler handler) {
+ URIBuilder uriBuilder = processor.getURIBuilder()
+ .setPath(URI_BASE_PATH + PATH_GROUPS);
+ try {
+ HttpPost request = new HttpPost(uriBuilder.build());
+ JSONObject body = new JSONObject()
+ .put(WS_REST_GET_MEMBERS_REQUEST, new JSONObject()
+ .put(WS_GROUP_LOOKUPS, new JSONObject[] {
+ new JSONObject().put(GROUP_NAME, getConfiguration().getSuperGroup()) }));
+ System.out.println("Request = " + body.toString());
+ JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON);
+ System.out.println("Got response: " + response);
+ processor.checkSuccess(response, WS_GET_MEMBERS_RESULTS);
+ JSONArray subjects = processor.getArray(response, WS_GET_MEMBERS_RESULTS, RESULTS, WS_SUBJECTS);
+ List ids = selectSubjectIds(subjects);
+ System.out.println("Subject IDs found: " + ids);
+ for (String id : ids) {
+ ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
+ builder.setUid(id);
+ builder.setName(id);
+ AttributeBuilder groupBuilder = new AttributeBuilder().setName(ATTR_GROUP);
+ groupBuilder.setAttributeValueCompleteness(AttributeValueCompleteness.INCOMPLETE);
+ builder.addAttribute(groupBuilder.build());
+ if (!handler.handle(builder.build())) {
+ return;
+ }
+ }
+ } catch (RuntimeException | URISyntaxException e) {
+ throw processor.processException(e, uriBuilder, "Get all users");
+ }
+ }
+
+ private List selectSubjectIds(JSONArray subjects) {
+ List rv = new ArrayList<>(subjects.length());
+ for (Object subject : subjects) {
+ if (subject instanceof JSONObject) {
+ JSONObject subjObject = (JSONObject) subject;
+ if (processor.isSuccess(subjObject)) {
+ String sourceId = processor.getStringOrNull(subjObject, "sourceId");
+ if (getConfiguration().getSubjectSource().equals(sourceId)) {
+ rv.add(processor.getString(subjObject, "id"));
+ }
+ } else {
+ LOG.warn("Skipping not-success subject from response: {}", subject);
+ }
+ } else {
+ throw new IllegalStateException("Expected subject as JSONObject, got " + subject);
+ }
+ }
+ return rv;
+ }
+
+ private void getUser(String id, ResultsHandler handler) {
+ URIBuilder uriBuilder = processor.getURIBuilder()
+ .setPath(URI_BASE_PATH + PATH_SUBJECTS);
+ try {
+ HttpPost request = new HttpPost(uriBuilder.build());
+ JSONObject body = new JSONObject()
+ .put("WsRestGetGroupsRequest", new JSONObject()
+ .put("subjectLookups", new JSONObject[] {
+ new JSONObject().put("subjectId", id) }));
+ System.out.println("Request = " + body.toString());
+ JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON);
+ System.out.println("Got response: " + response);
+ processor.checkSuccess(response, WS_GET_GROUPS_RESULTS);
+ JSONArray groups = processor.getArray(response, WS_GET_GROUPS_RESULTS, RESULTS, WS_GROUPS);
+
+ ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
+ builder.setUid(id);
+ builder.setName(id);
+ builder.addAttribute(ATTR_GROUP, selectGroupNames(groups));
+ handler.handle(builder.build());
+ } catch (RuntimeException | URISyntaxException e) {
+ throw processor.processException(e, uriBuilder, "Get all users");
+ }
+ }
+
+ private List selectGroupNames(JSONArray groups) {
+ List rv = new ArrayList<>();
+ String expectedPrefix = getConfiguration().getRootStem() + ":";
+ for (Object group : groups) {
+ if (group instanceof JSONObject) {
+ JSONObject gObject = (JSONObject) group;
+ String name = processor.getStringOrNull(gObject, "name");
+ String extension = processor.getStringOrNull(gObject, "extension");
+ if (name != null && name.equals(expectedPrefix + extension)) {
+ rv.add(name);
+ }
+ } else {
+ throw new IllegalStateException("Expected group as JSONObject, got " + group);
+ }
+ }
+ return rv;
+ }
+
+ private GrouperConfiguration getConfiguration() {
+ return processor.configuration;
+ }
+}
diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java
new file mode 100644
index 0000000..9e4f6c1
--- /dev/null
+++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GroupProcessor.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright 2017 Evolveum
+ *
+ * Licensed 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 com.evolveum.polygon.connector.grouper.rest;
+
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.net.URISyntaxException;
+
+import static com.evolveum.polygon.connector.grouper.rest.Processor.*;
+
+/**
+ * @author surmanek
+ * @author mederly
+ *
+ */
+public class GroupProcessor {
+
+ private final Processor processor;
+
+ private static final String ATTR_EXTENSION = "extension";
+
+ public GroupProcessor(Processor processor) {
+ this.processor = processor;
+ }
+
+ ObjectClassInfoBuilder buildSchema() {
+ ObjectClassInfoBuilder builder = new ObjectClassInfoBuilder();
+
+ builder.setType(ObjectClass.GROUP_NAME);
+ AttributeInfoBuilder extension = new AttributeInfoBuilder(ATTR_EXTENSION, String.class);
+ builder.addAttributeInfo(extension.build());
+
+ return builder;
+ }
+
+ void read(Filter filter, ResultsHandler handler, OperationOptions options) {
+ if (filter == null) {
+ getAllGroups(handler);
+ } else if (filter instanceof EqualsFilter && ((EqualsFilter) filter).getAttribute() instanceof Name) {
+ Attribute name = ((EqualsFilter) filter).getAttribute();
+ if (name != null) {
+ if (name.getValue() == null || name.getValue().isEmpty()) {
+ throw new IllegalArgumentException("No group name to look for");
+ } else if (name.getValue().size() > 1) {
+ throw new IllegalArgumentException("More than one group name to look for: " + name.getValue());
+ } else {
+ getGroupByName((String) name.getValue().get(0), handler);
+ }
+ } else {
+ processor.throwNullAttrException(filter);
+ }
+ } else if (filter instanceof EqualsFilter && ((EqualsFilter) filter).getAttribute() instanceof Uid) {
+ Attribute name = ((EqualsFilter) filter).getAttribute();
+ if (name != null) {
+ if (name.getValue() == null || name.getValue().isEmpty()) {
+ throw new IllegalArgumentException("No group UUID to look for");
+ } else if (name.getValue().size() > 1) {
+ throw new IllegalArgumentException("More than one group UUID to look for: " + name.getValue());
+ } else {
+ getGroupByUuid((String) name.getValue().get(0), handler);
+ }
+ } else {
+ processor.throwNullAttrException(filter);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported filter: " + filter);
+ }
+ }
+
+ private void getAllGroups(ResultsHandler handler) {
+ URIBuilder uriBuilder = processor.getURIBuilder()
+ .setPath(URI_BASE_PATH + PATH_GROUPS);
+ try {
+ HttpPost request = new HttpPost(uriBuilder.build());
+ JSONObject body = new JSONObject()
+ .put("WsRestFindGroupsRequest", new JSONObject()
+ .put("wsQueryFilter", new JSONObject()
+ .put("queryFilterType", "FIND_BY_STEM_NAME")
+ .put("stemName", getConfiguration().getRootStem())));
+ executeFindGroupsResponse(request, body, handler);
+ } catch (RuntimeException | URISyntaxException e) {
+ throw processor.processException(e, uriBuilder, "Get all groups");
+ }
+ }
+
+ private void executeFindGroupsResponse(HttpPost request, JSONObject body, ResultsHandler handler) {
+ System.out.println("Request = " + body.toString());
+ JSONObject response = processor.callRequest(request, body, true, CONTENT_TYPE_JSON);
+ System.out.println("Got response: " + response);
+ processor.checkSuccess(response, "WsFindGroupsResults");
+ JSONArray groups = processor.getArray(response, "WsFindGroupsResults", "groupResults");
+ for (Object group : groups) {
+ if (!handlerGroupJsonObject(group, handler)) {
+ return;
+ }
+ }
+ }
+
+ private void getGroupByName(String groupName, ResultsHandler handler) {
+ URIBuilder uriBuilder = processor.getURIBuilder()
+ .setPath(URI_BASE_PATH + PATH_GROUPS);
+ try {
+ HttpPost request = new HttpPost(uriBuilder.build());
+ JSONObject body = new JSONObject()
+ .put("WsRestFindGroupsRequest", new JSONObject()
+ .put("wsGroupLookups", new JSONObject[] { new JSONObject()
+ .put("groupName", groupName) }));
+ executeFindGroupsResponse(request, body, handler);
+ } catch (RuntimeException | URISyntaxException e) {
+ throw processor.processException(e, uriBuilder, "Get all groups");
+ }
+ }
+
+ private void getGroupByUuid(String groupUuid, ResultsHandler handler) {
+ URIBuilder uriBuilder = processor.getURIBuilder()
+ .setPath(URI_BASE_PATH + PATH_GROUPS);
+ try {
+ HttpPost request = new HttpPost(uriBuilder.build());
+ JSONObject body = new JSONObject()
+ .put("WsRestFindGroupsRequest", new JSONObject()
+ .put("wsGroupLookups", new JSONObject[] { new JSONObject()
+ .put("uuid", groupUuid) }));
+ executeFindGroupsResponse(request, body, handler);
+ } catch (RuntimeException | URISyntaxException e) {
+ throw processor.processException(e, uriBuilder, "Get all groups");
+ }
+ }
+
+ private boolean handlerGroupJsonObject(Object group, ResultsHandler handler) {
+ if (group instanceof JSONObject) {
+ JSONObject gObject = (JSONObject) group;
+ String name = processor.getStringOrNull(gObject, "name");
+ String extension = processor.getStringOrNull(gObject, "extension");
+ String uuid = processor.getStringOrNull(gObject, "uuid");
+ ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
+ builder.setObjectClass(ObjectClass.GROUP);
+ builder.setUid(uuid);
+ builder.setName(name);
+ builder.addAttribute(ATTR_EXTENSION, extension);
+ return handler.handle(builder.build());
+ } else {
+ throw new IllegalStateException("Expected group as JSONObject, got " + group);
+ }
+ }
+
+ private GrouperConfiguration getConfiguration() {
+ return processor.configuration;
+ }
+
+}
diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java
new file mode 100644
index 0000000..ffa0e5d
--- /dev/null
+++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConfiguration.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2016 Evolveum
+ *
+ * Licensed 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 com.evolveum.polygon.connector.grouper.rest;
+
+import org.identityconnectors.common.StringUtil;
+import org.identityconnectors.common.logging.Log;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.exceptions.ConfigurationException;
+import org.identityconnectors.framework.spi.AbstractConfiguration;
+import org.identityconnectors.framework.spi.ConfigurationProperty;
+import org.identityconnectors.framework.spi.StatefulConfiguration;
+
+/**
+ * @author surmanek
+ * @author mederly
+ *
+ */
+public class GrouperConfiguration extends AbstractConfiguration implements StatefulConfiguration {
+
+ private static final Log LOG = Log.getLog(GrouperConfiguration.class);
+
+ private String name;
+ private GuardedString password;
+ private String baseUrl;
+ private String superGroup;
+ private String rootStem;
+ private Boolean ignoreSslValidation;
+ private String subjectSource;
+
+ // getter and setter methods for "baseUrl" attribute:
+ @ConfigurationProperty(order = 1, displayMessageKey = "baseUrl.display", helpMessageKey = "baseUrl.help", required = true)
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ public void setBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ // getter and setter methods for "name" attribute:
+ @ConfigurationProperty(order = 2, displayMessageKey = "username.display", helpMessageKey = "username.help", required = true)
+ public String getUsername() {
+ return name;
+ }
+
+ public void setUsername(String name) {
+ this.name = name;
+ }
+
+ private String stringPassword = "";
+ @ConfigurationProperty(order = 3, displayMessageKey = "password.display", helpMessageKey = "password.help", required = true, confidential = false)
+ public GuardedString getPassword() {
+ return password;
+ }
+ public void setPassword(GuardedString password) {
+ this.password = password;
+ }
+
+ public String getStringPassword() {
+ password.access(new GuardedString.Accessor() {
+ @Override
+ public void access(char[] clearChars) {
+ stringPassword = new String(clearChars);
+ }
+ });
+ return stringPassword;
+ }
+
+ @ConfigurationProperty(order = 4, displayMessageKey = "superGroup.display", helpMessageKey = "superGroup.help", required = true)
+ public String getSuperGroup() {
+ return superGroup;
+ }
+
+ public void setSuperGroup(String superGroup) {
+ this.superGroup = superGroup;
+ }
+
+ @ConfigurationProperty(order = 5, displayMessageKey = "rootStem.display", helpMessageKey = "superGroup.help", required = true)
+ public String getRootStem() {
+ return rootStem;
+ }
+
+ public void setRootStem(String rootStem) {
+ this.rootStem = rootStem;
+ }
+
+ @ConfigurationProperty(order = 6, displayMessageKey = "ignoreSslValidation.display", helpMessageKey = "ignoreSslValidation.help", required = false)
+ public Boolean getIgnoreSslValidation() {
+ return ignoreSslValidation;
+ }
+
+ public void setIgnoreSslValidation(Boolean ignoreSslValidation) {
+ this.ignoreSslValidation = ignoreSslValidation;
+ }
+
+ @ConfigurationProperty(order = 7, displayMessageKey = "subjectSource.display", helpMessageKey = "subjectSource.help", required = false)
+ public String getSubjectSource() {
+ return subjectSource;
+ }
+
+ public void setSubjectSource(String subjectSource) {
+ this.subjectSource = subjectSource;
+ }
+
+ @Override
+ public void validate() {
+ String exceptionMsg;
+ if (baseUrl == null || StringUtil.isBlank(baseUrl)) {
+ exceptionMsg = "Base url is not provided.";
+ } else if (name == null || StringUtil.isBlank(name)) {
+ exceptionMsg = "Name is not provided.";
+ } else if (password == null) {
+ exceptionMsg = "Password is not provided.";
+ } else if (superGroup == null) {
+ exceptionMsg = "Super group is not provided.";
+ } else if (rootStem == null) {
+ exceptionMsg = "Root stem is not provided.";
+ } else if (subjectSource == null) {
+ exceptionMsg = "Subject source is not provided.";
+ } else {
+ return;
+ }
+ LOG.error(exceptionMsg);
+ throw new ConfigurationException(exceptionMsg);
+ }
+
+ @Override
+ public void release() {
+ LOG.info("The release of configuration resources is being performed");
+ this.password = null;
+ this.name = null;
+ this.baseUrl = null;
+ this.superGroup = null;
+ this.rootStem = null;
+ }
+
+ @Override
+ public String toString() {
+ return "GrouperConfiguration{" +
+ "username='" + name + '\'' +
+ ", baseUrl='" + baseUrl + '\'' +
+ ", superGroup='" + superGroup + '\'' +
+ ", rootStem='" + rootStem + '\'' +
+ ", ignoreSslValidation='" + ignoreSslValidation + '\'' +
+ '}';
+ }
+
+}
diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java
new file mode 100644
index 0000000..8f63944
--- /dev/null
+++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/GrouperConnector.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2017 Evolveum
+ *
+ * Licensed 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 com.evolveum.polygon.connector.grouper.rest;
+
+import org.identityconnectors.common.CollectionUtil;
+import org.identityconnectors.common.logging.Log;
+import org.identityconnectors.framework.common.exceptions.ConfigurationException;
+import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.*;
+import org.identityconnectors.framework.spi.Configuration;
+import org.identityconnectors.framework.spi.Connector;
+import org.identityconnectors.framework.spi.ConnectorClass;
+import org.identityconnectors.framework.spi.operations.*;
+
+import java.util.List;
+
+/**
+ * @author surmanek
+ * @author mederly
+ *
+ */
+@ConnectorClass(displayNameKey = "GrouperConnector.rest.display", configurationClass = GrouperConfiguration.class)
+
+public class GrouperConnector implements TestOp, SchemaOp, Connector, SearchOp {
+
+ private static final Log LOG = Log.getLog(GrouperConnector.class);
+ private GrouperConfiguration configuration;
+ private Processor processor;
+ private AccountProcessor accountProcessor;
+ private GroupProcessor groupProcessor;
+
+ private static final String PROJECT_NAME = "PROJECT";
+ private static final String ATTR_GROUPS = "groups";
+
+ @Override
+ public GrouperConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public void init(Configuration configuration) {
+ if (configuration == null) {
+ LOG.error("Initialization of the configuration failed: Configuration is not provided.");
+ throw new ConfigurationException(
+ "Initialization of the configuration failed: Configuration is not provided.");
+ }
+ this.configuration = (GrouperConfiguration) configuration;
+ this.configuration.validate();
+ this.processor = new Processor(this.configuration);
+ this.accountProcessor = new AccountProcessor(processor);
+ this.groupProcessor = new GroupProcessor(processor);
+ }
+
+ @Override
+ public void dispose() {
+ configuration = null;
+ processor = null;
+ accountProcessor = null;
+ groupProcessor = null;
+ }
+
+ @Override
+ public void test() {
+ LOG.info("Testing connection...");
+ processor.test();
+ LOG.ok("Testing finished successfully.");
+ }
+
+ @Override
+ public Schema schema() {
+ SchemaBuilder schemaBuilder = new SchemaBuilder(GrouperConnector.class);
+
+ // build user schema:
+ AccountProcessor user = new AccountProcessor(processor);
+ ObjectClassInfoBuilder userBuilder = user.buildSchema();
+ schemaBuilder.defineObjectClass(userBuilder.build());
+
+ // build group schema:
+ GroupProcessor group = new GroupProcessor(processor);
+ ObjectClassInfoBuilder groupBuilder = group.buildSchema();
+ schemaBuilder.defineObjectClass(groupBuilder.build());
+
+ return schemaBuilder.build();
+ }
+
+ @Override
+ public FilterTranslator createFilterTranslator(ObjectClass arg0, OperationOptions arg1) {
+ return new FilterTranslator() {
+ @Override
+ public List translate(Filter filter) {
+ return CollectionUtil.newList(filter);
+ }
+ };
+ }
+
+ @Override
+ public void executeQuery(ObjectClass objClass, Filter filter, ResultsHandler handler, OperationOptions options) {
+ LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Execute Query-Parameters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ if (objClass == null) {
+ LOG.error("Get operation failed: Attribute Object Class is not provided.");
+ throw new InvalidAttributeValueException("Attribute Object Class is not provided.");
+ } else
+ LOG.info("ObjectClasss: {0}", objClass.toString());
+
+ if (handler == null) {
+ LOG.error("Get operation failed: Attribute Result Handler is not provided.");
+ throw new InvalidAttributeValueException("Attribute Result Handler is not provided.");
+ } else
+ LOG.info("Execute Query-Handler: {0}", handler.toString());
+
+ if (options == null) {
+ LOG.error("Get operation failed: Attribute Options is not provided.");
+ throw new InvalidAttributeValueException("Attribute Options is not provided.");
+ } else
+ LOG.info("Options: {0}", options.toString());
+
+ LOG.info("Filter: {0}", filter);
+ LOG.info("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+
+ if (objClass.is(ObjectClass.ACCOUNT_NAME)) {
+ accountProcessor.read(filter, handler, options);
+ } else if (objClass.is(ObjectClass.GROUP_NAME)) {
+ groupProcessor.read(filter, handler, options);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java b/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java
new file mode 100644
index 0000000..f41c7b9
--- /dev/null
+++ b/src/main/java/com/evolveum/polygon/connector/grouper/rest/Processor.java
@@ -0,0 +1,1205 @@
+/*******************************************************************************
+ * Copyright 2017 Evolveum
+ *
+ * Licensed 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 com.evolveum.polygon.connector.grouper.rest;
+
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.JPEGTranscoder;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.util.EntityUtils;
+import org.identityconnectors.common.logging.Log;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.exceptions.*;
+import org.identityconnectors.framework.common.objects.*;
+import org.identityconnectors.framework.common.objects.filter.*;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URISyntaxException;
+import java.net.URLConnection;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author surmanek
+ *
+ */
+public class Processor {
+
+ static final Log LOG = Log.getLog(GrouperConnector.class);
+ static final String CONTENT_TYPE_JSON = "application/json; charset=utf-8";
+ public static final String WS_FIND_STEMS_RESULTS = "WsFindStemsResults";
+ public static final String RESULT_METADATA = "resultMetadata";
+ public static final String SUCCESS = "success";
+ public static final String STEM_RESULTS = "stemResults";
+ public static final String PATH_STEMS = "/stems";
+ public static final String WS_GET_MEMBERS_RESULTS = "WsGetMembersResults";
+ public static final String RESULTS = "results";
+ public static final String WS_SUBJECTS = "wsSubjects";
+ public static final String WS_REST_GET_MEMBERS_REQUEST = "WsRestGetMembersRequest";
+ public static final String WS_GROUP_LOOKUPS = "wsGroupLookups";
+ public static final String GROUP_NAME = "groupName";
+ public static final String WS_GET_GROUPS_RESULTS = "WsGetGroupsResults";
+ public static final String WS_GROUPS = "wsGroups";
+ GrouperConfiguration configuration;
+
+ public static final String URI_BASE_PATH = "/grouper-ws/servicesRest/json/v2_4_000";
+ public static final String PATH_GROUPS = "/groups";
+ public static final String PATH_SUBJECTS = "/subjects";
+
+
+ static final String URI_USER_PATH = "/user";
+ static final String URI_SEARCH_PATH = "/search";
+ static final String PARAM_USERNAME = "username";
+ static final String PARAM_START_AT = "startAt";
+ static final String PARAM_MAX_RESULTS = "maxResults";
+ static final String ATTR_GROUPS = "groups";
+ static final String ATTR_AVATAR_URLS = "avatarUrls";
+
+ static final String USER_NAME = "USER";
+ static final String PROJECT_NAME = "PROJECT";
+
+ static final String URI_PASSWORD_PATH = "/password";
+ static final String URI_TEMP_AVATAR_PATH = "/avatar/temporary";
+ static final String URI_AVATAR_PATH = "/avatar";
+ static final String URI_GROUP_PATH = "/group";
+ static final String URI_GROUPS_PICKER_PATH = "/groups/picker";
+ static final String URI_PROJECT_PATH = "/project";
+
+ static final String PARAM_FILENAME = "filename";
+ static final String PARAM_KEY = "key";
+ static final String CONTENT_TYPE_JPEG_IMAGE = "image/jpeg";
+ static final String UID = "key";
+
+ // project:
+ static final String ATTR_DEVELOPERS_GROUPS = "Developers.groups";
+ static final String ATTR_DEVELOPERS_USERS = "Developers.users";
+ static final String ATTR_ADMINISTRATORS_GROUPS = "Administrators.groups";
+ static final String ATTR_ADMINISTRATORS_USERS = "Administrators.users";
+ static final String ATTR_ACTOR_USER = "user";
+ static final String ATTR_ACTOR_GROUP = "group";
+ static final String ATTR_DEVELOPERS = "Developers";
+ static final String ATTR_ADMINISTRATORS = "Administrators";
+
+ // user+project:
+ static final String ATTR_KEY = "key";
+ static final String ATTR_AVATAR_BYTE_ARRRAY = "binaryAvatar";
+ // user+group:
+ static final String ATTR_SELF = "self";
+ static final String ATTR_NAME = "name";
+ static final String ATTR_EXPAND = "expand";
+
+ static final String MIDPOINT_NAME = "__NAME__";
+ static final boolean IS_MULTI_VALUE = true;
+ static final boolean IS_SINGLE_VALUE = false;
+ static final String EXTENDED_ATTR_NAME = "name";
+ static final String EXTENDED_ATTR_ITEMS = "items";
+
+ public Processor(GrouperConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+
+ //put objects of array1 to the end of array2
+ private JSONArray concatJSONArrays(JSONArray array1, JSONArray array2){
+ for (Object obj : array1){
+ array2.put(obj);
+ }
+ return array2;
+ }
+
+
+ JSONObject callRequest(HttpEntityEnclosingRequestBase request, JSONObject jo, Boolean parseResult,
+ String contentType) {
+ // don't log request here - password field !!!
+ if (contentType != null)
+ request.addHeader("Content-Type", contentType);
+ request.addHeader("Authorization", "Basic " + authEncoding());
+ HttpEntity entity;
+ try {
+ entity = new ByteArrayEntity(jo.toString().getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e1) {
+ String exceptionMsg = "Creating request entity failed: problem occured during entity encoding.";
+ LOG.error(exceptionMsg);
+ throw new ConnectorIOException(exceptionMsg);
+ }
+ request.setEntity(entity);
+ try (CloseableHttpResponse response = execute(request)){
+
+
+
+ //LOG.ok("Request: {0}", request.toString());
+ //response = execute(request);
+ LOG.ok("Response: {0}", response);
+ processResponseErrors(response);
+
+ if (!parseResult) {
+ return null;
+ }
+
+ String result = EntityUtils.toString(response.getEntity());
+
+ LOG.ok("Response body: {0}", result);
+ return new JSONObject(result);
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Request failed: problem occured during execute request with uri: ")
+ .append(request.getURI()).append(": \n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString(), e);
+ }
+ }
+
+ JSONObject callRequest(HttpRequestBase request, Boolean parseResult, String contentType) {
+ // don't log request here - password field !!!
+ //CloseableHttpResponse response = null;
+ LOG.ok("request URI: {0}", request.getURI());
+ request.addHeader("Content-Type", contentType);
+ request.addHeader("Authorization", "Basic " + authEncoding());
+ try (CloseableHttpResponse response = execute(request)){
+
+ LOG.ok("Response: {0}", response);
+ processResponseErrors(response);
+
+ if (!parseResult) {
+ //closeResponse(response);
+ return null;
+ }
+ // DO NOT USE getEntity() TWICE!!!
+ String result = EntityUtils.toString(response.getEntity());
+ //closeResponse(response);
+ LOG.ok("Response body: {0}", result);
+ return new JSONObject(result);
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Request failed: problem occured during execute request with uri: ")
+ .append(request.getURI()).append(": \n\t").append(e.getLocalizedMessage());
+ //closeResponse(response);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString(), e);
+ }
+ }
+
+ JSONArray callRequest(HttpRequestBase request) {
+ //CloseableHttpResponse response = null;
+ LOG.ok("request URI: {0}", request.getURI());
+ request.addHeader("Content-Type", CONTENT_TYPE_JSON);
+ request.addHeader("Authorization", "Basic " + authEncoding());
+ try (CloseableHttpResponse response = execute(request)) {
+
+ //response = execute(request);
+ LOG.ok("Response: {0}", response);
+ processResponseErrors(response);
+ // DO NOT USE getEntity() TWICE!!!
+ String result = EntityUtils.toString(response.getEntity());
+ //closeResponse(response);
+ LOG.ok("Response body: {0}", result);
+ return new JSONArray(result);
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Request failed: problem occured during execute request with URI: ")
+ .append(request.getURI()).append(": \n\t").append(e.getLocalizedMessage());
+ //closeResponse(response);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString(), e);
+ }
+ }
+
+ String authEncoding() {
+ String username = configuration.getUsername();
+ String password = configuration.getStringPassword();
+ if (username == null || username.equals("")) {
+ LOG.error("Authentication failed: Username is not provided.");
+ throw new InvalidCredentialException("Authentication failed: Username is not provided.");
+ }
+ if (password == null || password.equals("")) {
+ LOG.error("Authentication failed: Password is not provided.");
+ throw new InvalidPasswordException("Authentication failed: Password is not provided.");
+ }
+ StringBuilder nameAndPasswd = new StringBuilder();
+ nameAndPasswd.append(username).append(":").append(password);
+ // String nameAndPasswd = "administrator:training"
+ String encoding = Base64.encodeBase64String(nameAndPasswd.toString().getBytes());
+ return encoding;
+ }
+
+ CloseableHttpResponse execute(HttpUriRequest request) {
+ try {
+ HttpClientBuilder clientBuilder = HttpClientBuilder.create();
+ if (Boolean.TRUE.equals(configuration.getIgnoreSslValidation())) {
+ SSLContextBuilder sslCtxBuilder = new SSLContextBuilder();
+ sslCtxBuilder.loadTrustMaterial(null, new TrustStrategy() {
+ public boolean isTrusted(X509Certificate[] chain, String authType) {
+ return true;
+ }
+ });
+ SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslCtxBuilder.build(), NoopHostnameVerifier.INSTANCE);
+ clientBuilder.setSSLSocketFactory(factory);
+ System.out.println("Ignoring SSL validation");
+ }
+ CloseableHttpClient client = clientBuilder.build();
+ CloseableHttpResponse response = client.execute(request);
+ // print response code:
+ LOG.ok("response code: {0}", String.valueOf(response.getStatusLine().getStatusCode()));
+ // client.close();
+ // DO NOT CLOSE response HERE !!!
+ return response;
+ } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Execution of the request failed: problem occurred during HTTP client execution: \n\t")
+ .append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString(), e);
+ e.printStackTrace();
+ throw new ConnectorIOException(exceptionMsg.toString());
+ }
+ }
+
+ /**
+ * Checks HTTP response for errors. If the response is an error then the
+ * method throws the ConnId exception that is the most appropriate match for
+ * the error.
+ */
+ void processResponseErrors(CloseableHttpResponse response) {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode <= 299) {
+ return;
+ }
+ String responseBody = null;
+ try {
+ responseBody = EntityUtils.toString(response.getEntity());
+ } catch (IOException e) {
+ LOG.warn("cannot read response body: " + e, e);
+ }
+
+ StringBuilder message = new StringBuilder();
+ message.append("HTTP error ").append(statusCode).append(" ").append(response.getStatusLine().getReasonPhrase())
+ .append(" : ").append(responseBody);
+ if (statusCode == 401 || statusCode == 403) {
+ StringBuilder anauthorizedMessage = new StringBuilder(); // response
+ // body
+ // of
+ // status
+ // code
+ // 401
+ // contains
+ // binary
+ // data.
+ anauthorizedMessage.append("HTTP error ").append(statusCode).append(" ")
+ .append(response.getStatusLine().getReasonPhrase())
+ .append(" : Provided credentials are incorrect.");
+ closeResponse(response);
+ LOG.error("{0}", anauthorizedMessage.toString());
+ throw new InvalidCredentialException(anauthorizedMessage.toString());
+ }
+ LOG.error("{0}", message.toString());
+ if ((statusCode == 400 || statusCode == 404) && message.toString().contains("already")) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new AlreadyExistsException(message.toString());
+ }
+ if (statusCode == 400 || statusCode == 405 || statusCode == 406) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new ConnectorIOException(message.toString());
+ }
+ if (statusCode == 402 || statusCode == 407) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new PermissionDeniedException(message.toString());
+ }
+ if (statusCode == 404 || statusCode == 410) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new UnknownUidException(message.toString());
+ }
+ if (statusCode == 408) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new OperationTimeoutException(message.toString());
+ }
+ if (statusCode == 412) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new PreconditionFailedException(message.toString());
+ }
+ if (statusCode == 418) {
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new UnsupportedOperationException("Sorry, no cofee: " + message.toString());
+ }
+
+ closeResponse(response);
+ LOG.error(message.toString());
+ throw new ConnectorException(message.toString());
+ }
+
+ void closeResponse(CloseableHttpResponse response) {
+ // to avoid pool waiting
+ if (response == null)
+ return;
+ try {
+ response.close();
+ } catch (IOException e) {
+ LOG.warn(e, "Failed to close response: " + response);
+ }
+ }
+
+ void closeClient(CloseableHttpClient client){
+ if (client == null){
+ return;
+ }
+ try {
+ client.close();
+ } catch (IOException e) {
+ LOG.warn(e, "Failed to close client.");
+ }
+ }
+
+ // filter json objects by substring:
+ JSONArray substringFiltering(JSONArray inputJsonArray, String attrName, String subValue) {
+ JSONArray jsonArrayOut = new JSONArray();
+ // String attrName = attribute.getName().toString();
+ // LOGGER.info("\n\tSubstring filtering: {0} ({1})", attrName,
+ // subValue);
+ for (int i = 0; i < inputJsonArray.length(); i++) {
+ JSONObject jsonObject = inputJsonArray.getJSONObject(i);
+ if (!jsonObject.has(attrName)) {
+ LOG.warn("\n\tProcessing JSON Object does not contain attribute {0}.", attrName);
+ return null;
+ }
+ if (jsonObject.has(attrName) && (jsonObject.get(attrName)).toString().contains(subValue)) {
+ // LOG.ok("value: {0}, subValue: {1} - MATCH: {2}",
+ // jsonObject.get(attrName).toString(), subValue, "YES");
+ jsonArrayOut.put(jsonObject);
+ }
+ // else LOG.ok("value: {0}, subValue: {1} - MATCH: {2}",
+ // jsonObject.getString(attrName), subValue, "NO");
+ }
+ return jsonArrayOut;
+ }
+
+ JSONArray startsWithFiltering(Filter query, Attribute attr, OperationOptions options, String objClass) {
+ String attrValue = attr.getValue().get(0).toString();
+ HttpGet request;
+ URIBuilder getUri = null;
+ if (attrValue != null) {
+ try {
+ getUri = getURIBuilder();
+ getUri.setPath(URI_BASE_PATH + URI_USER_PATH + URI_SEARCH_PATH);
+ getUri.addParameter(PARAM_USERNAME, attrValue);
+ if (options.getPagedResultsOffset() != null || options.getPageSize() != null) {
+ int pageNumber = options.getPagedResultsOffset();
+ int usersPerPage = options.getPageSize();
+ int startAt = (pageNumber * usersPerPage) - usersPerPage;
+ //LOG.info("\n\tpage: {0}, users per page {1}, start at: {2}", pageNumber, usersPerPage, startAt);
+ getUri.addParameter(PARAM_MAX_RESULTS, String.valueOf(usersPerPage));
+ getUri.addParameter(PARAM_START_AT, String.valueOf(startAt));
+ }
+ ;
+ request = new HttpGet(getUri.build());
+
+ JSONArray objectsArray = new JSONArray();
+ if (objClass.equals(ObjectClass.GROUP_NAME)) {
+ JSONObject object = callRequest(request, true, CONTENT_TYPE_JSON);
+ objectsArray = object.getJSONArray(ATTR_GROUPS);
+ }
+ if (objClass.equals(ObjectClass.ACCOUNT_NAME) || objClass.equals(PROJECT_NAME)) {
+ objectsArray = callRequest(request);
+ }
+ // handleObjects(objectsArray, handler, options, objClass);
+ return objectsArray;
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Get operation failed: problem occurred during executing URI: ").append(getUri)
+ .append(", using attribute: ").append(attrValue).append("\n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ } else
+ throwNullAttrException(query);
+ return null;
+ }
+
+ // method called when attribute of query filter is null:
+ void throwNullAttrException(Filter query) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg
+ .append("Get operation failed: problem occurred because of not provided attribute of query filter: ")
+ .append(query);
+ LOG.error(exceptionMsg.toString());
+ throw new InvalidAttributeValueException(exceptionMsg.toString());
+ }
+
+ // create uri from base host:
+ URIBuilder getURIBuilder() {
+ String baseHost = configuration.getBaseUrl();
+ URIBuilder uri = new URIBuilder();
+ uri.setScheme("https");
+ uri.setHost(baseHost);
+ uri.setPath(URI_BASE_PATH);
+ return uri;
+ }
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // avatar processing:
+ void createOrUpdateAvatar(byte[] avatar, String uid, String objectName) {
+ // crop image to square and resize it to needed size:
+ byte[] resizedImage = resizeAndCropImage(avatar, 48, 48);
+ String username = null, key = null;
+
+ CloseableHttpClient client = HttpClientBuilder.create().build();
+
+ boolean needsCropping = false;
+ int cropperWidth, cropperOffsetX, cropperOffsetY;
+ JSONObject cropBody = new JSONObject();
+ URIBuilder uri = getURIBuilder();
+ JSONObject responseObject = new JSONObject();
+ String avatarId = null;
+
+ // delete old avatar before the new one will be successfully set:
+ deleteAvatar(uid, objectName);
+
+ // uri for uploading user avatar:
+ if (objectName.equals(USER_NAME)) { // USER
+ username = getUsernameFromUid(uid);
+ uri.setPath(URI_BASE_PATH + URI_USER_PATH + URI_TEMP_AVATAR_PATH);
+ uri.addParameter(PARAM_USERNAME, username);
+ uri.addParameter(PARAM_FILENAME, username + ".jpeg");
+ }
+
+ // uri for uploading project avatar
+ if (objectName.equals(PROJECT_NAME)) { // PROJECT
+ key = getProjectKeyFromUid(uid);
+ uri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + key + URI_TEMP_AVATAR_PATH);
+ uri.addParameter(PARAM_FILENAME, key + ".jpeg");
+ // LOGGER.info("\n\tupload project avatar: {0}", uri.toString());
+ }
+
+ HttpEntityEnclosingRequestBase postRequest;
+ HttpEntityEnclosingRequestBase putRequest;
+ CloseableHttpResponse response = null;
+ // 1st step: upload avatar:
+ try {
+ postRequest = new HttpPost(uri.build());
+ if (resizedImage != null) {
+ postRequest.setHeader("X-Atlassian-Token", "no-check");
+ postRequest.setHeader("Authorization", "Basic YWRtaW5pc3RyYXRvcjp0cmFpbmluZw==");
+ postRequest.setHeader("Content-Type", CONTENT_TYPE_JPEG_IMAGE);
+ postRequest.setEntity(new ByteArrayEntity(resizedImage));
+
+ response = (CloseableHttpResponse) client.execute(postRequest);
+ // print response code:
+ //LOG.ok("response code: {0}", String.valueOf(response.getStatusLine().getStatusCode()));
+ // client.close();
+ // DO NOT CLOSE response HERE !!!
+ processResponseErrors(response);
+ String result = EntityUtils.toString(response.getEntity());
+
+ closeResponse(response);
+ responseObject = new JSONObject(result);
+
+ if (responseObject.has("needsCropping")) {
+ needsCropping = "true".equals(responseObject.get("needsCropping").toString());
+ // LOGGER.info("\n\tneedsCropping-{0}", needsCropping);
+ }
+ if (responseObject.has("cropperWidth")) {
+ cropperWidth = (int) responseObject.get("cropperWidth");
+ // LOGGER.info("\n\tcropperWidth-{0}", cropperWidth);
+ cropBody.put("cropperWidth", cropperWidth);
+ }
+ if (responseObject.has("cropperOffsetX")) {
+ cropperOffsetX = (int) responseObject.get("cropperOffsetX");
+ // LOGGER.info("\n\tcropperOffsetX-{0}",cropperOffsetX);
+ cropBody.put("cropperOffsetX", cropperOffsetX);
+ }
+ if (responseObject.has("cropperOffsetY")) {
+ cropperOffsetY = (int) responseObject.get("cropperOffsetY");
+ // LOGGER.info("\n\tcropperOffsetY-{0}", cropperOffsetY);
+ cropBody.put("cropperOffsetY", cropperOffsetY);
+ }
+ if (responseObject.has("id")) {
+ avatarId = responseObject.getString("id");
+ // LOGGER.info("\n\tid-{0}", cropperOffsetY);
+ }
+ }
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Uploading of the avatar failed: problem occurred during building URI: ").append(uri)
+ .append("\n\t").append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Uploading of the avatar failed: problem occured during request execution: \n\t")
+ .append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString());
+ }
+ // 2nd step: crop avatar:
+ if (needsCropping) {
+ uri.removeQuery();
+ if (objectName.equals(USER_NAME)) { // USER
+ uri.setPath(URI_BASE_PATH + URI_USER_PATH + URI_AVATAR_PATH);
+ uri.addParameter(PARAM_USERNAME, username);
+ }
+ if (objectName.equals(PROJECT_NAME)) { // PROJECT
+ uri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + key + URI_AVATAR_PATH);
+ LOG.info("\n\tcrop project avatar: {0}", uri.toString());
+ }
+ try {
+ postRequest = new HttpPost(uri.build());
+ postRequest.setHeader("X-Atlassian-Token", "no-check");
+ postRequest.setHeader("Content-Type", CONTENT_TYPE_JSON);
+
+ HttpEntity entity = new ByteArrayEntity(cropBody.toString().getBytes("UTF-8"));
+ postRequest.setEntity(entity);
+
+ response = (CloseableHttpResponse) client.execute(postRequest);
+ // print response code:
+ LOG.ok("response code: {0}", String.valueOf(response.getStatusLine().getStatusCode()));
+ // client.close();
+ // DO NOT CLOSE response HERE !!!
+ processResponseErrors(response);
+ String result = EntityUtils.toString(response.getEntity());
+
+ closeResponse(response);
+ LOG.ok("response body: {0}", result);
+ responseObject = new JSONObject(result);
+
+ avatarId = responseObject.getString("id");
+ // LOGGER.info("\n\t2nd step: {0}", responseObject.toString());
+
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Cropping of the avatar failed: problem occurred during building URI: ").append(uri)
+ .append("\n\t").append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ } catch (UnsupportedEncodingException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg
+ .append("Cropping of the avatar failed: problem occurred during encoding request body for URI: ")
+ .append(uri).append("\n\t").append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Cropping of the avatar failed: problem occured during request execution: \n\t")
+ .append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString());
+ }
+
+ }
+ // 3rd step: confirm avatar:
+
+ try {
+ uri.removeQuery();
+ if (objectName.equals(USER_NAME)) { // USER
+ uri.setPath(URI_BASE_PATH + URI_USER_PATH + URI_AVATAR_PATH);
+ uri.addParameter(PARAM_USERNAME, username);
+ }
+ if (objectName.equals(PROJECT_NAME)) { // PROJECT
+ uri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + key + URI_AVATAR_PATH);
+ LOG.info("\n\tconfirm project avatar: {0}", uri.toString());
+ }
+
+ putRequest = new HttpPut(uri.build());
+ putRequest.setHeader("X-Atlassian-Token", "no-check");
+ putRequest.setHeader("Content-Type", CONTENT_TYPE_JSON);
+ JSONObject confirmBody = new JSONObject();
+ confirmBody.put("id", avatarId);
+
+ HttpEntity entity = new ByteArrayEntity(confirmBody.toString().getBytes("UTF-8"));
+ putRequest.setEntity(entity);
+
+ response = (CloseableHttpResponse) client.execute(putRequest);
+
+ LOG.ok("response code: {0}", String.valueOf(response.getStatusLine().getStatusCode()));
+ // client.close();
+ // DO NOT CLOSE response HERE !!!
+ processResponseErrors(response);
+
+ closeResponse(response);
+
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Confirmation of the avatar failed: problem occurred during building URI: ").append(uri)
+ .append("\n\t").append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ } catch (UnsupportedEncodingException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg
+ .append("Confirmation of the avatar failed: problem occurred during encoding request body for URI: ")
+ .append(uri).append("\n\t").append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Confirmation of the avatar failed: problem occured during request execution: \n\t")
+ .append(e.getLocalizedMessage());
+ closeResponse(response);
+ closeClient(client);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString());
+ }
+ }
+
+ void deleteAvatar(String uid, String objectName) {
+ // user:
+ // http://example.com:8080/jira/rest/api/2/user/avatar/?username=
+ // project:
+ // http://example.com:8080/jira/rest/api/2/project//avatar/
+ URIBuilder deleteUri = getURIBuilder();
+ CloseableHttpResponse response = null;
+ if (uid != null) {
+ try {
+ deleteUri = getURIBuilder();
+
+ String avatarId = getAvatarId(uid, objectName);
+ if (avatarId == null) {
+ LOG.warn("Deleting of the avatar ignored: Requested avatar is probably default system avatar.");
+ return;
+ }
+
+ if (objectName.equals(PROJECT_NAME)) {
+ String key = getProjectKeyFromUid(uid);
+ deleteUri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + key + URI_AVATAR_PATH + "/" + avatarId);
+ }
+ if (objectName.equals(USER_NAME)) {
+ String username = getUsernameFromUid(uid);
+ deleteUri.setPath(URI_BASE_PATH + URI_USER_PATH + URI_AVATAR_PATH + "/" + avatarId);
+ deleteUri.addParameter(PARAM_USERNAME, username);
+ }
+
+ HttpDelete request = new HttpDelete(deleteUri.build());
+ request.addHeader("Authorization", "Basic " + authEncoding());
+ response = execute(request);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 401) {
+ LOG.warn("Deleting of the avatar was ignored: Avatar with id {0} is probably system avatar.",
+ avatarId);
+ return;
+ } else {
+ processResponseErrors(response);
+ }
+ closeResponse(response);
+
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Deletion of the avatar failed: problem occurred during executing URI: ")
+ .append(deleteUri).append(", using uid attribute: ").append(uid).append("\n\t")
+ .append(e.getLocalizedMessage());
+ closeResponse(response);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ }
+ }
+
+ String getAvatarId(String uid, String objectName) {
+ URIBuilder getUri = getURIBuilder();
+ if (uid != null) {
+ try {
+ getUri = getURIBuilder();
+ JSONObject object = null;
+
+ if (objectName.equals(PROJECT_NAME)) {
+ getUri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + uid);
+ }
+ if (objectName.equals(USER_NAME)) {
+ getUri.setPath(URI_BASE_PATH + URI_USER_PATH);
+ getUri.addParameter(PARAM_KEY, uid);
+ }
+
+ HttpGet request = new HttpGet(getUri.build());
+ object = callRequest(request, true, CONTENT_TYPE_JSON);
+ // LOGGER.info("project key: {0}", object.getString(ATTR_KEY));
+ JSONObject avatarUrls = object.getJSONObject(ATTR_AVATAR_URLS);
+ String avatarUrl = avatarUrls.getString("48x48");
+ URIBuilder avatarUri = new URIBuilder(avatarUrl);
+ List queryParameters = avatarUri.getQueryParams();
+
+ for (NameValuePair pair : queryParameters) {
+ String name = pair.getName();
+ if (name.equals("avatarId")) {
+ return pair.getValue();
+ }
+ }
+
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Getting avatar uid failed: problem occurred during executing URI: ").append(getUri)
+ .append("\n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ }
+ return null;
+ }
+
+ byte[] resizeAndCropImage(byte[] image, int width, int height) {
+ ByteArrayInputStream bis = new ByteArrayInputStream(image);
+ BufferedImage buffImage;
+ try {
+ buffImage = ImageIO.read(bis);
+ if (buffImage == null) {
+ String exceptionMsg = "\n\tBuffering of the avatar for resize failed!";
+ LOG.error(exceptionMsg);
+ throw new ConnectorIOException(exceptionMsg);
+ }
+ // crop image if needed:
+ int originalWidth = buffImage.getWidth();
+ int originalHeight = buffImage.getHeight();
+ BufferedImage croppedBuffImage;
+ if (originalWidth > originalHeight) {
+ croppedBuffImage = buffImage.getSubimage((originalWidth / 2 - originalHeight / 2), 0, originalHeight,
+ originalHeight);
+ } else if (originalHeight > originalWidth) {
+ croppedBuffImage = buffImage.getSubimage(0, (originalHeight / 2 - originalWidth / 2), originalWidth,
+ originalWidth);
+ } else {
+ croppedBuffImage = buffImage;
+ }
+
+ // LOGGER.info("\n\tConverting image format and size...");
+ // resize image:
+ BufferedImage resizedBuffImage = new BufferedImage(width, height, 5);
+ Graphics2D imgGraphics = resizedBuffImage.createGraphics();
+ imgGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ imgGraphics.drawImage(croppedBuffImage, 0, 0, width, height, null);
+ imgGraphics.dispose();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ // LOGGER.info("\n\tWritting output image...");
+ if (ImageIO.write(resizedBuffImage, "jpeg", bos) == false) {
+ LOG.error("\n\tConverting image format and size faild.");
+ return null;
+ } else {
+ // LOGGER.info("\n\tConverting finished successfully.");
+ byte[] resizedImage = bos.toByteArray();
+ // ImageIO.write(resizedImage, "png", new File("D:\\out.png"));
+ return resizedImage;
+
+ }
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg
+ .append("Converting avatar image format failed: problem occured during converting format and writing it to byte array stream: \n\t")
+ .append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString());
+ }
+ }
+
+ byte[] getAvatar(String avatarUrl, ObjectClass objClass) {
+ byte[] result = null;
+ try {
+ URIBuilder getUri = new URIBuilder(avatarUrl);
+ HttpGet request = new HttpGet(getUri.build());
+ request.addHeader("Authorization", "Basic " + authEncoding());
+ request.addHeader("User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31");
+
+ CloseableHttpResponse response = execute(request);
+ processResponseErrors(response);
+
+ HttpEntity entity = response.getEntity();
+ result = EntityUtils.toByteArray(entity);
+ String imageType = URLConnection.guessContentTypeFromStream(new ByteArrayInputStream(result));
+ // LOGGER.info("\n\ttype: {0}", contentType);
+
+ // SVG avatar:
+ if (imageType.contains("xml")) {
+ // LOGGER.info("\n\tSVG avatar");
+ TranscoderInput inputSVGimage = new TranscoderInput(avatarUrl);
+ OutputStream outputJPEGstream = new ByteArrayOutputStream();
+ TranscoderOutput outputJPEGimage = new TranscoderOutput(outputJPEGstream);
+ JPEGTranscoder SVGtoJPEGconverter = new JPEGTranscoder();
+ SVGtoJPEGconverter.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(1));
+ try {
+ SVGtoJPEGconverter.transcode(inputSVGimage, outputJPEGimage);
+ result = ((ByteArrayOutputStream) outputJPEGstream).toByteArray();
+ // ByteArrayInputStream bis = new
+ // ByteArrayInputStream(result);
+ // BufferedImage img = ImageIO.read(bis);
+ // ImageIO.write(img, "jpeg", new File("D:\\outJPEG.jpeg"));
+ outputJPEGstream.flush();
+ outputJPEGstream.close();
+ closeResponse(response);
+ return result;
+ } catch (TranscoderException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Converting from SVG format to JPEG foramt failed").append("\n\t")
+ .append(e.getLocalizedMessage());
+ closeResponse(response);
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ } else {
+ return result;
+ }
+ } catch (IOException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Getting avatar failed: problem occured during determining of image format:")
+ .append("\n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorIOException(exceptionMsg.toString());
+ } catch (URISyntaxException e1) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Getting avatar failed: Problem occured during bilding URI: ").append(avatarUrl)
+ .append("\n\t").append(e1.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ // return result;
+ }
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ String getUsernameFromUid(String uid) {
+ URIBuilder getUri = getURIBuilder();
+ if (uid != null) {
+ try {
+ getUri = getURIBuilder();
+ getUri.setPath(URI_BASE_PATH + URI_USER_PATH);
+ getUri.addParameter(UID, uid);
+ HttpGet request = new HttpGet(getUri.build());
+ JSONObject user = callRequest(request, true, CONTENT_TYPE_JSON);
+ // LOGGER.info("username: {0}", user.getString(ATTR_NAME));
+ return user.getString(ATTR_NAME);
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Get username form user ID failed: problem occurred during executing URI: ")
+ .append(getUri).append(", using uid attribute: ").append(uid).append("\n\t")
+ .append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ }
+ return null;
+ }
+
+ String getUserIdFromName(String username) {
+ URIBuilder getUri = getURIBuilder();
+ if (username != null) {
+ try {
+ getUri = getURIBuilder();
+ getUri.setPath(URI_BASE_PATH + URI_USER_PATH);
+ getUri.addParameter(PARAM_USERNAME, username);
+ HttpGet request = new HttpGet(getUri.build());
+ JSONObject user = callRequest(request, true, CONTENT_TYPE_JSON);
+ LOG.info("username: {0}", user.getString(ATTR_KEY));
+ return user.getString(ATTR_KEY);
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Get user ID form user username failed: problem occurred during executing URI: ")
+ .append(getUri).append("\n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ }
+ return null;
+ }
+
+ String getProjectKeyFromUid(String uid) {
+ URIBuilder getUri = getURIBuilder();
+ if (uid != null) {
+ try {
+ getUri = getURIBuilder();
+ getUri.setPath(URI_BASE_PATH + URI_PROJECT_PATH + "/" + uid);
+ HttpGet request = new HttpGet(getUri.build());
+ JSONObject project = callRequest(request, true, CONTENT_TYPE_JSON);
+ LOG.info("project key: {0}", project.getString(ATTR_KEY));
+ return project.getString(ATTR_KEY);
+ } catch (URISyntaxException e) {
+ StringBuilder exceptionMsg = new StringBuilder();
+ exceptionMsg.append("Get project key form uid failed: problem occurred during executing URI: ")
+ .append(getUri).append("\n\t").append(e.getLocalizedMessage());
+ LOG.error(exceptionMsg.toString());
+ throw new ConnectorException(exceptionMsg.toString());
+ }
+ }
+ return null;
+ }
+
+ T addAttr(ConnectorObjectBuilder builder, String attrName, T attrVal) {
+ if (attrVal != null) {
+ builder.addAttribute(attrName, attrVal);
+ }
+ return attrVal;
+ }
+
+ String getStringAttr(Set attributes, String attrName) throws InvalidAttributeValueException {
+ return getAttr(attributes, attrName, String.class);
+ }
+
+ GuardedString getGuardedStringAttr(Set attributes, String attrName)
+ throws InvalidAttributeValueException {
+ return getAttr(attributes, attrName, GuardedString.class);
+ }
+
+ T getAttr(Set attributes, String attrName, Class type)
+ throws InvalidAttributeValueException {
+ return getAttr(attributes, attrName, type, null);
+ }
+
+ byte[] getByteArrayAttr(Set attributes, String attrName)
+ throws InvalidAttributeValueException {
+ return getAttr(attributes, attrName, byte[].class);
+ }
+
+ @SuppressWarnings("unchecked")
+ private T getAttr(Set attributes, String attrName, Class type, T defaultVal)
+ throws InvalidAttributeValueException {
+ for (Attribute attr : attributes) {
+ if (attrName.equals(attr.getName())) {
+ List