Merge branch 'master' of ssh://repository.entgra.net:222/community/device-mgt-core into vpp-v2

otp-db-change
osh 1 year ago
commit a20c5a723c

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>grafana-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>grafana-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>grafana-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>analytics-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

@ -22,7 +22,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -24,13 +24,12 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIService
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Scope;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Mediation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevision;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevisionDeployment;
import org.json.JSONObject;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
import org.wso2.carbon.apimgt.api.model.Scope;
import org.wso2.carbon.apimgt.api.model.Mediation;
import org.wso2.carbon.apimgt.api.model.APIRevision;
import org.wso2.carbon.apimgt.api.model.APIRevisionDeployment;
import org.wso2.carbon.apimgt.api.model.Documentation;
import java.util.List;
@ -48,7 +47,7 @@ public interface PublisherRESTAPIServices {
boolean updateSharedScope(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, Scope scope)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
JSONObject getApi(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, APIIdentifier apiIdentifier)
JSONObject getApi(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, String apiUuid)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
JSONObject getApis(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo)
@ -65,15 +64,15 @@ public interface PublisherRESTAPIServices {
throws APIServicesException, BadRequestException, UnexpectedResponseException;
JSONObject getAllApiSpecificMediationPolicies(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
APIIdentifier apiIdentifier)
String apiUuid)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
boolean addApiSpecificMediationPolicy(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
String uuid, Mediation mediation)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
boolean updateApiSpecificMediationPolicyContent(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
String uuid, Mediation mediation)
boolean deleteApiSpecificMediationPolicy(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
String uuid, Mediation mediation)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
boolean changeLifeCycleStatus(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
@ -108,11 +107,11 @@ public interface PublisherRESTAPIServices {
String uuid, String documentID)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
Documentation addDocumentation(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation addDocumentation(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
String uuid, Documentation documentation)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
boolean addDocumentationContent(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
APIInfo api, String docId, String docContent)
String apiUuid, String docId, String docContent)
throws APIServicesException, BadRequestException, UnexpectedResponseException;
}

@ -21,30 +21,26 @@ package io.entgra.device.mgt.core.apimgt.extension.rest.api;
import com.google.gson.Gson;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Scope;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Mediation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevision;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevisionDeployment;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.AccessTokenInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.HttpsTrustManagerUtils;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.util.ScopeUtils;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.RequestBody;
import okhttp3.*;
import okhttp3.Request.Builder;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.ssl.Base64;
import org.json.JSONArray;
import org.json.JSONObject;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
import org.wso2.carbon.apimgt.api.model.Scope;
import org.wso2.carbon.apimgt.api.model.Mediation;
import org.wso2.carbon.apimgt.api.model.APIRevision;
import org.wso2.carbon.apimgt.api.model.APIRevisionDeployment;
import org.wso2.carbon.apimgt.api.model.Documentation;
import java.io.IOException;
import java.util.List;
@ -237,10 +233,10 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
}
@Override
public JSONObject getApi(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, APIIdentifier apiIdentifier)
public JSONObject getApi(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo, String apiUuid)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String getAllApi = endPointPrefix + Constants.API_ENDPOINT + apiIdentifier.getUUID();
String getAllApi = endPointPrefix + Constants.API_ENDPOINT + apiUuid;
Request request = new Request.Builder()
.url(getAllApi)
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
@ -259,7 +255,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
generateAccessTokenFromRefreshToken(accessTokenInfo.getRefresh_token(),
apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret());
//TODO: max attempt count
return getApi(apiApplicationKey, refreshedAccessToken, apiIdentifier);
return getApi(apiApplicationKey, refreshedAccessToken, apiUuid);
} else if (HttpStatus.SC_BAD_REQUEST == response.code()) {
String msg = "Bad Request, Invalid request";
log.error(msg);
@ -344,6 +340,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
" \"apiThrottlingPolicy\": " + api.getApiThrottlingPolicy() + ",\n" +
" \"authorizationHeader\": \"" + api.getAuthorizationHeader() + "\",\n" +
" \"visibility\": \"" + api.getVisibility() + "\",\n" +
" \"mediationPolicies\": " + (api.getInSequence() != null ? "[{\"name\": \"" + api.getInSequence() + "\",\"type\": \"in\"}]" : null) + ",\n" +
" \"subscriptionAvailability\": \"" + api.getSubscriptionAvailability() + "\",\n" +
" \"subscriptionAvailableTenants\": [],\n" +
" \"additionalProperties\": [],\n" +
@ -359,7 +356,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
" \"endpointConfig\": " + api.getEndpointConfig().toString() + ",\n" +
" \"endpointImplementationType\": \"ENDPOINT\",\n" +
" \"scopes\": " + api.getScopes().toString() + ",\n" +
" \"operations\": " + api.getOperations().toString() + ",\n" +
" \"operations\": " + (api.getOperations() != null ? api.getOperations().toString() : null) + ",\n" +
" \"threatProtectionPolicies\": null,\n" +
" \"categories\": [],\n" +
" \"keyManagers\": " + gson.toJson(api.getKeyManagers()) + ",\n" +
@ -431,6 +428,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
" \"apiThrottlingPolicy\": " + api.getApiThrottlingPolicy() + ",\n" +
" \"authorizationHeader\": \"" + api.getAuthorizationHeader() + "\",\n" +
" \"visibility\": \"" + api.getVisibility() + "\",\n" +
" \"mediationPolicies\": " + (api.getInSequence() != null ? "[{\"name\": \"" + api.getInSequence() + "\",\"type\": \"in\"}]" : null) + ",\n" +
" \"subscriptionAvailability\": \"" + api.getSubscriptionAvailability() + "\",\n" +
" \"subscriptionAvailableTenants\": [],\n" +
" \"additionalProperties\": [],\n" +
@ -446,7 +444,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
" \"endpointConfig\": " + api.getEndpointConfig().toString() + ",\n" +
" \"endpointImplementationType\": \"ENDPOINT\",\n" +
" \"scopes\": " + api.getScopes().toString() + ",\n" +
" \"operations\": " + api.getOperations().toString() + ",\n" +
" \"operations\": " + (api.getOperations() != null? api.getOperations().toString() : null) + ",\n" +
" \"threatProtectionPolicies\": null,\n" +
" \"categories\": [],\n" +
" \"keyManagers\": " + gson.toJson(api.getKeyManagers()) + ",\n" +
@ -493,11 +491,16 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
String uuid, String asyncApiDefinition)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String addNewScope = endPointPrefix + Constants.API_ENDPOINT + uuid;
String saveAsyncAPI = endPointPrefix + Constants.API_ENDPOINT + uuid + "/asyncapi";
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("apiDefinition", asyncApiDefinition)
.build();
RequestBody requestBody = RequestBody.create(JSON, asyncApiDefinition);
Request request = new Request.Builder()
.url(addNewScope)
.url(saveAsyncAPI)
.addHeader(Constants.HEADER_CONTENT_TYPE, "multipart/form-data")
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
+ accessTokenInfo.getAccess_token())
.put(requestBody)
@ -532,10 +535,10 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
@Override
public JSONObject getAllApiSpecificMediationPolicies(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
APIIdentifier apiIdentifier)
String apiUuid)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String getAPIMediationEndPoint = endPointPrefix + Constants.API_ENDPOINT + apiIdentifier.getUUID() + "/mediation-policies";
String getAPIMediationEndPoint = endPointPrefix + Constants.API_ENDPOINT + apiUuid + "/mediation-policies";
Request request = new Request.Builder()
.url(getAPIMediationEndPoint)
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
@ -554,7 +557,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
generateAccessTokenFromRefreshToken(accessTokenInfo.getRefresh_token(),
apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret());
//TODO: max attempt count
return getAllApiSpecificMediationPolicies(apiApplicationKey, refreshedAccessToken, apiIdentifier);
return getAllApiSpecificMediationPolicies(apiApplicationKey, refreshedAccessToken, apiUuid);
} else if (HttpStatus.SC_BAD_REQUEST == response.code()) {
String msg = "Bad Request, Invalid request";
log.error(msg);
@ -575,12 +578,17 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
String uuid, Mediation mediation)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String addAPIMediation = endPointPrefix + Constants.API_ENDPOINT + uuid + "/mediation-policies/" + mediation.getUuid()
+ "/content";
String addAPIMediation = endPointPrefix + Constants.API_ENDPOINT + uuid + "/mediation-policies";
RequestBody requestBody = RequestBody.create(JSON, String.valueOf(mediation));
Request request = new Request.Builder()
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("inlineContent", mediation.getConfig())
.addFormDataPart("type", mediation.getType())
.build();
Request request = new Builder()
.url(addAPIMediation)
.addHeader(Constants.HEADER_CONTENT_TYPE, "multipart/form-data")
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
+ accessTokenInfo.getAccess_token())
.post(requestBody)
@ -613,26 +621,23 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
}
@Override
public boolean updateApiSpecificMediationPolicyContent(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
public boolean deleteApiSpecificMediationPolicy(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
String uuid, Mediation mediation)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String updateApiMediationEndPOint = endPointPrefix + Constants.API_ENDPOINT + uuid + "/mediation-policies/" + mediation.getUuid()
+ "/content";
String deleteApiMediationEndPOint = endPointPrefix + Constants.API_ENDPOINT + uuid + "/mediation-policies/" + mediation.getUuid();
RequestBody requestBody = RequestBody.create(JSON, String.valueOf(mediation));
Request request = new Request.Builder()
.url(updateApiMediationEndPOint)
.url(deleteApiMediationEndPOint)
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
+ accessTokenInfo.getAccess_token())
.put(requestBody)
.delete()
.build();
try {
Response response = client.newCall(request).execute();
if (HttpStatus.SC_CREATED == response.code()) { // Check response status
if (HttpStatus.SC_NO_CONTENT == response.code()) { // Check response status
return true;
} else if (HttpStatus.SC_UNAUTHORIZED == response.code()) {
APIApplicationServices apiApplicationServices = new APIApplicationServicesImpl();
@ -640,7 +645,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
generateAccessTokenFromRefreshToken(accessTokenInfo.getRefresh_token(),
apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret());
//TODO: max attempt count
return updateApiSpecificMediationPolicyContent(apiApplicationKey, refreshedAccessToken, uuid, mediation);
return deleteApiSpecificMediationPolicy(apiApplicationKey, refreshedAccessToken, uuid, mediation);
} else if (HttpStatus.SC_BAD_REQUEST == response.code()) {
String msg = "Bad Request, Invalid mediation policy";
log.error(msg);
@ -1018,18 +1023,19 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
String addNewScope = endPointPrefix + Constants.API_ENDPOINT + uuid + "/documents";
String document = "{\n" +
" \"name\": \" " + documentation.getName() + " \",\n" +
" \"type\": \" " + documentation.getType() + " \",\n" +
" \"summary\": \" " + documentation.getSummary() + " \",\n" +
" \"sourceType\": \" " + documentation.getSourceType() + " \",\n" +
" \"inlineContent\": \" " + documentation.getSourceType() + " \",\n" +
" \"visibility\": \" " + documentation.getVisibility() + " \",\n" +
" \"createdBy\": \" admin \"\n" +
" \"name\": \"" + documentation.getName() + "\",\n" +
" \"type\": \"" + documentation.getType() + "\",\n" +
" \"summary\": \"" + documentation.getSummary() + "\",\n" +
" \"sourceType\": \"" + documentation.getSourceType() + "\",\n" +
" \"inlineContent\": \"" + documentation.getSourceType() + "\",\n" +
" \"visibility\": \"" + documentation.getVisibility() + "\",\n" +
" \"createdBy\": \"admin\"\n" +
"}";
RequestBody requestBody = RequestBody.create(JSON, document);
Request request = new Request.Builder()
.url(addNewScope)
.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.APPLICATION_JSON)
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
+ accessTokenInfo.getAccess_token())
.post(requestBody)
@ -1063,14 +1069,19 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
@Override
public boolean addDocumentationContent(APIApplicationKey apiApplicationKey, AccessTokenInfo accessTokenInfo,
APIInfo api, String docId, String docContent)
String apiUuid, String docId, String docContent)
throws APIServicesException, BadRequestException, UnexpectedResponseException {
String addDocumentationContentEndPoint = endPointPrefix + Constants.API_ENDPOINT + api.getId() + "/documents/" + docId;
String addDocumentationContentEndPoint = endPointPrefix + Constants.API_ENDPOINT + apiUuid + "/documents/" + docId + "/content";
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("inlineContent", docContent)
.build();
RequestBody requestBody = RequestBody.create(JSON, docContent);
Request request = new Request.Builder()
.url(addDocumentationContentEndPoint)
.addHeader(Constants.HEADER_CONTENT_TYPE, "multipart/form-data")
.addHeader(Constants.AUTHORIZATION_HEADER_NAME, Constants.AUTHORIZATION_HEADER_PREFIX_BEARER
+ accessTokenInfo.getAccess_token())
.post(requestBody)
@ -1086,7 +1097,7 @@ public class PublisherRESTAPIServicesImpl implements PublisherRESTAPIServices {
generateAccessTokenFromRefreshToken(accessTokenInfo.getRefresh_token(),
apiApplicationKey.getClientId(), apiApplicationKey.getClientSecret());
//TODO: max attempt count
return addDocumentationContent(apiApplicationKey, refreshedAccessToken, api, docId, docContent);
return addDocumentationContent(apiApplicationKey, refreshedAccessToken, apiUuid, docId, docContent);
} else if (HttpStatus.SC_BAD_REQUEST == response.code()) {
String msg = "Bad Request, Invalid documentation request body";
log.error(msg);

@ -1,12 +1,12 @@
/*
* Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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
* 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
@ -19,13 +19,8 @@
package io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import org.json.JSONObject;
import org.wso2.carbon.apimgt.api.model.APICategory;
import org.wso2.carbon.apimgt.api.model.CORSConfiguration;
import org.wso2.carbon.apimgt.api.model.WebsubSubscriptionConfiguration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* This class represents the API response.
@ -70,7 +65,6 @@ public class APIInfo {
private String accessControlRoles;
private BusinessInformation businessInformation;
private CORSConfiguration corsConfiguration;
private WebsubSubscriptionConfiguration websubSubscriptionConfiguration;
private String workflowStatus;
private String createdTime;
private String lastUpdatedTime;
@ -79,11 +73,16 @@ public class APIInfo {
private List<JSONObject> scopes = new ArrayList();
private List<JSONObject> operations;
private String threatProtectionPolicies;
private List<APICategory> apiCategories;
private List<String> keyManagers = new ArrayList();
private JSONObject serviceInfo = new JSONObject();
private AdvertiseInfo advertiseInfo;
private String asyncApiDefinition;
private Set<URITemplate> uriTemplates = new LinkedHashSet<URITemplate>();
private String inSequence;
private Map<String, String> wsUriMapping;
public String getId() {
return id;
}
@ -372,14 +371,6 @@ public class APIInfo {
this.corsConfiguration = corsConfiguration;
}
public WebsubSubscriptionConfiguration getWebsubSubscriptionConfiguration() {
return websubSubscriptionConfiguration;
}
public void setWebsubSubscriptionConfiguration(WebsubSubscriptionConfiguration websubSubscriptionConfiguration) {
this.websubSubscriptionConfiguration = websubSubscriptionConfiguration;
}
public String getWorkflowStatus() {
return workflowStatus;
}
@ -444,14 +435,6 @@ public class APIInfo {
this.threatProtectionPolicies = threatProtectionPolicies;
}
public List<APICategory> getApiCategories() {
return apiCategories;
}
public void setApiCategories(List<APICategory> apiCategories) {
this.apiCategories = apiCategories;
}
public List<String> getKeyManagers() {
return keyManagers;
}
@ -475,4 +458,36 @@ public class APIInfo {
public void setAdvertiseInfo(AdvertiseInfo advertiseInfo) {
this.advertiseInfo = advertiseInfo;
}
public String getInSequence() {
return inSequence;
}
public void setInSequence(String inSequence) {
this.inSequence = inSequence;
}
public String getAsyncApiDefinition() {
return asyncApiDefinition;
}
public void setAsyncApiDefinition(String asyncApiDefinition) {
this.asyncApiDefinition = asyncApiDefinition;
}
public Set<URITemplate> getUriTemplates() {
return uriTemplates;
}
public void setUriTemplates(Set<URITemplate> uriTemplates) {
this.uriTemplates = uriTemplates;
}
public Map<String, String> getWsUriMapping() {
return wsUriMapping;
}
public void setWsUriMapping(Map<String, String> wsUriMapping) {
this.wsUriMapping = wsUriMapping;
}
}

@ -0,0 +1,90 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import org.wso2.carbon.apimgt.api.model.APIRevisionDeployment;
import java.io.Serializable;
import java.util.List;
public class APIRevision implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String apiUUID;
private String revisionUUID;
private String description;
private String createdBy;
private String createdTime;
private List<APIRevisionDeployment> apiRevisionDeploymentList;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getApiUUID() {
return apiUUID;
}
public void setApiUUID(String apiUUID) {
this.apiUUID = apiUUID;
}
public String getRevisionUUID() {
return revisionUUID;
}
public void setRevisionUUID(String revisionUUID) {
this.revisionUUID = revisionUUID;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getCreatedTime() {
return createdTime;
}
public void setCreatedTime(String createdTime) {
this.createdTime = createdTime;
}
public List<APIRevisionDeployment> getApiRevisionDeploymentList() {
return apiRevisionDeploymentList;
}
public void setApiRevisionDeploymentList(List<APIRevisionDeployment> apiRevisionDeploymentList) {
this.apiRevisionDeploymentList = apiRevisionDeploymentList;
}
}

@ -0,0 +1,79 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import java.io.Serializable;
public class APIRevisionDeployment implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String revisionUUID;
private String deployment;
private String vhost;
private boolean isDisplayOnDevportal;
private String deployedTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRevisionUUID() {
return revisionUUID;
}
public void setRevisionUUID(String revisionUUID) {
this.revisionUUID = revisionUUID;
}
public String getDeployment() {
return deployment;
}
public void setDeployment(String deployment) {
this.deployment = deployment;
}
public String getVhost() {
return vhost;
}
public void setVhost(String vhost) {
this.vhost = vhost;
}
public boolean isDisplayOnDevportal() {
return isDisplayOnDevportal;
}
public void setDisplayOnDevportal(boolean displayOnDevportal) {
isDisplayOnDevportal = displayOnDevportal;
}
public String getDeployedTime() {
return deployedTime;
}
public void setDeployedTime(String deployedTime) {
this.deployedTime = deployedTime;
}
}

@ -1,12 +1,12 @@
/*
* Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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
* 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
@ -15,7 +15,6 @@
* specific language governing permissions and limitations
* under the License.
*/
package io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
/**

@ -1,12 +1,12 @@
/*
* Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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
* 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

@ -0,0 +1,82 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import java.util.List;
/**
* API CORS Configuration
*/
public class CORSConfiguration {
private boolean corsConfigurationEnabled;
private List<String> accessControlAllowOrigins;
private boolean accessControlAllowCredentials;
private List<String> accessControlAllowHeaders;
private List<String> accessControlAllowMethods;
public CORSConfiguration(boolean corsConfigurationEnabled, List<String> accessControlAllowOrigins,
boolean accessControlAllowCredentials,
List<String> accessControlAllowHeaders, List<String> accessControlAllowMethods) {
this.corsConfigurationEnabled = corsConfigurationEnabled;
this.accessControlAllowOrigins = accessControlAllowOrigins;
this.accessControlAllowCredentials = accessControlAllowCredentials;
this.accessControlAllowHeaders = accessControlAllowHeaders;
this.accessControlAllowMethods = accessControlAllowMethods;
}
public boolean isCorsConfigurationEnabled() {
return corsConfigurationEnabled;
}
public void setCorsConfigurationEnabled(boolean corsConfigurationEnabled) {
this.corsConfigurationEnabled = corsConfigurationEnabled;
}
public List<String> getAccessControlAllowOrigins() {
return accessControlAllowOrigins;
}
public void setAccessControlAllowOrigins(List<String> accessControlAllowOrigins) {
this.accessControlAllowOrigins = accessControlAllowOrigins;
}
public boolean isAccessControlAllowCredentials() {
return accessControlAllowCredentials;
}
public void setAccessControlAllowCredentials(boolean accessControlAllowCredentials) {
this.accessControlAllowCredentials = accessControlAllowCredentials;
}
public List<String> getAccessControlAllowHeaders() {
return accessControlAllowHeaders;
}
public void setAccessControlAllowHeaders(List<String> accessControlAllowHeaders) {
this.accessControlAllowHeaders = accessControlAllowHeaders;
}
public List<String> getAccessControlAllowMethods() {
return accessControlAllowMethods;
}
public void setAccessControlAllowMethods(List<String> accessControlAllowMethods) {
this.accessControlAllowMethods = accessControlAllowMethods;
}
}

@ -0,0 +1,192 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import java.util.Date;
public class Documentation {
private static final long serialVersionUID = 1L;
private String id;
private String documentId;
private DocumentationType type;
private String name;
private String summary;
private DocumentSourceType sourceType;
private String sourceUrl;
private DocumentVisibility visibility;
private Date lastUpdated;
private String filePath;
private Date createdDate;
private String otherTypeName;
public String getOtherTypeName() {
return this.otherTypeName;
}
public void setOtherTypeName(String otherTypeName) {
this.otherTypeName = otherTypeName;
}
public String getFilePath() {
return this.filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getSourceUrl() {
return this.sourceUrl;
}
public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl;
}
public Documentation(DocumentationType type, String name) {
this.type = type;
this.name = name;
}
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o != null && this.getClass() == o.getClass()) {
Documentation that = (Documentation)o;
return this.name.equals(that.name) && this.type == that.type;
} else {
return false;
}
}
public DocumentationType getType() {
return this.type;
}
public String getName() {
return this.name;
}
public String getSummary() {
return this.summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public DocumentVisibility getVisibility() {
return this.visibility;
}
public void setVisibility(DocumentVisibility visibility) {
this.visibility = visibility;
}
public DocumentSourceType getSourceType() {
return this.sourceType;
}
public void setSourceType(DocumentSourceType sourceType) {
this.sourceType = sourceType;
}
public int hashCode() {
int result = this.type.hashCode();
result = 31 * result + this.name.hashCode();
return result;
}
public Date getLastUpdated() {
return this.lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return documentId;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public Date getCreatedDate() {
return this.createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public static enum DocumentVisibility {
OWNER_ONLY("owner_only"),
PRIVATE("private"),
API_LEVEL("api_level");
private String visibility;
private DocumentVisibility(String visibility) {
this.visibility = visibility;
}
}
public static enum DocumentSourceType {
INLINE("In line"),
MARKDOWN("Markdown"),
URL("URL"),
FILE("File");
private String type;
private DocumentSourceType(String type) {
this.type = type;
}
}
public static enum DocumentationType {
HOWTO("How To"),
SAMPLES("Samples"),
PUBLIC_FORUM("Public Forum"),
SUPPORT_FORUM("Support Forum"),
API_MESSAGE_FORMAT("API Message Format"),
SWAGGER_DOC("Swagger API Definition"),
OTHER("Other");
private String type;
private DocumentationType(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
public class Mediation {
private String uuid;
private String name;
private String type;
private String config;
private boolean isGlobal;
public Mediation(){}
public void setUuid(String id){
this.uuid=id;
}
public String getUuid(){return uuid;}
public void setName(String name){this.name=name;}
public String getName(){return name;}
public void setType(String mType){this.type=mType;}
public String getType(){return type;}
public void setConfig(String mConfig){this.config=mConfig;}
public String getConfig(){return config;}
public boolean isGlobal() {
return isGlobal;
}
public void setGlobal(boolean isGlobal) {
this.isGlobal = isGlobal;
}
}

@ -1,12 +1,12 @@
/*
* Copyright (c) 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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
* 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

@ -0,0 +1,100 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import java.io.Serializable;
import java.util.Objects;
public class Scope implements Serializable{
private static final long serialVersionUID = 1L;
String key;
String name;
String roles;
String description;
String id;
int usageCount;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getUsageCount() {
return usageCount;
}
public void setUsageCount(int usageCount) {
this.usageCount = usageCount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Scope scope = (Scope) o;
if (id != null ? !id.equals(scope.id) : scope.id != null) return false;
if (!key.equals(scope.key)) return false;
if (!name.equals(scope.name)) return false;
if (roles != null ? !roles.equals(scope.roles) : scope.roles != null) return false;
return description != null ? description.equals(scope.description) : scope.description == null;
}
@Override
public int hashCode() {
return Objects.hash(key, name, roles, description, id);
}
}

@ -0,0 +1,440 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo;
import org.json.simple.JSONValue;
import org.wso2.carbon.apimgt.api.dto.ConditionGroupDTO;
import org.wso2.carbon.apimgt.api.model.APIProductIdentifier;
import org.wso2.carbon.apimgt.api.model.Scope;
import org.wso2.carbon.apimgt.api.model.policy.PolicyConstants;
import java.io.Serializable;
import java.util.*;
public class URITemplate implements Serializable{
private static final long serialVersionUID = 1L;
private String uriTemplate;
private String resourceURI;
private String resourceSandboxURI;
private String httpVerb;
private String authType;
private LinkedHashSet<String> httpVerbs = new LinkedHashSet<String>();
private List<String> authTypes = new ArrayList<String>();
private List<String> throttlingConditions = new ArrayList<String>();
private String applicableLevel;
private String throttlingTier;
private List<String> throttlingTiers = new ArrayList<String>();
private org.wso2.carbon.apimgt.api.model.Scope scope;
private String mediationScript;
private List<org.wso2.carbon.apimgt.api.model.Scope> scopes = new ArrayList<org.wso2.carbon.apimgt.api.model.Scope>();
private Map<String, String> mediationScripts = new HashMap<String, String>();
private ConditionGroupDTO[] conditionGroups;
private int id;
private Set<APIProductIdentifier> usedByProducts = new HashSet<>();
private String amznResourceName;
private int amznResourceTimeout;
public ConditionGroupDTO[] getConditionGroups() {
return conditionGroups;
}
public void setConditionGroups(ConditionGroupDTO[] conditionGroups) {
this.conditionGroups = conditionGroups;
}
public String getMediationScript() {
return mediationScript;
}
public List<String> getThrottlingConditions() {
return throttlingConditions;
}
public void setThrottlingConditions(List<String> throttlingConditions) {
this.throttlingConditions = throttlingConditions;
}
public void setMediationScript(String mediationScript) {
this.mediationScript = mediationScript;
}
/**
* Set mediation script for a given http method
* @param method http method name
* @param mediationScript mediation script content
*/
public void setMediationScripts(String method, String mediationScript){
if (mediationScript != null && !mediationScript.trim().equals("") && !mediationScript.trim().equals("null")){
mediationScripts.put(method, mediationScript);
}
}
/**
* Generating the script by aggregating scripts of each http method to form a single script in to be
* used when generating synapse configuration file.
*
* @return aggregated script in the following format,
* if (http-method = 'GET'){
* //script for GET
* }
* ....
* ....
* if (http-method = 'POST'){
* //script for POST
* }
*/
public String getAggregatedMediationScript(){
if (mediationScripts.isEmpty()){
return "null";
}else if (mediationScripts.size() == 1 && httpVerbs.size() == 1){
return mediationScript;
}else{
StringBuilder aggregatedScript = new StringBuilder();
for (Map.Entry<String, String> entry : mediationScripts.entrySet()){
String httpMethod = entry.getKey();
String mediationScript = entry.getValue();
aggregatedScript.append("if (mc.getProperty('REST_METHOD') == '").append(httpMethod).append("'){");
aggregatedScript.append(mediationScript);
aggregatedScript.append("}");
}
return aggregatedScript.toString();
}
}
public String getThrottlingTier() {
return throttlingTier;
}
public void setThrottlingTier(String throttlingTier) {
this.throttlingTier = throttlingTier;
}
public List<String> getThrottlingTiers(){
return throttlingTiers;
}
public void setThrottlingTiers(List<String> throttlingTiers) {
this.throttlingTiers = throttlingTiers;
}
public String getHTTPVerb() {
return httpVerb;
}
public void setHTTPVerb(String httpVerb) {
this.httpVerb = httpVerb;
}
public String getAuthType() {
return authType;
}
public void setAuthType(String authType) {
this.authType = authType;
}
public String getResourceURI() {
return resourceURI;
}
public void setResourceURI(String resourceURI) {
this.resourceURI = resourceURI;
}
public boolean isResourceURIExist(){
return this.resourceURI != null;
}
public String getResourceSandboxURI() {
return resourceSandboxURI;
}
public void setResourceSandboxURI(String resourceSandboxURI) {
this.resourceSandboxURI = resourceSandboxURI;
}
public boolean isResourceSandboxURIExist(){
return this.resourceSandboxURI != null;
}
public String getUriTemplate() {
return uriTemplate;
}
public void setUriTemplate(String template) {
this.uriTemplate = template;
}
public void setHttpVerbs(String httpVerb) {
httpVerbs.add(httpVerb);
}
public LinkedHashSet<String> getHttpVerbs() {
return httpVerbs;
}
public void setAuthTypes(String authType) {
authTypes.add(authType);
}
public String getAuthTypes() {
return authType;
}
public String getMethodsAsString() {
StringBuilder stringBuilder = new StringBuilder();
for (String method : httpVerbs) {
stringBuilder.append(method).append(" ");
}
return stringBuilder.toString().trim();
}
public String getAuthTypeAsString() {
StringBuilder stringBuilder = new StringBuilder();
for (String authType : authTypes) {
stringBuilder.append(authType).append(" ");
}
return stringBuilder.toString().trim();
}
public String getThrottlingConditionsAsString() {
StringBuilder stringBuilder = new StringBuilder();
for (String authType : throttlingConditions) {
stringBuilder.append(authType).append(" ");
}
return stringBuilder.toString().trim();
}
public void setThrottlingTiers(String tier) {
throttlingTiers.add(tier);
}
public String getThrottlingTiersAsString() {
StringBuilder stringBuilder = new StringBuilder();
for (String tier : throttlingTiers) {
if (tier.contains(PolicyConstants.THROTTLING_TIER_CONTENT_AWARE_SEPERATOR)) {
stringBuilder.append(tier.substring(0,
tier.indexOf(PolicyConstants.THROTTLING_TIER_CONTENT_AWARE_SEPERATOR)).trim()).append(" ");
} else {
stringBuilder.append(tier.trim()).append(" ");
}
}
return stringBuilder.toString().trim();
}
public boolean checkContentAwareFromThrottlingTiers() {
// use the content aware property appended to throttling tiers
if (!throttlingTiers.isEmpty()) {
String throttlingTierWithContentAware = throttlingTiers.get(0);
if (throttlingTierWithContentAware != null &&
throttlingTierWithContentAware.contains(PolicyConstants.THROTTLING_TIER_CONTENT_AWARE_SEPERATOR)) {
String[] splitThrottlingTiers =
throttlingTierWithContentAware.split(PolicyConstants.THROTTLING_TIER_CONTENT_AWARE_SEPERATOR);
return Boolean.valueOf(splitThrottlingTiers[splitThrottlingTiers.length - 1]);
}
}
return false;
}
public org.wso2.carbon.apimgt.api.model.Scope getScope() {
return scope;
}
public List<org.wso2.carbon.apimgt.api.model.Scope> getScopes() {
return scopes;
}
public void setScope(org.wso2.carbon.apimgt.api.model.Scope scope) {
this.scope = scope;
}
public void setScopes(org.wso2.carbon.apimgt.api.model.Scope scope){
this.scopes.add(scope);
}
public String getResourceMap(){
Map verbs = new LinkedHashMap();
int i = 0;
for (String method : httpVerbs) {
Map verb = new LinkedHashMap();
verb.put("auth_type",authTypes.get(i));
verb.put("throttling_tier",throttlingTiers.get(i));
//Following parameter is not required as it not need to reflect UI level. If need please enable it.
// /verb.put("throttling_conditions", throttlingConditions.get(i));
try{
org.wso2.carbon.apimgt.api.model.Scope tmpScope = scopes.get(i);
if(tmpScope != null){
verb.put("scope",tmpScope.getKey());
}
}catch(IndexOutOfBoundsException e){
//todo need to rewrite to prevent this type of exceptions
}
verbs.put(method,verb);
i++;
}
//todo this is a hack to make key validation service stub from braking need to rewrite.
return JSONValue.toJSONString(verbs);
}
public String getApplicableLevel() {
return applicableLevel;
}
public void setApplicableLevel(String applicableLevel) {
this.applicableLevel = applicableLevel;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
URITemplate that = (URITemplate) o;
if (!uriTemplate.equals(that.uriTemplate)) {
return false;
}
if (resourceURI != null ? !resourceURI.equals(that.resourceURI) : that.resourceURI != null) {
return false;
}
if (resourceSandboxURI != null ? !resourceSandboxURI.equals(that.resourceSandboxURI) : that
.resourceSandboxURI != null) {
return false;
}
if (!httpVerb.equals(that.httpVerb)) {
return false;
}
if (!authType.equals(that.authType)) {
return false;
}
if (!httpVerbs.equals(that.httpVerbs)) {
return false;
}
if (!authTypes.equals(that.authTypes)) {
return false;
}
if (throttlingConditions != null ? !throttlingConditions.equals(that.throttlingConditions) : that
.throttlingConditions != null) {
return false;
}
if (applicableLevel != null ? !applicableLevel.equals(that.applicableLevel) : that.applicableLevel != null) {
return false;
}
if (!throttlingTier.equals(that.throttlingTier)) {
return false;
}
if (!throttlingTiers.equals(that.throttlingTiers)) {
return false;
}
if (scope != null ? !scope.equals(that.scope) : that.scope != null) {
return false;
}
if (mediationScript != null ? !mediationScript.equals(that.mediationScript) : that.mediationScript != null) {
return false;
}
if (scopes != null ? !scopes.equals(that.scopes) : that.scopes != null) {
return false;
}
if (mediationScripts != null ? !mediationScripts.equals(that.mediationScripts) : that.mediationScripts !=
null) {
return false;
}
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(conditionGroups, that.conditionGroups);
}
@Override
public int hashCode() {
int result = uriTemplate.hashCode();
result = 31 * result + (resourceURI != null ? resourceURI.hashCode() : 0);
result = 31 * result + (resourceSandboxURI != null ? resourceSandboxURI.hashCode() : 0);
result = 31 * result + (httpVerb != null ? httpVerb.hashCode() : 0);
result = 31 * result + (authType != null ? authType.hashCode() : 0);
result = 31 * result + (httpVerbs != null ? httpVerbs.hashCode() : 0);
result = 31 * result + (authTypes != null ? authTypes.hashCode() : 0);
result = 31 * result + (throttlingConditions != null ? throttlingConditions.hashCode() : 0);
result = 31 * result + (applicableLevel != null ? applicableLevel.hashCode() : 0);
result = 31 * result + (throttlingTier != null ? throttlingTier.hashCode() : 0);
result = 31 * result + (throttlingTiers != null ? throttlingTiers.hashCode() : 0);
result = 31 * result + (scope != null ? scope.hashCode() : 0);
result = 31 * result + (mediationScript != null ? mediationScript.hashCode() : 0);
result = 31 * result + (scopes != null ? scopes.hashCode() : 0);
result = 31 * result + (mediationScripts != null ? mediationScripts.hashCode() : 0);
result = 31 * result + Arrays.hashCode(conditionGroups);
return result;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<org.wso2.carbon.apimgt.api.model.Scope> retrieveAllScopes() {
return this.scopes;
}
public void addAllScopes(List<Scope> scopes) {
this.scopes = scopes;
}
public Set<APIProductIdentifier> retrieveUsedByProducts() {
return usedByProducts;
}
public void addUsedByProduct(APIProductIdentifier usedByProduct) {
usedByProducts.add(usedByProduct);
}
public void setAmznResourceName(String amznResourceName) {
this.amznResourceName = amznResourceName;
}
public String getAmznResourceName() {
return amznResourceName;
}
public void setAmznResourceTimeout(int amznResourceTimeout) {
this.amznResourceTimeout = amznResourceTimeout;
}
public int getAmznResourceTimeout() {
return amznResourceTimeout;
}
}

@ -21,7 +21,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

@ -21,7 +21,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>apimgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -23,26 +23,29 @@ import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServi
import io.entgra.device.mgt.core.apimgt.extension.rest.api.PublisherRESTAPIServicesImpl;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.constants.Constants;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIApplicationKey;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Scope;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Mediation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevision;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIRevisionDeployment;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.CORSConfiguration;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.AccessTokenInfo;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.APIServicesException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.BadRequestException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.exceptions.UnexpectedResponseException;
import io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.APIInfo;
import io.entgra.device.mgt.core.apimgt.webapp.publisher.config.WebappPublisherConfig;
import io.entgra.device.mgt.core.apimgt.webapp.publisher.dto.ApiScope;
import io.entgra.device.mgt.core.apimgt.webapp.publisher.dto.ApiUriTemplate;
import io.entgra.device.mgt.core.apimgt.webapp.publisher.exception.APIManagerPublisherException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.apimgt.api.model.Documentation;
import org.wso2.carbon.apimgt.api.model.DocumentationType;
import org.json.JSONArray;
import org.json.JSONObject;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.api.APIProvider;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
import org.wso2.carbon.apimgt.api.model.APIRevision;
import org.wso2.carbon.apimgt.api.model.APIRevisionDeployment;
import org.wso2.carbon.apimgt.api.model.CORSConfiguration;
import org.wso2.carbon.apimgt.api.model.Mediation;
import org.wso2.carbon.apimgt.api.model.Scope;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerFactory;
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
@ -167,6 +170,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
break;
}
}
String apiUuid = apiIdentifier.getUUID();
if (!apiFound) {
// add new scopes as shared scopes
for (ApiScope apiScope : apiConfig.getScopes()) {
@ -182,9 +186,10 @@ public class APIPublisherServiceImpl implements APIPublisherService {
}
APIInfo api = getAPI(apiConfig, true);
JSONObject createdAPI = publisherRESTAPIServices.addAPI(apiApplicationKey, accessTokenInfo, api);
apiUuid = createdAPI.getString("id");
if (apiConfig.getEndpointType() != null && "WS".equals(apiConfig.getEndpointType())) {
publisherRESTAPIServices.saveAsyncApiDefinition(apiApplicationKey, accessTokenInfo,
createdAPI.getString("id"), apiConfig.getAsyncApiDefinition());
apiUuid, apiConfig.getAsyncApiDefinition());
}
if (CREATED_STATUS.equals(createdAPI.getString("lifeCycleStatus"))) {
// if endpoint type "dynamic" and then add in sequence
@ -195,13 +200,13 @@ public class APIPublisherServiceImpl implements APIPublisherService {
mediation.setType("in");
mediation.setGlobal(false);
publisherRESTAPIServices.addApiSpecificMediationPolicy(apiApplicationKey,
accessTokenInfo, createdAPI.getString("id"), mediation);
accessTokenInfo, apiUuid, mediation);
}
publisherRESTAPIServices.changeLifeCycleStatus(apiApplicationKey, accessTokenInfo,
createdAPI.getString("id"), PUBLISH_ACTION);
apiUuid, PUBLISH_ACTION);
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(createdAPI.getString("id"));
apiRevision.setApiUUID(apiUuid);
apiRevision.setDescription("Initial Revision");
String apiRevisionId = publisherRESTAPIServices.addAPIRevision(apiApplicationKey,
accessTokenInfo, apiRevision).getString("id");
@ -214,7 +219,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
List<APIRevisionDeployment> apiRevisionDeploymentList = new ArrayList<>();
apiRevisionDeploymentList.add(apiRevisionDeployment);
publisherRESTAPIServices.deployAPIRevision(apiApplicationKey, accessTokenInfo,
createdAPI.getString("id"), apiRevisionId, apiRevisionDeploymentList);
apiUuid, apiRevisionId, apiRevisionDeploymentList);
}
} else {
if (WebappPublisherConfig.getInstance().isEnabledUpdateApi()) {
@ -257,7 +262,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
// Get existing API
JSONObject existingAPI = publisherRESTAPIServices.getApi(apiApplicationKey, accessTokenInfo,
apiIdentifier);
apiUuid);
if (scopesToMoveAsSharedScopes.size() > 0) {
// update API to remove local scopes
APIInfo api = getAPI(apiConfig, false);
@ -274,15 +279,15 @@ public class APIPublisherServiceImpl implements APIPublisherService {
}
}
existingAPI = publisherRESTAPIServices.getApi(apiApplicationKey, accessTokenInfo, apiIdentifier);
existingAPI = publisherRESTAPIServices.getApi(apiApplicationKey, accessTokenInfo, apiUuid);
APIInfo api = getAPI(apiConfig, true);
api.setLastUpdatedTime(existingAPI.getString("lifeCycleStatus"));
api.setId(existingAPI.getString("id"));
api.setId(apiUuid);
publisherRESTAPIServices.updateApi(apiApplicationKey, accessTokenInfo, api);
if (apiConfig.getEndpointType() != null && "WS".equals(apiConfig.getEndpointType())) {
publisherRESTAPIServices.saveAsyncApiDefinition(apiApplicationKey, accessTokenInfo,
existingAPI.getString("id"), apiConfig.getAsyncApiDefinition());
apiUuid, apiConfig.getAsyncApiDefinition());
}
// if endpoint type "dynamic" and then add /update in sequence
@ -293,48 +298,55 @@ public class APIPublisherServiceImpl implements APIPublisherService {
mediation.setType("in");
mediation.setGlobal(false);
List<Mediation> mediationList = (List) publisherRESTAPIServices
JSONArray mediationList = (JSONArray) publisherRESTAPIServices
.getAllApiSpecificMediationPolicies(apiApplicationKey, accessTokenInfo,
apiIdentifier).get("list");
apiUuid).get("list");
boolean isMediationPolicyFound = false;
for (Mediation m : mediationList) {
if (apiConfig.getInSequenceName().equals(m.getName())) {
m.setConfig(apiConfig.getInSequenceConfig());
publisherRESTAPIServices.
updateApiSpecificMediationPolicyContent(apiApplicationKey,
accessTokenInfo, existingAPI.getString("id"), m);
for (int i = 0; i < mediationList.length(); i++) {
JSONObject mediationObj = mediationList.getJSONObject(i);
if (apiConfig.getInSequenceName().equals(mediationObj.getString("name"))) {
mediation.setUuid(mediationObj.getString("id"));
publisherRESTAPIServices.deleteApiSpecificMediationPolicy(apiApplicationKey,
accessTokenInfo, apiUuid, mediation);
publisherRESTAPIServices.addApiSpecificMediationPolicy(apiApplicationKey,
accessTokenInfo, apiUuid, mediation);
isMediationPolicyFound = true;
break;
}
}
if (!isMediationPolicyFound) {
publisherRESTAPIServices.addApiSpecificMediationPolicy(apiApplicationKey,
accessTokenInfo, existingAPI.getString("id"), mediation);
accessTokenInfo, apiUuid, mediation);
}
}
// This will retrieve the deployed revision
JSONArray revisionDeploymentList = (JSONArray) publisherRESTAPIServices.getAPIRevisions(apiApplicationKey,
accessTokenInfo, existingAPI.getString("id"), true).get("list");
// This will retrieve the un deployed revision list
JSONArray undeployedRevisionList = (JSONArray) publisherRESTAPIServices.getAPIRevisions(apiApplicationKey,
accessTokenInfo, existingAPI.getString("id"), false).get("list");
int apiRevisionCount = (int) publisherRESTAPIServices.getAPIRevisions(apiApplicationKey,
accessTokenInfo, existingAPI.getString("id"), null).get("count");
accessTokenInfo, apiUuid, null).get("count");
if (apiRevisionCount >= 5) {
JSONObject latestRevisionDeployment = revisionDeploymentList.getJSONObject(0);
JSONObject earliestUndeployRevision = undeployedRevisionList.getJSONObject(0);
publisherRESTAPIServices.undeployAPIRevisionDeployment(apiApplicationKey,
accessTokenInfo, latestRevisionDeployment, existingAPI.getString("id"));
publisherRESTAPIServices.deleteAPIRevision(apiApplicationKey, accessTokenInfo,
earliestUndeployRevision, existingAPI.getString("id"));
// This will retrieve the deployed revision
JSONArray revisionDeploymentList = (JSONArray) publisherRESTAPIServices.getAPIRevisions(
apiApplicationKey, accessTokenInfo, apiUuid,
true).get("list");
if (revisionDeploymentList.length() > 0) {
JSONObject latestRevisionDeployment = revisionDeploymentList.getJSONObject(0);
publisherRESTAPIServices.undeployAPIRevisionDeployment(apiApplicationKey,
accessTokenInfo, latestRevisionDeployment, apiUuid);
}
// This will retrieve the un deployed revision list
JSONArray undeployedRevisionList = (JSONArray) publisherRESTAPIServices.getAPIRevisions(
apiApplicationKey, accessTokenInfo, apiUuid,
false).get("list");
if (undeployedRevisionList.length() > 0) {
JSONObject earliestUndeployRevision = undeployedRevisionList.getJSONObject(0);
publisherRESTAPIServices.deleteAPIRevision(apiApplicationKey, accessTokenInfo,
earliestUndeployRevision, apiUuid);
}
}
// create new revision
APIRevision apiRevision = new APIRevision();
apiRevision.setApiUUID(existingAPI.getString("id"));
apiRevision.setApiUUID(apiUuid);
apiRevision.setDescription("Updated Revision");
String apiRevisionId = publisherRESTAPIServices.addAPIRevision(apiApplicationKey,
accessTokenInfo, apiRevision).getString("id");
@ -348,17 +360,15 @@ public class APIPublisherServiceImpl implements APIPublisherService {
apiRevisionDeploymentList.add(apiRevisionDeployment);
publisherRESTAPIServices.deployAPIRevision(apiApplicationKey, accessTokenInfo,
existingAPI.getString("id"), apiRevisionId, apiRevisionDeploymentList);
apiUuid, apiRevisionId, apiRevisionDeploymentList);
if (CREATED_STATUS.equals(existingAPI.getString("lifeCycleStatus"))) {
publisherRESTAPIServices.changeLifeCycleStatus(apiApplicationKey,accessTokenInfo,
existingAPI.getString("id"), PUBLISH_ACTION);
apiUuid, PUBLISH_ACTION);
}
}
}
if (apiConfig.getApiDocumentationSourceFile() != null) {
APIInfo api = getAPI(apiConfig, true);
if (apiUuid != null && apiConfig.getApiDocumentationSourceFile() != null) {
String fileName =
CarbonUtils.getCarbonHome() + File.separator + "repository" +
File.separator + "resources" + File.separator + "api-docs" + File.separator +
@ -376,7 +386,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
br.close();
String docContent = stringBuilder.toString();
Documentation apiDocumentation = new Documentation(DocumentationType.HOWTO, apiConfig.getApiDocumentationName());
Documentation apiDocumentation = new Documentation(Documentation.DocumentationType.HOWTO, apiConfig.getApiDocumentationName());
apiDocumentation.setVisibility(Documentation.DocumentVisibility.API_LEVEL);
apiDocumentation.setSourceType(Documentation.DocumentSourceType.MARKDOWN);
apiDocumentation.setCreatedDate(new Date());
@ -385,25 +395,27 @@ public class APIPublisherServiceImpl implements APIPublisherService {
apiDocumentation.setOtherTypeName(null);
JSONArray documentList = (JSONArray) publisherRESTAPIServices.getDocumentations(apiApplicationKey,
accessTokenInfo, api.getId()).get("list");
accessTokenInfo, apiUuid).get("list");
if (documentList.length() > 0) {
for (int i = 0; i < documentList.length(); i++) {
JSONObject existingDoc = documentList.getJSONObject(i);
if (existingDoc.getString("name").equals(apiConfig.getApiDocumentationName())
&& existingDoc.getString("type").equals(DocumentationType.HOWTO)) {
&& existingDoc.getString("type").equals(Documentation.DocumentationType.HOWTO.name())) {
publisherRESTAPIServices.deleteDocumentations(apiApplicationKey, accessTokenInfo,
api.getId(), existingDoc.getString("documentId"));
apiUuid, existingDoc.getString("documentId"));
}
}
} else {
log.info("There is no any existing api documentation.");
}
Documentation createdDoc = publisherRESTAPIServices.addDocumentation(apiApplicationKey, accessTokenInfo,
api.getId(), apiDocumentation);
publisherRESTAPIServices.addDocumentationContent(apiApplicationKey, accessTokenInfo, api,
createdDoc.getId(), docContent);
io.entgra.device.mgt.core.apimgt.extension.rest.api.dto.APIInfo.Documentation createdDoc = publisherRESTAPIServices.addDocumentation(apiApplicationKey, accessTokenInfo,
apiUuid, apiDocumentation);
publisherRESTAPIServices.addDocumentationContent(apiApplicationKey, accessTokenInfo, apiUuid,
createdDoc.getDocumentId(), docContent);
}
} catch (APIManagementException | IOException | APIServicesException |
BadRequestException | UnexpectedResponseException e) {
@ -572,45 +584,49 @@ public class APIPublisherServiceImpl implements APIPublisherService {
}
apiInfo.setPolicies(availableTiers);
if (config.getEndpointType() == null) {
List<JSONObject> operations = new ArrayList();
List<JSONObject> scopeSet = new ArrayList();
Iterator<ApiUriTemplate> iterator;
for (iterator = config.getUriTemplates().iterator(); iterator.hasNext(); ) {
ApiUriTemplate apiUriTemplate = iterator.next();
JSONObject operation = new JSONObject();
operation.put("target", apiUriTemplate.getUriTemplate());
operation.put("verb", apiUriTemplate.getHttpVerb());
operation.put("authType", apiUriTemplate.getAuthType());
operation.put("throttlingPolicy", UNLIMITED_TIER);
if (includeScopes) {
if (apiUriTemplate.getScope() != null) {
String scopeString = "{\n" +
" \"scope\": {\n" +
" \"id\": null,\n" +
" \"name\": \"" + apiUriTemplate.getScope().getKey() + "\",\n" +
" \"displayName\": \"" + apiUriTemplate.getScope().getName() + "\",\n" +
" \"description\": \"" + apiUriTemplate.getScope().getDescription() + "\",\n" +
" \"bindings\": [\n" +
" \"" + apiUriTemplate.getScope().getRoles() + "\"\n" +
" ],\n" +
" \"usageCount\": null\n" +
" },\n" +
" \"shared\": true\n" +
" }";
JSONObject scope = new JSONObject(scopeString);
scopeSet.add(scope);
Set<String> scopes = new HashSet<>();
scopes.add(apiUriTemplate.getScope().getKey());
operation.put("scopes", scopes);
}
if (config.getEndpointType() != null && "WS".equals(config.getEndpointType())) {
apiInfo.setAsyncApiDefinition(config.getAsyncApiDefinition());
}
//set operations and scopes
List<JSONObject> operations = new ArrayList();
List<JSONObject> scopeSet = new ArrayList();
Iterator<ApiUriTemplate> iterator;
for (iterator = config.getUriTemplates().iterator(); iterator.hasNext(); ) {
ApiUriTemplate apiUriTemplate = iterator.next();
JSONObject operation = new JSONObject();
operation.put("target", apiUriTemplate.getUriTemplate());
operation.put("verb", apiUriTemplate.getHttpVerb());
operation.put("authType", apiUriTemplate.getAuthType());
operation.put("throttlingPolicy", UNLIMITED_TIER);
operation.put("uriMapping", apiUriTemplate.getUriMapping());
if (includeScopes) {
if (apiUriTemplate.getScope() != null) {
String scopeString = "{\n" +
" \"scope\": {\n" +
" \"id\": null,\n" +
" \"name\": \"" + apiUriTemplate.getScope().getKey() + "\",\n" +
" \"displayName\": \"" + apiUriTemplate.getScope().getName() + "\",\n" +
" \"description\": \"" + apiUriTemplate.getScope().getDescription() + "\",\n" +
" \"bindings\": [\n" +
" \"" + apiUriTemplate.getScope().getRoles() + "\"\n" +
" ],\n" +
" \"usageCount\": null\n" +
" },\n" +
" \"shared\": true\n" +
" }";
JSONObject scope = new JSONObject(scopeString);
scopeSet.add(scope);
Set<String> scopes = new HashSet<>();
scopes.add(apiUriTemplate.getScope().getKey());
operation.put("scopes", scopes);
}
operations.add(operation);
}
apiInfo.setScopes(scopeSet);
apiInfo.setOperations(operations);
operations.add(operation);
}
apiInfo.setScopes(scopeSet);
apiInfo.setOperations(operations);
if (config.isSharedWithAllTenants()) {
apiInfo.setSubscriptionAvailability(SUBSCRIPTION_TO_ALL_TENANTS);
@ -640,7 +656,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
if (config.getEndpointType() != null && "dynamic".equals(config.getEndpointType())) {
endpointConfig = "{\n" +
" \"endpoint_type\": \"http\",\n" +
" \"endpoint_type\": \"default\",\n" +
" \"sandbox_endpoints\": {\n" +
" \"url\": \" default \"\n" +
" },\n" +
@ -649,8 +665,7 @@ public class APIPublisherServiceImpl implements APIPublisherService {
" }\n" +
" }";
endPointConfig = new JSONObject(endpointConfig);
//TODO: Will be used in dynamic endpoints
// apiInfo.setInSequence(config.getInSequenceName());
apiInfo.setInSequence(config.getInSequenceName());
}
// if ws endpoint
@ -702,42 +717,5 @@ public class APIPublisherServiceImpl implements APIPublisherService {
apiInfo.setServiceInfo(null);
return apiInfo;
//TODO: Will be used in WS or dynamic endpoints
// if (config.getEndpointType() != null && "WS".equals(config.getEndpointType())) {
// api.setAsyncApiDefinition(config.getAsyncApiDefinition());
// AsyncApiParser asyncApiParser = new AsyncApiParser();
// try {
// api.setUriTemplates(asyncApiParser.getURITemplates(config.getAsyncApiDefinition(), true));
// } catch (APIManagementException e) {
//
// }
// api.setWsUriMapping(asyncApiParser.buildWSUriMapping(config.getAsyncApiDefinition()));
// } else {
// api.setSwaggerDefinition(APIPublisherUtil.getSwaggerDefinition(config));
//
// Set<URITemplate> uriTemplates = new HashSet<>();
// Iterator<ApiUriTemplate> iterator;
// for (iterator = config.getUriTemplates().iterator(); iterator.hasNext(); ) {
// ApiUriTemplate apiUriTemplate = iterator.next();
// URITemplate uriTemplate = new URITemplate();
// uriTemplate.setAuthType(apiUriTemplate.getAuthType());
// uriTemplate.setHTTPVerb(apiUriTemplate.getHttpVerb());
// uriTemplate.setResourceURI(apiUriTemplate.getResourceURI());
// uriTemplate.setUriTemplate(apiUriTemplate.getUriTemplate());
// if (includeScopes) {
// Scope scope = new Scope();
// if (apiUriTemplate.getScope() != null) {
// scope.setName(apiUriTemplate.getScope().getName());
// scope.setDescription(apiUriTemplate.getScope().getDescription());
// scope.setKey(apiUriTemplate.getScope().getKey());
// scope.setRoles(apiUriTemplate.getScope().getRoles());
// uriTemplate.setScopes(scope);
// }
// }
// uriTemplates.add(uriTemplate);
// }
// api.setUriTemplates(uriTemplates);
// }
}
}

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>application-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>application-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>certificate-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>certificate-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>certificate-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -847,8 +847,9 @@ public class CertificateGenerator {
X500Name issuerName = new X500Name(subjectDn);
String commonName = certificationRequest.getSubject().getRDNs(BCStyle.CN)[0].getFirst()
.getValue().toString();
X500Name subjectName = new X500Name("O=" + commonName + "O=AndroidDevice,CN=" +
serialNumber);
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
X500Name subjectName = new X500Name("O=" + commonName + " ,CN=" +
serialNumber + ", OU=tenant_" + tenantId);
Date startDate = new Date(System.currentTimeMillis());
Date endDate = new Date(System.currentTimeMillis()
+ TimeUnit.DAYS.toMillis(365 * 100));
@ -869,7 +870,7 @@ public class CertificateGenerator {
io.entgra.device.mgt.core.certificate.mgt.core.bean.Certificate certificate =
new io.entgra.device.mgt.core.certificate.mgt.core.bean.Certificate();
List<io.entgra.device.mgt.core.certificate.mgt.core.bean.Certificate> certificates = new ArrayList<>();
certificate.setTenantId(PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId());
certificate.setTenantId(tenantId);
certificate.setCertificate(issuedCert);
certificates.add(certificate);
saveCertInKeyStore(certificates);

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt-extensions</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -533,7 +533,14 @@ public interface GroupManagementService {
defaultValue = "1")
@DefaultValue("1")
@QueryParam("depth")
int depth);
int depth,
@ApiParam(
name = "allowed",
value = "Whether to return allowed group",
defaultValue = "false")
@QueryParam("allowed")
@DefaultValue("false")
boolean allowed);
@Path("/name/{groupName}")
@GET

@ -176,10 +176,11 @@ public class GroupManagementServiceImpl implements GroupManagementService {
}
@Override
public Response getGroup(int groupId, boolean requireGroupProps, int depth) {
public Response getGroup(int groupId, boolean requireGroupProps, int depth, boolean allowed) {
try {
GroupManagementProviderService service = DeviceMgtAPIUtils.getGroupManagementProviderService();
DeviceGroup deviceGroup = service.getGroup(groupId, requireGroupProps, depth);
DeviceGroup deviceGroup = allowed ? service.getUserOwnGroup(groupId, requireGroupProps, depth):
service.getGroup(groupId, requireGroupProps, depth);
if (deviceGroup != null) {
return Response.status(Response.Status.OK).entity(deviceGroup).build();
} else {

@ -126,11 +126,14 @@ public class RoleManagementServiceImpl implements RoleManagementService {
List<String> visibleRoles;
RoleList visibleRoleList = new RoleList();
try {
metadata = DeviceMgtAPIUtils.getMetadataManagementService().retrieveMetadata(metaKey);
String metaValue = metadata.getMetaValue();
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(metaValue);
boolean decision = (boolean) jsonObject.get(Constants.IS_USER_ABLE_TO_VIEW_ALL_ROLES);
boolean decision = false;
if(DeviceMgtAPIUtils.getMetadataManagementService().retrieveMetadata(metaKey) != null){
metadata = DeviceMgtAPIUtils.getMetadataManagementService().retrieveMetadata(metaKey);
String metaValue = metadata.getMetaValue();
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(metaValue);
decision = (boolean) jsonObject.get(Constants.IS_USER_ABLE_TO_VIEW_ALL_ROLES);
}
if (decision) {
if (userStore == null || "".equals(userStore)){
userStore = PRIMARY_USER_STORE;

@ -176,13 +176,13 @@ public class GroupManagementServiceImplTest {
Mockito.doReturn(new DeviceGroup()).when(groupManagementProviderService).getGroup(1, false, 1);
Mockito.doReturn(null).when(groupManagementProviderService).getGroup(2, false, 1);
Mockito.doThrow(new GroupManagementException()).when(groupManagementProviderService).getGroup(3, false, 1);
Response response = groupManagementService.getGroup(1, false, 1);
Response response = groupManagementService.getGroup(1, false, 1, false);
Assert.assertEquals(response.getStatus(), Response.Status.OK.getStatusCode(),
"getGroup request failed for a request with valid parameters");
response = groupManagementService.getGroup(2, false, 1);
response = groupManagementService.getGroup(2, false, 1, false);
Assert.assertEquals(response.getStatus(), Response.Status.NOT_FOUND.getStatusCode(),
"getGroup request returned a group for a non-existing group");
response = groupManagementService.getGroup(3, false, 1);
response = groupManagementService.getGroup(3, false, 1, false);
Assert.assertEquals(response.getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"getGroup request returned a group for a in-valid request");
}

@ -21,7 +21,7 @@
<parent>
<artifactId>device-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>device-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -1353,7 +1353,7 @@ public abstract class AbstractGroupDAOImpl implements GroupDAO {
+ "FROM DM_DEVICE d, "
+ "(SELECT dgm.DEVICE_ID "
+ "FROM DM_DEVICE_GROUP_MAP dgm "
+ "WHERE dgm.GROUP_ID = (SELECT ID FROM DM_GROUP WHERE GROUP_NAME = ? )) dgm1 "
+ "WHERE dgm.GROUP_ID = (SELECT ID FROM DM_GROUP WHERE GROUP_NAME = ? AND TENANT_ID = ?)) dgm1 "
+ "WHERE d.ID = dgm1.DEVICE_ID AND d.TENANT_ID = ?) gd, DM_DEVICE_TYPE t "
+ "WHERE gd.DEVICE_TYPE_ID = t.ID) d1 "
+ "WHERE d1.DEVICE_ID = e.DEVICE_ID AND TENANT_ID = ?";
@ -1362,6 +1362,7 @@ public abstract class AbstractGroupDAOImpl implements GroupDAO {
stmt.setString(1, groupName);
stmt.setInt(2, tenantId);
stmt.setInt(3, tenantId);
stmt.setInt(4, tenantId);
try (ResultSet rs = stmt.executeQuery()) {
devices = new ArrayList<>();
while (rs.next()) {

@ -351,4 +351,6 @@ public interface GroupManagementProviderService {
* @throws GroupManagementException
*/
DeviceTypesOfGroups getDeviceTypesOfGroups(List<String> identifiers) throws GroupManagementException;
DeviceGroup getUserOwnGroup(int groupId, boolean requireGroupProps, int depth) throws GroupManagementException;
}

@ -570,20 +570,28 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
String parentPath;
List<DeviceGroup> childrenGroups;
if (StringUtils.isBlank(username)) {
GroupManagementDAOFactory.openConnection();
rootGroups = groupDAO.getGroups(request, tenantId);
for (DeviceGroup rootGroup : rootGroups) {
parentPath = DeviceManagerUtil.createParentPath(rootGroup);
childrenGroups = groupDAO.getChildrenGroups(parentPath, tenantId);
createGroupWithChildren(
rootGroup, childrenGroups, requireGroupProps, tenantId, request.getDepth(), 0);
if (requireGroupProps) {
populateGroupProperties(rootGroup, tenantId);
try {
GroupManagementDAOFactory.openConnection();
rootGroups = groupDAO.getGroups(request, tenantId);
for (DeviceGroup rootGroup : rootGroups) {
parentPath = DeviceManagerUtil.createParentPath(rootGroup);
childrenGroups = groupDAO.getChildrenGroups(parentPath, tenantId);
createGroupWithChildren(
rootGroup, childrenGroups, requireGroupProps, tenantId, request.getDepth(), 0);
if (requireGroupProps) {
populateGroupProperties(rootGroup, tenantId);
}
}
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source to retrieve all groups "
+ "with hierarchy";
log.error(msg, e);
throw new GroupManagementException(msg, e);
} finally {
GroupManagementDAOFactory.closeConnection();
}
} else {
List<Integer> allDeviceGroupIdsOfUser = getGroupIds(username);
GroupManagementDAOFactory.openConnection();
rootGroups = this.getGroups(allDeviceGroupIdsOfUser, tenantId);
if (requireGroupProps) {
for (DeviceGroup rootGroup : rootGroups) {
@ -591,19 +599,12 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
}
}
}
} catch (GroupManagementDAOException e) {
String msg = "Error occurred while retrieving all groups with hierarchy";
log.error(msg, e);
throw new GroupManagementException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source to retrieve all groups "
+ "with hierarchy";
log.error(msg, e);
throw new GroupManagementException(msg, e);
} finally {
GroupManagementDAOFactory.closeConnection();
}
PaginationResult groupResult = new PaginationResult();
groupResult.setData(rootGroups);
if (StringUtils.isBlank(username)) {
@ -616,6 +617,7 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
private List<DeviceGroup> getGroups(List<Integer> groupIds, int tenantId) throws GroupManagementException {
try {
GroupManagementDAOFactory.openConnection();
List<DeviceGroup >groups = groupDAO.getGroups(groupIds, tenantId);
if (groups == null) {
String msg = "Retrieved null when getting groups for group ids " + groupIds.toString();
@ -625,10 +627,17 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
if (groups.isEmpty()) return groups;
groups.sort(Comparator.comparing(DeviceGroup::getGroupId));
return getTree(groups);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source to retrieve all groups "
+ "with hierarchy";
log.error(msg, e);
throw new GroupManagementException(msg, e);
} catch (GroupManagementDAOException ex) {
String msg = "Error occurred while getting groups for group ids " + groupIds.toString();
log.error(msg, ex);
throw new GroupManagementException(msg, ex);
} finally {
GroupManagementDAOFactory.closeConnection();
}
}
@ -636,8 +645,8 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
List<DeviceGroup> tree = new ArrayList<>();
for (DeviceGroup deviceGroup : groups) {
DeviceGroup treeNode = tree.stream().
filter(node -> deviceGroup.getParentPath().
contains(Integer.toString(node.getGroupId()))).
filter(node -> Arrays.stream(deviceGroup.getParentPath().split("/")).
collect(Collectors.toList()).contains(Integer.toString(node.getGroupId()))).
findFirst().orElse(null);
if (treeNode != null) {
if (Objects.equals(treeNode.getParentPath(), deviceGroup.getParentPath())) {
@ -657,6 +666,76 @@ public class GroupManagementProviderServiceImpl implements GroupManagementProvid
return tree;
}
private DeviceGroup findGroupFromTree(List<DeviceGroup> tree, int groupId) {
for (DeviceGroup node: tree) {
if (node.getGroupId() == groupId) return node;
if (node.getChildrenGroups() != null) {
DeviceGroup tempNode = findGroupFromTree(node.getChildrenGroups(), groupId);
if (tempNode != null) {
return tempNode;
}
}
}
return null;
}
private boolean isAdminUser(String username, UserStoreManager userStoreManager)
throws GroupManagementException {
try {
if (!userStoreManager.isExistingUser(username)) {
String msg = "User doesn't exists with given username " + username;
throw new GroupManagementException(msg);
}
String []currentRoles = userStoreManager.getRoleListOfUser(username);
for (String role : currentRoles) {
if (role.equals("admin")) return true;
}
return false;
} catch (UserStoreException e) {
String msg = "Error occurred while requesting user details";
log.error(msg, e);
throw new GroupManagementException(msg, e);
}
}
@Override
public DeviceGroup getUserOwnGroup(int groupId, boolean requireGroupProps, int depth) throws GroupManagementException {
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
String username = ctx.getUsername();
int tenantId = ctx.getTenantId();
try {
UserStoreManager userStoreManager = DeviceManagementDataHolder.getInstance().
getRealmService().getTenantUserRealm(tenantId).getUserStoreManager();
if (isAdminUser(username, userStoreManager)) {
return getGroup(groupId, requireGroupProps);
}
List<Integer> userOwnGroupIds = this.getGroupIds(username);
if (userOwnGroupIds == null) {
String msg = "Retrieved null when getting group ids for user " + username;
log.error(msg);
throw new GroupManagementException(msg);
}
DeviceGroup deviceGroup = findGroupFromTree(
getGroups(userOwnGroupIds, tenantId), groupId);
if (deviceGroup != null && requireGroupProps)
populateGroupProperties(deviceGroup, tenantId);
return deviceGroup;
} catch (UserStoreException e) {
String msg = "Error occurred while getting user store manager service";
log.error(msg, e);
throw new GroupManagementException(msg, e);
} catch (GroupManagementDAOException e) {
String msg = "Error occurred while obtaining group '" + groupId + "'";
log.error(msg, e);
throw new GroupManagementException(msg, e);
}
}
@Override
public List<DeviceGroup> getGroups(String username, boolean requireGroupProps) throws GroupManagementException {
if (username == null || username.isEmpty()) {

@ -66,4 +66,9 @@ public class TestHeartBeatManagementService implements HeartBeatManagementServic
@Override public Map<Integer, ServerContext> getActiveServers() throws HeartBeatManagementException {
return null;
}
@Override
public void notifyClusterFormationChanged(int elapsedTimeInSeconds) throws HeartBeatManagementException {
}
}

@ -22,7 +22,7 @@
<parent>
<artifactId>device-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -23,7 +23,7 @@
<parent>
<artifactId>device-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>heartbeat-management</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,6 +22,7 @@ import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.HeartBeatBeaconC
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.HeartBeatBeaconUtils;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.config.datasource.DataSourceConfig;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.exception.InvalidConfigurationStateException;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.ClusterFormationChangedNotifier;
import org.w3c.dom.Document;
import org.wso2.carbon.utils.CarbonUtils;
@ -29,8 +30,10 @@ import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.util.List;
@XmlRootElement(name = "HeartBeatBeaconConfig")
public class HeartBeatBeaconConfig {
@ -50,6 +53,7 @@ public class HeartBeatBeaconConfig {
private static final String SERVER_UUID_FILE_LOCATION =
CarbonUtils.getCarbonConfigDirPath() + File.separator + "server-credentials.properties";
private List<String> notifiers;
private HeartBeatBeaconConfig() {
}
@ -135,4 +139,13 @@ public class HeartBeatBeaconConfig {
}
}
@XmlElementWrapper(name = "ClusterFormationChangedNotifiers", required = true)
@XmlElement(name = "Notifier", required = true)
public List<String> getNotifiers() {
return notifiers;
}
public void setNotifiers(List<String> notifiers) {
this.notifiers = notifiers;
}
}

@ -22,6 +22,7 @@ import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.HeartBeatBeaconU
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.config.HeartBeatBeaconConfig;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.config.datasource.DataSourceConfig;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.dao.HeartBeatBeaconDAOFactory;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.ClusterFormationChangedNotifierRepository;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.HeartBeatManagementService;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.HeartBeatManagementServiceImpl;
import org.apache.commons.logging.Log;
@ -29,6 +30,8 @@ import org.apache.commons.logging.LogFactory;
import org.osgi.service.component.ComponentContext;
import org.wso2.carbon.ndatasource.core.DataSourceService;
import java.util.List;
/**
* @scr.component name="io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.heartbeatBeaconComponent"
* immediate="true"
@ -58,10 +61,23 @@ public class HeartBeatBeaconComponent {
DataSourceConfig dsConfig = HeartBeatBeaconConfig.getInstance().getDataSourceConfig();
HeartBeatBeaconDAOFactory.init(dsConfig);
ClusterFormationChangedNotifierRepository clusterFormationChangedNotifierRepository
= new ClusterFormationChangedNotifierRepository();
List<String> notifiers = HeartBeatBeaconConfig.getInstance().getNotifiers();
if (notifiers != null && notifiers.size() > 0) {
for (String notifier : notifiers) {
clusterFormationChangedNotifierRepository.addNotifier(notifier);
}
}
HeartBeatBeaconDataHolder.getInstance().setClusterFormationChangedNotifierRepository(
clusterFormationChangedNotifierRepository);
//Setting up executors to notify heart beat status */
HeartBeatExecutor.setUpNotifiers(HeartBeatBeaconUtils.getServerDetails());
}
if (log.isDebugEnabled()) {
log.debug("Heart Beat Notifier bundle has been successfully initialized");
}

@ -18,6 +18,7 @@
package io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.internal;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.ClusterFormationChangedNotifierRepository;
import io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service.HeartBeatManagementService;
public class HeartBeatBeaconDataHolder {
@ -27,6 +28,7 @@ public class HeartBeatBeaconDataHolder {
private static HeartBeatBeaconDataHolder thisInstance = new HeartBeatBeaconDataHolder();
private ClusterFormationChangedNotifierRepository clusterFormationChangedNotifierRepository;
private HeartBeatBeaconDataHolder() {}
public static HeartBeatBeaconDataHolder getInstance() {
@ -48,4 +50,12 @@ public class HeartBeatBeaconDataHolder {
public void setLocalServerUUID(String localServerUUID) {
this.localServerUUID = localServerUUID;
}
public ClusterFormationChangedNotifierRepository getClusterFormationChangedNotifierRepository() {
return clusterFormationChangedNotifierRepository;
}
public void setClusterFormationChangedNotifierRepository(ClusterFormationChangedNotifierRepository clusterFormationChangedNotifierRepository) {
this.clusterFormationChangedNotifierRepository = clusterFormationChangedNotifierRepository;
}
}

@ -69,6 +69,7 @@ public class HeartBeatExecutor {
try {
recordHeartBeat(designatedUUID);
electDynamicTaskExecutionCandidate(cumilativeTimeOut);
notifyClusterFormationChanged(cumilativeTimeOut);
} catch (Exception e) {
log.error("Error while executing record heart beat task. This will result in schedule operation malfunction.", e);
}
@ -98,5 +99,8 @@ public class HeartBeatExecutor {
HeartBeatBeaconDataHolder.getInstance().getHeartBeatManagementService().electCandidate(cumilativeTimeOut);
}
static void notifyClusterFormationChanged(int cumilativeTimeOut) throws HeartBeatManagementException {
HeartBeatBeaconDataHolder.getInstance().getHeartBeatManagementService().notifyClusterFormationChanged(cumilativeTimeOut);
}
}

@ -0,0 +1,25 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service;
public interface ClusterFormationChangedNotifier {
String getType();
void notifyClusterFormationChanged(int hashIndex, int activeServerCount);
}

@ -0,0 +1,64 @@
/*
* Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
*
* Entgra (Pvt) Ltd. 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 io.entgra.device.mgt.core.server.bootup.heartbeat.beacon.service;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ClusterFormationChangedNotifierRepository {
private Map<String, ClusterFormationChangedNotifier> notifiers;
private static final Log log = LogFactory.getLog(ClusterFormationChangedNotifierRepository.class);
public ClusterFormationChangedNotifierRepository() {
this.notifiers = new ConcurrentHashMap<>();
}
public void addNotifier(ClusterFormationChangedNotifier notifier) {
notifiers.put(notifier.getType(), notifier);
}
public void addNotifier(String className) {
try {
if (!StringUtils.isEmpty(className)) {
Class<?> clz = Class.forName(className);
ClusterFormationChangedNotifier notifier = (ClusterFormationChangedNotifier) clz.newInstance();
notifiers.put(notifier.getType(), notifier);
}
} catch (ClassNotFoundException e) {
log.error("Provided ClusterFormationChangedNotifier implementation '" + className + "' cannot be found", e);
} catch (InstantiationException e) {
log.error("Error occurred while instantiating ClusterFormationChangedNotifier implementation '" +
className + "'", e);
} catch (IllegalAccessException e) {
log.error("Error occurred while adding ClusterFormationChangedNotifier implementation '" + className + "'", e);
}
}
public ClusterFormationChangedNotifier getNotifier(String type) {
return notifiers.get(type);
}
public Map<String, ClusterFormationChangedNotifier> getNotifiers() {
return notifiers;
}
}

@ -36,6 +36,7 @@ public interface HeartBeatManagementService {
boolean recordHeartBeat(HeartBeatEvent event) throws HeartBeatManagementException;
void electCandidate(int elapsedTimeInSeconds) throws HeartBeatManagementException;
void notifyClusterFormationChanged(int elapsedTimeInSeconds) throws HeartBeatManagementException;
boolean updateTaskExecutionAcknowledgement(String newTask) throws HeartBeatManagementException;

@ -48,6 +48,9 @@ public class HeartBeatManagementServiceImpl implements HeartBeatManagementServic
private final HeartBeatDAO heartBeatDAO;
private static int lastActiveCount = -1;
private static int lastHashIndex = -1;
public HeartBeatManagementServiceImpl() {
this.heartBeatDAO = HeartBeatBeaconDAOFactory.getHeartBeatDAO();
}
@ -254,6 +257,55 @@ public class HeartBeatManagementServiceImpl implements HeartBeatManagementServic
throw new HeartBeatManagementException(msg);
}
}
@Override
public void notifyClusterFormationChanged(int elapsedTimeInSeconds) throws HeartBeatManagementException {
if (HeartBeatBeaconConfig.getInstance().isEnabled()) {
try {
HeartBeatBeaconDAOFactory.beginTransaction();
Map<String, ServerContext> servers = heartBeatDAO.getActiveServerDetails(elapsedTimeInSeconds);
HeartBeatBeaconDAOFactory.commitTransaction();
if (servers != null && !servers.isEmpty()) {
String serverUUID = HeartBeatBeaconDataHolder.getInstance().getLocalServerUUID();
ServerContext serverContext = servers.get(serverUUID);
// cluster change can be identified, either by changing hash index or changing active server count
if ((lastHashIndex != serverContext.getIndex()) || (lastActiveCount != servers.size())) {
lastHashIndex = serverContext.getIndex();
lastActiveCount = servers.size();
ClusterFormationChangedNotifierRepository repository = HeartBeatBeaconDataHolder.getInstance().getClusterFormationChangedNotifierRepository();
Map<String, ClusterFormationChangedNotifier> notifiers = repository.getNotifiers();
for (String type : notifiers.keySet()) {
ClusterFormationChangedNotifier notifier = notifiers.get(type);
Runnable r = new Runnable() {
@Override
public void run() {
notifier.notifyClusterFormationChanged(lastHashIndex, lastActiveCount);
}
};
new Thread(r).start();
}
}
}
} catch (HeartBeatDAOException e) {
String msg = "Error occurred while notifyClusterFormationChanged.";
log.error(msg, e);
throw new HeartBeatManagementException(msg, e);
} catch (TransactionManagementException e) {
HeartBeatBeaconDAOFactory.rollbackTransaction();
String msg = "Error occurred while electing candidate for dynamic task execution. Issue in opening a connection to the underlying data source";
log.error(msg, e);
throw new HeartBeatManagementException(msg, e);
} finally {
HeartBeatBeaconDAOFactory.closeConnection();
}
} else {
String msg = "Heart Beat Configuration Disabled. Error while notifyClusterFormationChanged.";
log.error(msg);
throw new HeartBeatManagementException(msg);
}
}
private void electCandidate(Map<String, ServerContext> servers) throws HeartBeatDAOException {
String electedCandidate = getRandomElement(servers.keySet());

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>identity-extensions</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>identity-extensions</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -23,7 +23,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>logger</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
</parent>
<artifactId>io.entgra.device.mgt.core.notification.logger</artifactId>

@ -22,7 +22,7 @@
<parent>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>operation-template-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>policy-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>policy-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>policy-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>policy-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -66,4 +66,9 @@ public class TestHeartBeatManagementService implements HeartBeatManagementServic
@Override public Map<Integer, ServerContext> getActiveServers() throws HeartBeatManagementException {
return null;
}
@Override
public void notifyClusterFormationChanged(int elapsedTimeInSeconds) throws HeartBeatManagementException {
}
}

@ -23,7 +23,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>subtype-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<artifactId>task-manager</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>task-manager</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>task-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>task-watcher</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>task-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<artifactId>tenant-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<artifactId>tenant-mgt</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -20,7 +20,7 @@
<parent>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>email-sender</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>transport-mgt</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -21,7 +21,7 @@
<parent>
<artifactId>io.entgra.device.mgt.core.parent</artifactId>
<groupId>io.entgra.device.mgt.core</groupId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>sms-handler</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

@ -22,7 +22,7 @@
<parent>
<groupId>io.entgra.device.mgt.core</groupId>
<artifactId>sms-handler</artifactId>
<version>5.0.27-SNAPSHOT</version>
<version>5.0.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save