/* * (C) Copyright 2006-2017 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Florent Guillaume * Mincong Huang */ package org.nuxeo.ecm.core.opencmis.impl.server; import static org.apache.chemistry.opencmis.commons.BasicPermissions.ALL; import static org.apache.chemistry.opencmis.commons.BasicPermissions.READ; import static org.apache.chemistry.opencmis.commons.BasicPermissions.WRITE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_POLICY; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_APPLY_ACL_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKIN_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKOUT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_TREE_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ACL_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_CHILDREN_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_DESCENDENTS_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PARENTS_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PROPERTIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_SOURCE; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_TARGET; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_POLICY; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_SET_CONTENT_DOCUMENT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT; import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_VIEW_CONTENT_OBJECT; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.chemistry.opencmis.commons.data.ExtensionFeature; import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition; import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; import org.apache.chemistry.opencmis.commons.enums.AclPropagation; import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl; import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges; import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates; import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions; import org.apache.chemistry.opencmis.commons.enums.CmisVersion; import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions; import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.CreatablePropertyTypesImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.NewTypeSettableAttributesImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl; import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.nuxeo.common.Environment; import org.nuxeo.ecm.core.api.security.PermissionProvider; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.opencmis.impl.util.TypeManagerImpl; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.CompositeType; import org.nuxeo.ecm.core.security.DefaultPermissionProvider; import org.nuxeo.ecm.core.security.PermissionVisibilityDescriptor; import org.nuxeo.runtime.api.Framework; /** * Information about a Nuxeo repository. */ public class NuxeoRepository { /** * @deprecated Since 7.10. Use {@link Environment#DISTRIBUTION_VERSION} */ @Deprecated public static final String NUXEO_VERSION_PROP = Environment.DISTRIBUTION_VERSION; public static final String NUXEO_URL_PROP = "nuxeo.url"; public static final String SUPPORTS_JOINS_PROP = "org.nuxeo.cmis.joins"; public static final String SUPPORTS_PROXIES_PROP = "org.nuxeo.cmis.proxies"; public static final String ELASTICSEARCH_PROP = "org.nuxeo.cmis.elasticsearch"; /** * Key of the configuration property {@code "org.nuxeo.cmis.relaxSpec"}, default to {@code false}. Setting this * property to {@code true} allows users to relax the CMIS specification 1.1 and use customized CMISQL. Please be * aware the risk of doing so. It will potentially cause query parsing error. * <p> * The relax mode does not follow the CMIS specification 1.1, section 2.1.14.2.4.4, where at most one * {@code CONTAINS()} function MUST be included in a single query statement. Currently, such mode only works for * CMIS query having no {@code JOIN} predicate. * * @see https://jira.nuxeo.com/browse/NXP-19858 */ public static final String RELAX_CMIS_SPEC = "org.nuxeo.cmis.relaxSpec"; private static final String NUXEO_CONTEXT_PATH_PROP = "org.nuxeo.ecm.contextPath"; private static final String NUXEO_CONTEXT_PATH_DEFAULT = "/nuxeo"; private static final String X_FORWARDED_HOST = "x-forwarded-host"; private static final String NUXEO_VH_HEADER = "nuxeo-virtual-host"; private static final String VH_PARAM = "nuxeo.virtual.host"; public static final String NUXEO_READ_REMOVE = "ReadRemove"; protected final String repositoryId; protected final String rootFolderId; protected boolean supportsJoins; protected boolean supportsProxies = true; protected boolean useElasticsearch; protected Map<CmisVersion, TypeManagerImpl> typeManagerByCmisVersion = new HashMap<>(); public NuxeoRepository(String repositoryId, String rootFolderId) { this.repositoryId = repositoryId; this.rootFolderId = rootFolderId; if (Framework.isBooleanPropertyTrue(SUPPORTS_JOINS_PROP)) { setSupportsJoins(true); } if (Framework.isBooleanPropertyFalse(SUPPORTS_PROXIES_PROP)) { setSupportsProxies(false); } if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_PROP)) { setUseElasticsearch(true); } } public void setSupportsJoins(boolean supportsJoins) { this.supportsJoins = supportsJoins; } public boolean supportsJoins() { return supportsJoins; } public void setSupportsProxies(boolean supportsProxies) { this.supportsProxies = supportsProxies; } public boolean supportsProxies() { return supportsProxies; } public void setUseElasticsearch(boolean useElasticsearch) { this.useElasticsearch = useElasticsearch; } public boolean useElasticsearch() { return useElasticsearch; } public String getId() { return repositoryId; } // no need to have it synchronized public TypeManagerImpl getTypeManager(CmisVersion cmisVersion) { TypeManagerImpl typeManager = typeManagerByCmisVersion.get(cmisVersion); if (typeManager == null) { typeManager = initializeTypes(cmisVersion); typeManagerByCmisVersion.put(cmisVersion, typeManager); } return typeManager; } protected TypeManagerImpl initializeTypes(CmisVersion cmisVersion) { SchemaManager schemaManager = Framework.getService(SchemaManager.class); // scan the types to find super/inherited relationships Map<String, List<String>> typesChildren = new HashMap<>(); for (DocumentType dt : schemaManager.getDocumentTypes()) { org.nuxeo.ecm.core.schema.types.Type st = dt.getSuperType(); if (st == null) { continue; } String name = st.getName(); List<String> siblings = typesChildren.get(name); if (siblings == null) { siblings = new LinkedList<>(); typesChildren.put(name, siblings); } siblings.add(dt.getName()); } // convert the transitive closure for Folder and Document subtypes Set<String> done = new HashSet<>(); TypeManagerImpl typeManager = new TypeManagerImpl(); typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_DOCUMENT, schemaManager, cmisVersion)); typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_FOLDER, schemaManager, cmisVersion)); typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_RELATIONSHIP, schemaManager, cmisVersion)); if (cmisVersion != CmisVersion.CMIS_1_0) { typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_SECONDARY, schemaManager, cmisVersion)); } addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_DOCUMENT, typesChildren, done, schemaManager, cmisVersion); addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_FOLDER, typesChildren, done, schemaManager, cmisVersion); addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_RELATION, typesChildren, done, schemaManager, cmisVersion); if (cmisVersion != CmisVersion.CMIS_1_0) { addSecondaryTypes(typeManager, schemaManager, cmisVersion); } return typeManager; } protected void addTypesRecursively(TypeManagerImpl typeManager, String name, Map<String, List<String>> typesChildren, Set<String> done, SchemaManager schemaManager, CmisVersion cmisVersion) { if (done.contains(name)) { return; } done.add(name); DocumentType dt = schemaManager.getDocumentType(name); String parentTypeId = NuxeoTypeHelper.getParentTypeId(dt); if (parentTypeId != null) { TypeDefinitionContainer parentType = typeManager.getTypeById(parentTypeId); if (parentType == null) { // if parent was ignored, reparent under cmis:document parentTypeId = BaseTypeId.CMIS_DOCUMENT.value(); } else { if (parentType.getTypeDefinition().getBaseTypeId() != BaseTypeId.CMIS_FOLDER && dt.isFolder()) { // reparent Folderish but child of Document under // cmis:folder parentTypeId = BaseTypeId.CMIS_FOLDER.value(); } } typeManager.addTypeDefinition(NuxeoTypeHelper.constructDocumentType(dt, parentTypeId, cmisVersion)); } // recurse in children List<String> children = typesChildren.get(name); if (children == null) { return; } for (String sub : children) { addTypesRecursively(typeManager, sub, typesChildren, done, schemaManager, cmisVersion); } } protected void addSecondaryTypes(TypeManagerImpl typeManager, SchemaManager schemaManager, CmisVersion cmisVersion) { for (CompositeType type : schemaManager.getFacets()) { typeManager.addTypeDefinition(NuxeoTypeHelper.constructSecondaryType(type, cmisVersion), false); } } public String getRootFolderId() { return rootFolderId; } public RepositoryInfo getRepositoryInfo(String latestChangeLogToken, CallContext callContext) { CmisVersion cmisVersion = callContext.getCmisVersion(); RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl(); repositoryInfo.setId(repositoryId); repositoryInfo.setName("Nuxeo Repository " + repositoryId); repositoryInfo.setDescription("Nuxeo Repository " + repositoryId); repositoryInfo.setCmisVersionSupported(cmisVersion.value()); repositoryInfo.setPrincipalAnonymous("Guest"); // TODO repositoryInfo.setPrincipalAnyone(SecurityConstants.EVERYONE); repositoryInfo.setThinClientUri(getBaseURL(callContext)); repositoryInfo.setChangesIncomplete(Boolean.FALSE); repositoryInfo.setChangesOnType(Arrays.asList(BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER)); repositoryInfo.setLatestChangeLogToken(latestChangeLogToken); repositoryInfo.setVendorName("Nuxeo"); repositoryInfo.setProductName("Nuxeo OpenCMIS Connector"); String version = Framework.getProperty(Environment.DISTRIBUTION_VERSION, "5.5 dev"); repositoryInfo.setProductVersion(version); repositoryInfo.setRootFolder(rootFolderId); repositoryInfo.setExtensionFeature(Collections.<ExtensionFeature> emptyList()); // capabilities RepositoryCapabilitiesImpl caps = new RepositoryCapabilitiesImpl(); caps.setAllVersionsSearchable(Boolean.TRUE); caps.setCapabilityAcl(CapabilityAcl.MANAGE); caps.setCapabilityChanges(CapabilityChanges.OBJECTIDSONLY); caps.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.PWCONLY); caps.setCapabilityJoin(supportsJoins ? CapabilityJoin.INNERANDOUTER : CapabilityJoin.NONE); caps.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED); caps.setCapabilityRendition(CapabilityRenditions.READ); caps.setIsPwcSearchable(Boolean.TRUE); caps.setIsPwcUpdatable(Boolean.TRUE); caps.setSupportsGetDescendants(Boolean.TRUE); caps.setSupportsGetFolderTree(Boolean.TRUE); caps.setSupportsMultifiling(Boolean.FALSE); caps.setSupportsUnfiling(Boolean.FALSE); caps.setSupportsVersionSpecificFiling(Boolean.FALSE); caps.setNewTypeSettableAttributes(new NewTypeSettableAttributesImpl()); caps.setCreatablePropertyTypes(new CreatablePropertyTypesImpl()); repositoryInfo.setCapabilities(caps); // ACL capabilities AclCapabilitiesDataImpl aclCaps = new AclCapabilitiesDataImpl(); aclCaps.setAclPropagation(AclPropagation.PROPAGATE); aclCaps.setSupportedPermissions(SupportedPermissions.REPOSITORY); List<PermissionDefinition> permDefs = new ArrayList<>(); addPermissionDefinitions(permDefs); aclCaps.setPermissionDefinitionData(permDefs); Map<String, PermissionMapping> permMap = new HashMap<>(); addPermissionMapping(permMap, CAN_GET_DESCENDENTS_FOLDER, READ); addPermissionMapping(permMap, CAN_GET_CHILDREN_FOLDER, READ); addPermissionMapping(permMap, CAN_GET_PARENTS_FOLDER, READ); addPermissionMapping(permMap, CAN_GET_FOLDER_PARENT_OBJECT, READ); addPermissionMapping(permMap, CAN_CREATE_DOCUMENT_FOLDER, WRITE); addPermissionMapping(permMap, CAN_CREATE_FOLDER_FOLDER, WRITE); // no CAN_CREATE_POLICY_FOLDER due to spec bug addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_SOURCE, READ); addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_TARGET, READ); addPermissionMapping(permMap, CAN_GET_PROPERTIES_OBJECT, READ); addPermissionMapping(permMap, CAN_VIEW_CONTENT_OBJECT, READ); addPermissionMapping(permMap, CAN_UPDATE_PROPERTIES_OBJECT, WRITE); addPermissionMapping(permMap, CAN_MOVE_OBJECT, WRITE); addPermissionMapping(permMap, CAN_MOVE_TARGET, WRITE); addPermissionMapping(permMap, CAN_MOVE_SOURCE, WRITE); addPermissionMapping(permMap, CAN_DELETE_OBJECT, WRITE); addPermissionMapping(permMap, CAN_DELETE_TREE_FOLDER, WRITE); addPermissionMapping(permMap, CAN_SET_CONTENT_DOCUMENT, WRITE); addPermissionMapping(permMap, CAN_DELETE_CONTENT_DOCUMENT, WRITE); addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_OBJECT, WRITE); addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_FOLDER, WRITE); addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_OBJECT, WRITE); addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_FOLDER, WRITE); addPermissionMapping(permMap, CAN_CHECKOUT_DOCUMENT, WRITE); addPermissionMapping(permMap, CAN_CANCEL_CHECKOUT_DOCUMENT, WRITE); addPermissionMapping(permMap, CAN_CHECKIN_DOCUMENT, WRITE); addPermissionMapping(permMap, CAN_GET_ALL_VERSIONS_VERSION_SERIES, READ); addPermissionMapping(permMap, CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, READ); addPermissionMapping(permMap, CAN_ADD_POLICY_OBJECT, WRITE); addPermissionMapping(permMap, CAN_ADD_POLICY_POLICY, WRITE); addPermissionMapping(permMap, CAN_REMOVE_POLICY_OBJECT, WRITE); addPermissionMapping(permMap, CAN_REMOVE_POLICY_POLICY, WRITE); addPermissionMapping(permMap, CAN_GET_APPLIED_POLICIES_OBJECT, READ); addPermissionMapping(permMap, CAN_GET_ACL_OBJECT, READ); addPermissionMapping(permMap, CAN_APPLY_ACL_OBJECT, ALL); aclCaps.setPermissionMappingData(permMap); repositoryInfo.setAclCapabilities(aclCaps); return repositoryInfo; } @SuppressWarnings("unchecked") protected static void addPermissionDefinitions(List<PermissionDefinition> permDefs) { addPermissionDefinition(permDefs, READ, "Read"); // = Nuxeo Read addPermissionDefinition(permDefs, WRITE, "Write"); // = Nuxeo ReadWrite addPermissionDefinition(permDefs, ALL, "All"); // = Nuxeo Everything addPermissionDefinition(permDefs, NUXEO_READ_REMOVE, "Remove"); Set<String> done = new HashSet<>(); done.add(SecurityConstants.READ); done.add(SecurityConstants.READ_WRITE); done.add(SecurityConstants.EVERYTHING); done.add(NUXEO_READ_REMOVE); /* * Add Nuxeo-specific permissions registered through the permissionsVisibility extension point. */ DefaultPermissionProvider permissionProvider = (DefaultPermissionProvider) Framework.getService(PermissionProvider.class); permissionProvider.getUserVisiblePermissionDescriptors(); // init var Map<String, PermissionVisibilityDescriptor> map; try { Field f; f = DefaultPermissionProvider.class.getDeclaredField("mergedPermissionsVisibility"); f.setAccessible(true); map = (Map<String, PermissionVisibilityDescriptor>) f.get(permissionProvider); } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { throw new RuntimeException(e); } // iterate for all types regisited, not just the default "" for (Entry<String, PermissionVisibilityDescriptor> en : map.entrySet()) { for (String permission : en.getValue().getSortedItems()) { if (!done.add(permission)) { continue; } addPermissionDefinition(permDefs, permission, permission); } } } protected static void addPermissionDefinition(List<PermissionDefinition> permDefs, String permission, String description) { PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl(); pd.setId(permission); pd.setDescription(description); permDefs.add(pd); } protected static void addPermissionMapping(Map<String, PermissionMapping> permMap, String key, String permission) { PermissionMappingDataImpl pm = new PermissionMappingDataImpl(); pm.setKey(key); pm.setPermissions(Collections.singletonList(permission)); permMap.put(key, pm); } /** Returns the server base URL (including context). */ private static String getBaseURL(CallContext callContext) { HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST); if (request != null) { String baseURL = getServerURL(request); String contextPath = request.getContextPath(); if (contextPath == null) { contextPath = Framework.getProperty(NUXEO_CONTEXT_PATH_PROP, NUXEO_CONTEXT_PATH_DEFAULT); } // add context path return baseURL + contextPath + '/'; } else { return Framework.getProperty(NUXEO_URL_PROP); } } /** * Returns the server URL according to virtual hosting headers (without trailing slash). */ private static String getServerURL(HttpServletRequest request) { String url = null; // Detect Nuxeo specific header for VH String nuxeoVH = request.getHeader(NUXEO_VH_HEADER); if (nuxeoVH == null) { nuxeoVH = Framework.getProperty(VH_PARAM); } if (nuxeoVH != null && nuxeoVH.startsWith("http")) { url = nuxeoVH; } else { // default values String scheme = request.getScheme(); String serverName = request.getServerName(); int serverPort = request.getServerPort(); // Detect virtual hosting based in standard header String forwardedHost = request.getHeader(X_FORWARDED_HOST); if (forwardedHost != null) { if (forwardedHost.contains(":")) { String[] split = forwardedHost.split(":"); serverName = split[0]; serverPort = Integer.parseInt(split[1]); } else { serverName = forwardedHost; serverPort = 80; // fallback } } url = buildURL(scheme, serverName, serverPort); } // strip trailing slash if (url.endsWith("/")) { url = url.substring(0, url.length() - 1); } return url; } /** Builds an URL (without trailing slash). */ private static String buildURL(String scheme, String serverName, int serverPort) { StringBuilder sb = new StringBuilder(); sb.append(scheme); sb.append("://"); sb.append(serverName); if (serverPort != 0) { if ("http".equals(scheme) && serverPort != 80 || "https".equals(scheme) && serverPort != 443) { sb.append(':'); sb.append(serverPort); } } return sb.toString(); } }