Java tutorial
/* * Copyright 2005-2013 WSO2, Inc. (http://wso2.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wso2.carbon.appfactory.s4.integration.utils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; import org.json.JSONObject; import org.wso2.carbon.appfactory.common.AppFactoryConstants; import org.wso2.carbon.appfactory.common.AppFactoryException; import org.wso2.carbon.appfactory.core.dao.JDBCAppVersionDAO; import org.wso2.carbon.appfactory.core.dao.JDBCApplicationDAO; import org.wso2.carbon.appfactory.core.governance.RxtManager; import org.wso2.carbon.appfactory.core.dao.ApplicationDAO; import org.wso2.carbon.appfactory.core.internal.ServiceHolder; import org.wso2.carbon.appfactory.core.util.AppFactoryCoreUtil; import org.wso2.carbon.appfactory.core.util.CommonUtil; import org.wso2.carbon.appfactory.s4.integration.DomainMapperEventHandler; import org.wso2.carbon.appfactory.s4.integration.internal.ServiceReferenceHolder; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.user.core.UserCoreConstants; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.util.Set; /** * Util class for domain mapping */ public class DomainMappingUtils { // End points public static final String DOMAIN_AVAILABLE_END_POINT = "/stratos/admin/cartridge/%s/subscription/%s/domains/%s"; public static final String ADD_DOMAIN_END_POINT = "/stratos/admin/cartridge/%s/subscription/%s/domains/"; public static final String REMOVE_DOMAIN_END_POINT = "/stratos/admin/cartridge/%s/subscription/%s/domains/%s"; public static final String DEFAULT_DOMAIN_SEPERATOR = "."; // Messages to display in frontend public static final String AF_DOMAIN_AVAILABILITY_ERROR_MSG = "Error occurred while checking availability for domain: \"%s\""; public static final String AF_DOMAIN_NOT_AVAILABLE_MSG = "Requested sub domain \"%s\" does not available"; public static final String AF_ERROR_ADD_DOMAIN_MSG = "Error occurred while updating domain mapping with domain : %s"; public static final String AF_ERROR_REMOVE_DOMAIN_MSG = "Error occurred while removing domain mapping."; public static final String AF_CUSTOM_URL_NOT_VERIFIED = "Requested custom URL %s is not verified"; public static final String AF_METADATA_ERROR_MSG = "Error occurred while accessing application metadata"; public static final String AF_ERROR_GENARIC_MSG = "Error occurred while updating domain mapping."; public static final String UNVERIFIED_VERIFICATION_CODE = ""; public static final String UNDEFINED_URL_RXT_VALUE = ""; public static final String JSON_PAYLOAD_KEY_DOMAINS = "domains"; public static final String JSON_PAYLOAD_KEY_DOMAIN_NAME = "domainName"; public static final String JSON_PAYLOAD_KEY_APP_CONTEXT = "applicationContext"; private static final Log log = LogFactory.getLog(DomainMappingUtils.class); private final static RxtManager rxtManager = RxtManager.getInstance(); private static final String AUTHORIZATION_HEADER = "Authorization"; // Default prod url private static final String DEFAULT_URL_PREFIX = "{prefix}"; private static final String DEFAULT_URL_MIDDLE = "{middle}"; private static final String DEFAULT_URL_SUFFIX = "{suffix}"; private static final String HYPHEN = "-"; private static final String DEFAULT_URL_FORMAT = DEFAULT_URL_PREFIX + HYPHEN + DEFAULT_URL_MIDDLE + "." + DEFAULT_URL_SUFFIX; /** * Send rest POST request to Stratos SM. * * @param stage the stage of the Stratos SM * @param body message body * @param addSubscriptionDomainEndPoint end point to send the message * @throws AppFactoryException */ public static DomainMappingResponse sendPostRequest(String stage, String body, String addSubscriptionDomainEndPoint) throws AppFactoryException { HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); String endPointUrl = getSMUrl(stage) + addSubscriptionDomainEndPoint; PostMethod postMethod = new PostMethod(endPointUrl); // password as garbage value since we authenticate with mutual ssl postMethod.setRequestHeader(AUTHORIZATION_HEADER, getAuthHeaderValue()); StringRequestEntity requestEntity; try { requestEntity = new StringRequestEntity(body, "application/json", "UTF-8"); } catch (UnsupportedEncodingException e) { String msg = "Error while setting parameters"; log.error(msg, e); throw new AppFactoryException(msg, e); } postMethod.setRequestEntity(requestEntity); return send(httpClient, postMethod); } /** * Get auth header value * * @return auth header in basic encode */ private static String getAuthHeaderValue() { String usernameInContext = CarbonContext.getThreadLocalCarbonContext().getUsername(); String tenantLessUserName = MultitenantUtils.getTenantAwareUsername(usernameInContext); String userName = tenantLessUserName + UserCoreConstants.TENANT_DOMAIN_COMBINER + CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); // password as garbage value since we authenticate with mutual ssl. // We need to set the auth header for requests with the username. byte[] getUserPasswordInBytes = (userName + ":nopassword").getBytes(); String encodedValue = new String(Base64.encodeBase64(getUserPasswordInBytes)); return "Basic " + encodedValue; } /** * Send REST DELETE request to stratos SM. * * @param stage the stage of the Stratos SM * @param removeSubscriptionDomainEndPoint end point to send the message * @throws AppFactoryException */ public static DomainMappingResponse sendDeleteRequest(String stage, String removeSubscriptionDomainEndPoint) throws AppFactoryException { HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); String endPointUrl = getSMUrl(stage) + removeSubscriptionDomainEndPoint; DeleteMethod deleteMethod = new DeleteMethod(endPointUrl); deleteMethod.setRequestHeader(AUTHORIZATION_HEADER, getAuthHeaderValue()); return send(httpClient, deleteMethod); } /** * Send rest GET request to Stratos SM * * @param stage the stage of the Stratos SM * @param getSubscriptionDomainEndPoint end point to send the message * @throws AppFactoryException */ public static DomainMappingResponse sendGetRequest(String stage, String getSubscriptionDomainEndPoint) throws AppFactoryException { HttpClient httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); String endPointUrl = getSMUrl(stage) + getSubscriptionDomainEndPoint; GetMethod getMethod = new GetMethod(endPointUrl); getMethod.setRequestHeader(AUTHORIZATION_HEADER, getAuthHeaderValue()); return send(httpClient, getMethod); } /** * Send rest request. * * @param httpClient client object * @param method method type * @throws AppFactoryException */ private static DomainMappingResponse send(HttpClient httpClient, HttpMethodBase method) throws AppFactoryException { int responseCode; String responseString = null; try { responseCode = httpClient.executeMethod(method); } catch (IOException e) { String msg = "Error occurred while executing method " + method.getName(); log.error(msg, e); throw new AppFactoryException(msg, e); } try { responseString = method.getResponseBodyAsString(); } catch (IOException e) { String msg = "error while getting response as String for " + method.getName(); log.error(msg, e); throw new AppFactoryException(msg, e); } finally { method.releaseConnection(); } if (log.isDebugEnabled()) { log.debug(" DomainMappingManagementService response id: " + responseCode + " message: " + responseString); } return new DomainMappingResponse(responseString, responseCode); } /** * Generate json payload for add new subscription domain * * @param domain domain to be mapped * @param appKey application key * @param version version to be mapped * @param stage the stage of the Stratos SM * @return JSON representation of the payload. * <p/> * Sample payload * domains : { * domainName: foo.bar.com, * applicationContext: /t/foo.com/webapps/bar-1.0.0 * } */ public static String generateAddSubscriptionDomainJSON(String domain, String appKey, String version, String stage) throws AppFactoryException { JSONObject domainsJSON = new JSONObject(); JSONObject domainsDataJSON = new JSONObject(); String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); try { String applicationUrl = AppFactoryCoreUtil.getApplicationUrl(appKey, version, stage, tenantDomain); URL url = new URL(applicationUrl); domainsDataJSON.put(JSON_PAYLOAD_KEY_DOMAIN_NAME, domain); // since in the format of /t/foodotcom/webapps/bar-1.0.0/ we are removing last "/" domainsDataJSON.put(JSON_PAYLOAD_KEY_APP_CONTEXT, StringUtils.removeEnd(url.getFile(), "/")); domainsJSON.put(JSON_PAYLOAD_KEY_DOMAINS, domainsDataJSON); if (log.isDebugEnabled()) { log.debug("Add new subscription json payload: " + domainsJSON.toString()); } } catch (JSONException e) { String errorMsg = "Error while generating json string for application key : " + appKey + " domain : " + domain + " version : " + version; log.error(errorMsg, e); throw new AppFactoryException(errorMsg); } catch (MalformedURLException e) { String errorMsg = "Error while generating application context for application key : " + appKey + " domain : " + domain + " version : " + version; log.error(errorMsg, e); throw new AppFactoryException(errorMsg); } return domainsJSON.toString(); } /** * Generate message content for initial mapping * * @param domain domain to be mapped * @return JSON message */ public static String generateInitialSubscriptionDomainJSON(String domain) throws AppFactoryException { JSONObject domainsJSON = new JSONObject(); JSONObject domainsDataJSON = new JSONObject(); try { domainsDataJSON.put(JSON_PAYLOAD_KEY_DOMAIN_NAME, domain); domainsDataJSON.put(JSON_PAYLOAD_KEY_APP_CONTEXT, "/" + getDefaultMappingContext()); domainsJSON.put(JSON_PAYLOAD_KEY_DOMAINS, domainsDataJSON); if (log.isDebugEnabled()) { log.debug("Map to initial url json payload: " + domainsJSON.toString()); } } catch (JSONException e) { String errorMsg = "Error while generating json string for domain: " + domain + " for initial mapping"; log.error(errorMsg, e); throw new AppFactoryException(errorMsg); } return domainsJSON.toString(); } /** * Get default application context for domain mapping. * * @return default application context */ public static String getDefaultMappingContext() { return ServiceHolder.getAppFactoryConfiguration().getFirstProperty("DefaultMappingContext"); } /** * Get Stratos SM's url. * * @param stage the stage of the Stratos SM * @return base url of the Stratos SM which corresponding to the {@code stage} */ public static String getSMUrl(String stage) { return ServiceHolder.getAppFactoryConfiguration() .getFirstProperty("ApplicationDeployment.DeploymentStage." + stage + ".TenantMgtUrl"); } /** * Publish domain mapping events to registered {@link org.wso2.carbon.appfactory.s4.integration.DomainMapperEventHandler} instances. * {@link org.wso2.carbon.appfactory.s4.integration.DomainMapperEventHandler} can perform their specific operations such as add DNS entry etc.. * based on the event. * * @param domain domain to be mapped/removed * @param action action to to be performed * @throws AppFactoryException */ public static void publishToDomainMappingEventHandlers(String domain, DomainMappingAction action) throws AppFactoryException { Set<DomainMapperEventHandler<?>> domainEventHandler = ServiceReferenceHolder.getInstance() .getDomainMapperEventHandler(); switch (action) { case ADD_DOMAIN_MAPPING: for (DomainMapperEventHandler<?> aDomainEventHandler1 : domainEventHandler) { DomainMapperEventHandler domainMapperEventHandler = (DomainMapperEventHandler) aDomainEventHandler1; try { domainMapperEventHandler.onDomainMappingCreate(domain); } catch (AppFactoryException e) { String msg = "Error occurred invoking domain mapping created event for " + domain; log.error(msg, e); throw new AppFactoryException(msg, e); } } break; case REMOVE_DOMAIN_MAPPING: for (DomainMapperEventHandler<?> aDomainEventHandler : domainEventHandler) { DomainMapperEventHandler domainMapperEventHandler = (DomainMapperEventHandler) aDomainEventHandler; try { domainMapperEventHandler.OnDomainMappingDelete(domain); } catch (AppFactoryException e) { String msg = "Error occurred invoking domain mapping removed event for " + domain; log.error(msg, e); throw new AppFactoryException(msg, e); } } break; default: } } /** * Get sub domain of the default url * * @param defaultUrl default url * @return the substring before the first occurrence of the default host domain, null if null String input or {@code defaultUrl} does not has the default domain */ public static String getSubDomain(String defaultUrl) { String defaultHostName = DEFAULT_DOMAIN_SEPERATOR + ServiceHolder.getAppFactoryConfiguration().getFirstProperty("DomainName"); if (StringUtils.contains(defaultUrl, defaultHostName)) { return StringUtils.substringBefore(defaultUrl, defaultHostName); } else { return null; } } /** * Generate the verification code for custom url * * @param customUrl custom url * @return verification code. Will return {@link org.wso2.carbon.appfactory.s4.integration.utils.DomainMappingUtils#UNVERIFIED_VERIFICATION_CODE} * if {@code customUrl} is null or empty. */ public static String generateVerificationCode(String customUrl) { // set verification code as hash value of the custom url. // this value will use as a pre check, when adding a domain mapping if (StringUtils.isNotBlank(customUrl)) { return Integer.toString(customUrl.hashCode()); } else { return DomainMappingUtils.UNVERIFIED_VERIFICATION_CODE; } } /** * Get the custom domain of the application * * @param appKey application key * @return custom domain. null if custom domain is not defined * @throws AppFactoryException */ public static String getCustomDomain(String appKey) throws AppFactoryException { String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); String customDomain; try { customDomain = ApplicationDAO.getInstance().getAppInfoRxtValue(appKey, AppFactoryConstants.RXT_KEY_APPINFO_CUSTOM_URL, tenantDomain); } catch (AppFactoryException e) { log.error("Error occurred while getting custom url for :" + appKey, e); throw new AppFactoryException(AF_METADATA_ERROR_MSG); } return customDomain; } /** * Get default production url for the application {@code appKey}. * * @param appKey application key * @return default production url. null if not defined * @throws org.wso2.carbon.appfactory.common.AppFactoryException */ public static String getDefaultDomain(String appKey) throws AppFactoryException { String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); String defaultSubDomain; try { defaultSubDomain = ApplicationDAO.getInstance().getAppInfoRxtValue(appKey, AppFactoryConstants.RXT_KEY_APPINFO_DEFAULT_DOMAIN, tenantDomain); } catch (AppFactoryException e) { log.error("Error occurred while getting default url for :" + appKey, e); throw new AppFactoryException(AF_METADATA_ERROR_MSG); } if (StringUtils.isNotBlank(defaultSubDomain)) { // Since we are saving only the subdomain, we append host domain to the end return defaultSubDomain + DEFAULT_DOMAIN_SEPERATOR + ServiceHolder.getAppFactoryConfiguration().getFirstProperty("DomainName"); } else { return null; } } /** * Updates the registry(appversion) with mapped domain. * * @param appKey application key * @param version mapped version * @param mappedDomain mapped domain * @throws org.wso2.carbon.appfactory.common.AppFactoryException */ public static void updateVersionMetadataWithMappedDomain(String appKey, String version, String mappedDomain) throws AppFactoryException { String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); try { JDBCAppVersionDAO.getInstance().updateSubDomainsOfVersion(appKey, version, mappedDomain); } catch (AppFactoryException e) { log.error("Error occurred while updating the appversion rxt with mapped domain for application id: " + appKey + " version:" + version + " domain: " + mappedDomain, e); throw new AppFactoryException(String.format(AF_ERROR_ADD_DOMAIN_MSG, mappedDomain)); } } /** * Updates the registry(appinfo) with default mapped domain. * * @param appKey application key * @param mappedDomain mapped domain * @throws org.wso2.carbon.appfactory.common.AppFactoryException */ public static void updateApplicationMetaDataMappedDomain(String appKey, String mappedDomain) throws AppFactoryException { String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); try { String subDomain = UNDEFINED_URL_RXT_VALUE; // Since we are saving sub domain of the default domain String defaultSubDomain = getSubDomain(mappedDomain); if (StringUtils.isNotBlank(defaultSubDomain)) { subDomain = defaultSubDomain; } else if (!StringUtils.equals(UNDEFINED_URL_RXT_VALUE, mappedDomain)) { // if given mappedDomain does not derived from the default domain host and does not equal to UNDEFINED_URL_RXT_VALUE log.warn( "Requested default url:" + mappedDomain + " does not derived from the default domain host: " + ServiceHolder.getAppFactoryConfiguration().getFirstProperty("DomainName") + " application id:" + appKey); } ApplicationDAO.getInstance().updateAppInfoRxt(appKey, AppFactoryConstants.RXT_KEY_APPINFO_DEFAULT_DOMAIN, subDomain, tenantDomain); } catch (AppFactoryException e) { log.error("Error occurred while updating the application rxt with mapped domain for application id: " + appKey + " domain: " + mappedDomain, e); throw new AppFactoryException(String.format(AF_ERROR_ADD_DOMAIN_MSG, mappedDomain)); } } /** * Updates the registry(appinfo) with custom domain and verification code. * * @param appKey application key * @param mappedDomain mapped domain * @param verificationCode verification code for the {@code mappedDomain}. {@code UNVERIFIED_VERIFICATION_CODE} if not verified * @throws AppFactoryException */ public static void updateCustomUrlMetadata(String appKey, String mappedDomain, String verificationCode) throws AppFactoryException { String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); try { ApplicationDAO.getInstance().updateAppInfoRxt(appKey, AppFactoryConstants.RXT_KEY_APPINFO_CUSTOM_URL, mappedDomain, tenantDomain); ApplicationDAO.getInstance().updateAppInfoRxt(appKey, AppFactoryConstants.RXT_KEY_APPINFO_CUSTOM_URL_VERIFICATION, verificationCode, tenantDomain); } catch (AppFactoryException e) { String msg = "Error occurred while updating the application rxt with custom domain for application id: " + appKey + " domain: " + mappedDomain; log.error(msg, e); throw new AppFactoryException(String.format(AF_ERROR_ADD_DOMAIN_MSG, mappedDomain)); } } /** * Update registry * * @param appKey application key * @param mappedDomain mapped domain * @param version mapped version * @param isCustomDomain whether {@code mappedDomain} is custom domain or not * @throws org.wso2.carbon.appfactory.common.AppFactoryException */ public static void updateMetadata(String appKey, String mappedDomain, String version, boolean isCustomDomain) throws AppFactoryException { // update appinfo rxt if (isCustomDomain) { updateCustomUrlMetadata(appKey, mappedDomain, generateVerificationCode(mappedDomain)); } else { updateApplicationMetaDataMappedDomain(appKey, mappedDomain); } // update version info rxt if (StringUtils.isNotBlank(version)) { updateVersionMetadataWithMappedDomain(appKey, version, mappedDomain); } } /** * Get domain available end point suffix. * * @param stage the stage of the Stratos SM * @param domain domain to be checked for availability * @param appType application type * @return domain availability end point in the format of * "/stratos/admin/cartridge/{cartridgeType}/subscription/{subscriptionAlias}/domains/{@code domain}" * <p/> * e.g: /stratos/admin/cartridge/samproduction/subscription/assamdotcom/domains/app1.sam.com */ public static String getDomainAvailableEndPoint(String stage, String domain, String appType) throws AppFactoryException { return String.format(DOMAIN_AVAILABLE_END_POINT, CommonUtil.getCartridgeType(stage, appType), CommonUtil.getSubscriptionAlias(stage, appType), domain); } /** * Get add domain end point suffix. * * @param stage the stage of the Stratos SM * @param appType application type * @return add domain end point in the format of * "/stratos/admin/cartridge/{cartridgeType}/subscription/{subscriptionAlias}/domains/" * <p/> * e.g: /stratos/admin/cartridge/samproduction/subscription/assamdotcom/domains/ */ public static String getAddDomainEndPoint(String stage, String appType) throws AppFactoryException { return String.format(ADD_DOMAIN_END_POINT, CommonUtil.getCartridgeType(stage, appType), CommonUtil.getSubscriptionAlias(stage, appType)); } /** * Get remove domain end point suffix * * @param stage the stage of the Stratos SM * @param domain domain to be removed * @param appType application type * @return remove domain end point in the format of * "/stratos/admin/cartridge/{cartridgeType}/subscription/{subscriptionAlias}/domains/{@code domain}" * <p/> * e.g /stratos/admin/cartridge/samproduction/subscription/assamdotcom/domains/app1.sam.com */ public static String getRemoveDomainEndPoint(String stage, String domain, String appType) throws AppFactoryException { return String.format(REMOVE_DOMAIN_END_POINT, CommonUtil.getCartridgeType(stage, appType), CommonUtil.getSubscriptionAlias(stage, appType), domain); } /** * Generate default production url in the format of {@code prefix}.{@code middle}.{@code suffix} * * @param prefix prefix of the url * @param middle middle part of the url * @param suffix suffix of the url * @return production url */ public static String generateDefaultProdUrl(String prefix, String middle, String suffix) { return DEFAULT_URL_FORMAT.replace(DEFAULT_URL_PREFIX, prefix).replace(DEFAULT_URL_MIDDLE, middle) .replace(DEFAULT_URL_SUFFIX, suffix); } }