001/*
002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.opencmis.impl.server;
020
021import static org.apache.chemistry.opencmis.commons.BasicPermissions.ALL;
022import static org.apache.chemistry.opencmis.commons.BasicPermissions.READ;
023import static org.apache.chemistry.opencmis.commons.BasicPermissions.WRITE;
024import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_OBJECT;
025import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_POLICY;
026import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER;
027import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT;
028import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_APPLY_ACL_OBJECT;
029import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT;
030import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKIN_DOCUMENT;
031import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKOUT_DOCUMENT;
032import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER;
033import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_FOLDER_FOLDER;
034import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE;
035import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET;
036import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT;
037import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_OBJECT;
038import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_TREE_FOLDER;
039import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ACL_OBJECT;
040import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES;
041import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT;
042import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_CHILDREN_FOLDER;
043import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_DESCENDENTS_FOLDER;
044import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT;
045import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT;
046import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PARENTS_FOLDER;
047import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PROPERTIES_OBJECT;
048import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_OBJECT;
049import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_SOURCE;
050import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_TARGET;
051import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER;
052import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT;
053import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_OBJECT;
054import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_POLICY;
055import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_SET_CONTENT_DOCUMENT;
056import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT;
057import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_VIEW_CONTENT_OBJECT;
058
059import java.lang.reflect.Field;
060import java.math.BigInteger;
061import java.util.ArrayList;
062import java.util.Arrays;
063import java.util.Collections;
064import java.util.HashMap;
065import java.util.HashSet;
066import java.util.LinkedList;
067import java.util.List;
068import java.util.Map;
069import java.util.Map.Entry;
070import java.util.Set;
071
072import javax.servlet.http.HttpServletRequest;
073
074import org.apache.chemistry.opencmis.commons.data.ExtensionFeature;
075import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
076import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
077import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
078import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
079import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
080import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
081import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
082import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
083import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
084import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
085import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
086import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
087import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
088import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
089import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
090import org.apache.chemistry.opencmis.commons.impl.dataobjects.CreatablePropertyTypesImpl;
091import org.apache.chemistry.opencmis.commons.impl.dataobjects.NewTypeSettableAttributesImpl;
092import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
093import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
094import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
095import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
096import org.apache.chemistry.opencmis.commons.server.CallContext;
097
098import org.nuxeo.common.Environment;
099import org.nuxeo.ecm.core.api.security.PermissionProvider;
100import org.nuxeo.ecm.core.api.security.SecurityConstants;
101import org.nuxeo.ecm.core.opencmis.impl.util.TypeManagerImpl;
102import org.nuxeo.ecm.core.schema.DocumentType;
103import org.nuxeo.ecm.core.schema.SchemaManager;
104import org.nuxeo.ecm.core.schema.types.CompositeType;
105import org.nuxeo.ecm.core.security.DefaultPermissionProvider;
106import org.nuxeo.ecm.core.security.PermissionVisibilityDescriptor;
107import org.nuxeo.runtime.api.Framework;
108
109/**
110 * Information about a Nuxeo repository.
111 */
112public class NuxeoRepository {
113
114    /**
115     * @deprecated Since 7.10. Use {@link Environment#DISTRIBUTION_VERSION}
116     */
117    @Deprecated
118    public static final String NUXEO_VERSION_PROP = Environment.DISTRIBUTION_VERSION;
119
120    public static final String NUXEO_URL_PROP = "nuxeo.url";
121
122    public static final String SUPPORTS_JOINS_PROP = "org.nuxeo.cmis.joins";
123
124    public static final String ELASTICSEARCH_PROP = "org.nuxeo.cmis.elasticsearch";
125
126    private static final String NUXEO_CONTEXT_PATH_PROP = "org.nuxeo.ecm.contextPath";
127
128    private static final String NUXEO_CONTEXT_PATH_DEFAULT = "/nuxeo";
129
130    private static final String X_FORWARDED_HOST = "x-forwarded-host";
131
132    private static final String NUXEO_VH_HEADER = "nuxeo-virtual-host";
133
134    private static final String VH_PARAM = "nuxeo.virtual.host";
135
136    public static final String NUXEO_READ_REMOVE = "ReadRemove";
137
138    protected final String repositoryId;
139
140    protected final String rootFolderId;
141
142    protected boolean supportsJoins;
143
144    protected boolean useElasticsearch;
145
146    protected Map<CmisVersion, TypeManagerImpl> typeManagerByCmisVersion = new HashMap<>();
147
148    public NuxeoRepository(String repositoryId, String rootFolderId) {
149        this.repositoryId = repositoryId;
150        this.rootFolderId = rootFolderId;
151        if (Framework.isBooleanPropertyTrue(SUPPORTS_JOINS_PROP)) {
152            setSupportsJoins(true);
153        }
154        if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_PROP)) {
155            setUseElasticsearch(true);
156        }
157    }
158
159    public void setSupportsJoins(boolean supportsJoins) {
160        this.supportsJoins = supportsJoins;
161    }
162
163    public boolean supportsJoins() {
164        return supportsJoins;
165    }
166
167    public void setUseElasticsearch(boolean useElasticsearch) {
168        this.useElasticsearch = useElasticsearch;
169    }
170
171    public boolean useElasticsearch() {
172        return useElasticsearch;
173    }
174
175    public String getId() {
176        return repositoryId;
177    }
178
179    // no need to have it synchronized
180    public TypeManagerImpl getTypeManager(CmisVersion cmisVersion) {
181        TypeManagerImpl typeManager = typeManagerByCmisVersion.get(cmisVersion);
182        if (typeManager == null) {
183            typeManager = initializeTypes(cmisVersion);
184            typeManagerByCmisVersion.put(cmisVersion, typeManager);
185        }
186        return typeManager;
187    }
188
189    protected TypeManagerImpl initializeTypes(CmisVersion cmisVersion) {
190        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
191        // scan the types to find super/inherited relationships
192        Map<String, List<String>> typesChildren = new HashMap<>();
193        for (DocumentType dt : schemaManager.getDocumentTypes()) {
194            org.nuxeo.ecm.core.schema.types.Type st = dt.getSuperType();
195            if (st == null) {
196                continue;
197            }
198            String name = st.getName();
199            List<String> siblings = typesChildren.get(name);
200            if (siblings == null) {
201                siblings = new LinkedList<>();
202                typesChildren.put(name, siblings);
203            }
204            siblings.add(dt.getName());
205        }
206        // convert the transitive closure for Folder and Document subtypes
207        Set<String> done = new HashSet<>();
208        TypeManagerImpl typeManager = new TypeManagerImpl();
209        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_DOCUMENT, schemaManager, cmisVersion));
210        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_FOLDER, schemaManager, cmisVersion));
211        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_RELATIONSHIP, schemaManager, cmisVersion));
212        if (cmisVersion != CmisVersion.CMIS_1_0) {
213            typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_SECONDARY, schemaManager, cmisVersion));
214        }
215        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_DOCUMENT, typesChildren, done, schemaManager, cmisVersion);
216        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_FOLDER, typesChildren, done, schemaManager, cmisVersion);
217        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_RELATION, typesChildren, done, schemaManager, cmisVersion);
218        if (cmisVersion != CmisVersion.CMIS_1_0) {
219            addSecondaryTypes(typeManager, schemaManager, cmisVersion);
220        }
221        return typeManager;
222    }
223
224    protected void addTypesRecursively(TypeManagerImpl typeManager, String name,
225            Map<String, List<String>> typesChildren, Set<String> done, SchemaManager schemaManager,
226            CmisVersion cmisVersion) {
227        if (done.contains(name)) {
228            return;
229        }
230        done.add(name);
231        DocumentType dt = schemaManager.getDocumentType(name);
232        String parentTypeId = NuxeoTypeHelper.getParentTypeId(dt);
233        if (parentTypeId != null) {
234            TypeDefinitionContainer parentType = typeManager.getTypeById(parentTypeId);
235            if (parentType == null) {
236                // if parent was ignored, reparent under cmis:document
237                parentTypeId = BaseTypeId.CMIS_DOCUMENT.value();
238            } else {
239                if (parentType.getTypeDefinition().getBaseTypeId() != BaseTypeId.CMIS_FOLDER && dt.isFolder()) {
240                    // reparent Folderish but child of Document under
241                    // cmis:folder
242                    parentTypeId = BaseTypeId.CMIS_FOLDER.value();
243                }
244            }
245            typeManager.addTypeDefinition(NuxeoTypeHelper.constructDocumentType(dt, parentTypeId, cmisVersion));
246        }
247        // recurse in children
248        List<String> children = typesChildren.get(name);
249        if (children == null) {
250            return;
251        }
252        for (String sub : children) {
253            addTypesRecursively(typeManager, sub, typesChildren, done, schemaManager, cmisVersion);
254        }
255    }
256
257    protected void addSecondaryTypes(TypeManagerImpl typeManager, SchemaManager schemaManager,
258            CmisVersion cmisVersion) {
259        for (CompositeType type : schemaManager.getFacets()) {
260            typeManager.addTypeDefinition(NuxeoTypeHelper.constructSecondaryType(type, cmisVersion), false);
261        }
262    }
263
264    public String getRootFolderId() {
265        return rootFolderId;
266    }
267
268    public RepositoryInfo getRepositoryInfo(String latestChangeLogToken, CallContext callContext) {
269        CmisVersion cmisVersion = callContext.getCmisVersion();
270
271        RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl();
272        repositoryInfo.setId(repositoryId);
273        repositoryInfo.setName("Nuxeo Repository " + repositoryId);
274        repositoryInfo.setDescription("Nuxeo Repository " + repositoryId);
275        repositoryInfo.setCmisVersionSupported(cmisVersion.value());
276        repositoryInfo.setPrincipalAnonymous("Guest"); // TODO
277        repositoryInfo.setPrincipalAnyone(SecurityConstants.EVERYONE);
278        repositoryInfo.setThinClientUri(getBaseURL(callContext));
279        repositoryInfo.setChangesIncomplete(Boolean.FALSE);
280        repositoryInfo.setChangesOnType(Arrays.asList(BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER));
281        repositoryInfo.setLatestChangeLogToken(latestChangeLogToken);
282        repositoryInfo.setVendorName("Nuxeo");
283        repositoryInfo.setProductName("Nuxeo OpenCMIS Connector");
284        String version = Framework.getProperty(Environment.DISTRIBUTION_VERSION, "5.5 dev");
285        repositoryInfo.setProductVersion(version);
286        repositoryInfo.setRootFolder(rootFolderId);
287        repositoryInfo.setExtensionFeature(Collections.<ExtensionFeature> emptyList());
288
289        // capabilities
290
291        RepositoryCapabilitiesImpl caps = new RepositoryCapabilitiesImpl();
292        caps.setAllVersionsSearchable(Boolean.TRUE);
293        caps.setCapabilityAcl(CapabilityAcl.MANAGE);
294        caps.setCapabilityChanges(CapabilityChanges.OBJECTIDSONLY);
295        caps.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.PWCONLY);
296        caps.setCapabilityJoin(supportsJoins ? CapabilityJoin.INNERANDOUTER : CapabilityJoin.NONE);
297        caps.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
298        caps.setCapabilityRendition(CapabilityRenditions.READ);
299        caps.setIsPwcSearchable(Boolean.TRUE);
300        caps.setIsPwcUpdatable(Boolean.TRUE);
301        caps.setSupportsGetDescendants(Boolean.TRUE);
302        caps.setSupportsGetFolderTree(Boolean.TRUE);
303        caps.setSupportsMultifiling(Boolean.FALSE);
304        caps.setSupportsUnfiling(Boolean.FALSE);
305        caps.setSupportsVersionSpecificFiling(Boolean.FALSE);
306        caps.setNewTypeSettableAttributes(new NewTypeSettableAttributesImpl());
307        caps.setCreatablePropertyTypes(new CreatablePropertyTypesImpl());
308        repositoryInfo.setCapabilities(caps);
309
310        // ACL capabilities
311
312        AclCapabilitiesDataImpl aclCaps = new AclCapabilitiesDataImpl();
313        aclCaps.setAclPropagation(AclPropagation.PROPAGATE);
314        aclCaps.setSupportedPermissions(SupportedPermissions.REPOSITORY);
315
316        List<PermissionDefinition> permDefs = new ArrayList<>();
317        addPermissionDefinitions(permDefs);
318        aclCaps.setPermissionDefinitionData(permDefs);
319
320        Map<String, PermissionMapping> permMap = new HashMap<>();
321        addPermissionMapping(permMap, CAN_GET_DESCENDENTS_FOLDER, READ);
322        addPermissionMapping(permMap, CAN_GET_CHILDREN_FOLDER, READ);
323        addPermissionMapping(permMap, CAN_GET_PARENTS_FOLDER, READ);
324        addPermissionMapping(permMap, CAN_GET_FOLDER_PARENT_OBJECT, READ);
325        addPermissionMapping(permMap, CAN_CREATE_DOCUMENT_FOLDER, WRITE);
326        addPermissionMapping(permMap, CAN_CREATE_FOLDER_FOLDER, WRITE);
327        // no CAN_CREATE_POLICY_FOLDER due to spec bug
328        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_SOURCE, READ);
329        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_TARGET, READ);
330        addPermissionMapping(permMap, CAN_GET_PROPERTIES_OBJECT, READ);
331        addPermissionMapping(permMap, CAN_VIEW_CONTENT_OBJECT, READ);
332        addPermissionMapping(permMap, CAN_UPDATE_PROPERTIES_OBJECT, WRITE);
333        addPermissionMapping(permMap, CAN_MOVE_OBJECT, WRITE);
334        addPermissionMapping(permMap, CAN_MOVE_TARGET, WRITE);
335        addPermissionMapping(permMap, CAN_MOVE_SOURCE, WRITE);
336        addPermissionMapping(permMap, CAN_DELETE_OBJECT, WRITE);
337        addPermissionMapping(permMap, CAN_DELETE_TREE_FOLDER, WRITE);
338        addPermissionMapping(permMap, CAN_SET_CONTENT_DOCUMENT, WRITE);
339        addPermissionMapping(permMap, CAN_DELETE_CONTENT_DOCUMENT, WRITE);
340        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_OBJECT, WRITE);
341        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_FOLDER, WRITE);
342        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_OBJECT, WRITE);
343        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_FOLDER, WRITE);
344        addPermissionMapping(permMap, CAN_CHECKOUT_DOCUMENT, WRITE);
345        addPermissionMapping(permMap, CAN_CANCEL_CHECKOUT_DOCUMENT, WRITE);
346        addPermissionMapping(permMap, CAN_CHECKIN_DOCUMENT, WRITE);
347        addPermissionMapping(permMap, CAN_GET_ALL_VERSIONS_VERSION_SERIES, READ);
348        addPermissionMapping(permMap, CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, READ);
349        addPermissionMapping(permMap, CAN_ADD_POLICY_OBJECT, WRITE);
350        addPermissionMapping(permMap, CAN_ADD_POLICY_POLICY, WRITE);
351        addPermissionMapping(permMap, CAN_REMOVE_POLICY_OBJECT, WRITE);
352        addPermissionMapping(permMap, CAN_REMOVE_POLICY_POLICY, WRITE);
353        addPermissionMapping(permMap, CAN_GET_APPLIED_POLICIES_OBJECT, READ);
354        addPermissionMapping(permMap, CAN_GET_ACL_OBJECT, READ);
355        addPermissionMapping(permMap, CAN_APPLY_ACL_OBJECT, ALL);
356        aclCaps.setPermissionMappingData(permMap);
357
358        repositoryInfo.setAclCapabilities(aclCaps);
359
360        return repositoryInfo;
361    }
362
363    @SuppressWarnings("unchecked")
364    protected static void addPermissionDefinitions(List<PermissionDefinition> permDefs) {
365        addPermissionDefinition(permDefs, READ, "Read"); // = Nuxeo Read
366        addPermissionDefinition(permDefs, WRITE, "Write"); // = Nuxeo ReadWrite
367        addPermissionDefinition(permDefs, ALL, "All"); // = Nuxeo Everything
368        addPermissionDefinition(permDefs, NUXEO_READ_REMOVE, "Remove");
369
370        Set<String> done = new HashSet<>();
371        done.add(SecurityConstants.READ);
372        done.add(SecurityConstants.READ_WRITE);
373        done.add(SecurityConstants.EVERYTHING);
374        done.add(NUXEO_READ_REMOVE);
375
376        /*
377         * Add Nuxeo-specific permissions registered through the permissionsVisibility extension point.
378         */
379
380        DefaultPermissionProvider permissionProvider = (DefaultPermissionProvider) Framework.getService(PermissionProvider.class);
381        permissionProvider.getUserVisiblePermissionDescriptors(); // init var
382        Map<String, PermissionVisibilityDescriptor> map;
383        try {
384            Field f;
385            f = DefaultPermissionProvider.class.getDeclaredField("mergedPermissionsVisibility");
386            f.setAccessible(true);
387            map = (Map<String, PermissionVisibilityDescriptor>) f.get(permissionProvider);
388        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
389            throw new RuntimeException(e);
390        }
391        // iterate for all types regisited, not just the default ""
392        for (Entry<String, PermissionVisibilityDescriptor> en : map.entrySet()) {
393            for (String permission : en.getValue().getSortedItems()) {
394                if (!done.add(permission)) {
395                    continue;
396                }
397                addPermissionDefinition(permDefs, permission, permission);
398            }
399        }
400    }
401
402    protected static void addPermissionDefinition(List<PermissionDefinition> permDefs, String permission,
403            String description) {
404        PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
405        pd.setId(permission);
406        pd.setDescription(description);
407        permDefs.add(pd);
408    }
409
410    protected static void addPermissionMapping(Map<String, PermissionMapping> permMap, String key, String permission) {
411        PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
412        pm.setKey(key);
413        pm.setPermissions(Collections.singletonList(permission));
414        permMap.put(key, pm);
415    }
416
417    /** Returns the server base URL (including context). */
418    private static String getBaseURL(CallContext callContext) {
419        HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST);
420        if (request != null) {
421            String baseURL = getServerURL(request);
422            String contextPath = request.getContextPath();
423            if (contextPath == null) {
424                contextPath = Framework.getProperty(NUXEO_CONTEXT_PATH_PROP, NUXEO_CONTEXT_PATH_DEFAULT);
425            }
426            // add context path
427            return baseURL + contextPath + '/';
428        } else {
429            return Framework.getProperty(NUXEO_URL_PROP);
430        }
431    }
432
433    /**
434     * Returns the server URL according to virtual hosting headers (without trailing slash).
435     */
436    private static String getServerURL(HttpServletRequest request) {
437        String url = null;
438        // Detect Nuxeo specific header for VH
439        String nuxeoVH = request.getHeader(NUXEO_VH_HEADER);
440        if (nuxeoVH == null) {
441            nuxeoVH = Framework.getProperty(VH_PARAM);
442        }
443        if (nuxeoVH != null && nuxeoVH.startsWith("http")) {
444            url = nuxeoVH;
445        } else {
446            // default values
447            String scheme = request.getScheme();
448            String serverName = request.getServerName();
449            int serverPort = request.getServerPort();
450            // Detect virtual hosting based in standard header
451            String forwardedHost = request.getHeader(X_FORWARDED_HOST);
452            if (forwardedHost != null) {
453                if (forwardedHost.contains(":")) {
454                    String[] split = forwardedHost.split(":");
455                    serverName = split[0];
456                    serverPort = Integer.parseInt(split[1]);
457                } else {
458                    serverName = forwardedHost;
459                    serverPort = 80; // fallback
460                }
461            }
462            url = buildURL(scheme, serverName, serverPort);
463        }
464        // strip trailing slash
465        if (url.endsWith("/")) {
466            url = url.substring(0, url.length() - 1);
467        }
468        return url;
469    }
470
471    /** Builds an URL (without trailing slash). */
472    private static String buildURL(String scheme, String serverName, int serverPort) {
473        StringBuilder sb = new StringBuilder();
474        sb.append(scheme);
475        sb.append("://");
476        sb.append(serverName);
477        if (serverPort != 0) {
478            if ("http".equals(scheme) && serverPort != 80 || "https".equals(scheme) && serverPort != 443) {
479                sb.append(':');
480                sb.append(serverPort);
481            }
482        }
483        return sb.toString();
484    }
485
486}