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 SUPPORTS_PROXIES_PROP = "org.nuxeo.cmis.proxies";
125
126    public static final String ELASTICSEARCH_PROP = "org.nuxeo.cmis.elasticsearch";
127
128    private static final String NUXEO_CONTEXT_PATH_PROP = "org.nuxeo.ecm.contextPath";
129
130    private static final String NUXEO_CONTEXT_PATH_DEFAULT = "/nuxeo";
131
132    private static final String X_FORWARDED_HOST = "x-forwarded-host";
133
134    private static final String NUXEO_VH_HEADER = "nuxeo-virtual-host";
135
136    private static final String VH_PARAM = "nuxeo.virtual.host";
137
138    public static final String NUXEO_READ_REMOVE = "ReadRemove";
139
140    protected final String repositoryId;
141
142    protected final String rootFolderId;
143
144    protected boolean supportsJoins;
145
146    protected boolean supportsProxies;
147
148    protected boolean useElasticsearch;
149
150    protected Map<CmisVersion, TypeManagerImpl> typeManagerByCmisVersion = new HashMap<>();
151
152    public NuxeoRepository(String repositoryId, String rootFolderId) {
153        this.repositoryId = repositoryId;
154        this.rootFolderId = rootFolderId;
155        if (Framework.isBooleanPropertyTrue(SUPPORTS_JOINS_PROP)) {
156            setSupportsJoins(true);
157        }
158        if (Framework.isBooleanPropertyTrue(SUPPORTS_PROXIES_PROP)) {
159            setSupportsProxies(true);
160        }
161        if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_PROP)) {
162            setUseElasticsearch(true);
163        }
164    }
165
166    public void setSupportsJoins(boolean supportsJoins) {
167        this.supportsJoins = supportsJoins;
168    }
169
170    public boolean supportsJoins() {
171        return supportsJoins;
172    }
173
174    public void setSupportsProxies(boolean supportsProxies) {
175        this.supportsProxies = supportsProxies;
176    }
177
178    public boolean supportsProxies() {
179        return supportsProxies;
180    }
181
182    public void setUseElasticsearch(boolean useElasticsearch) {
183        this.useElasticsearch = useElasticsearch;
184    }
185
186    public boolean useElasticsearch() {
187        return useElasticsearch;
188    }
189
190    public String getId() {
191        return repositoryId;
192    }
193
194    // no need to have it synchronized
195    public TypeManagerImpl getTypeManager(CmisVersion cmisVersion) {
196        TypeManagerImpl typeManager = typeManagerByCmisVersion.get(cmisVersion);
197        if (typeManager == null) {
198            typeManager = initializeTypes(cmisVersion);
199            typeManagerByCmisVersion.put(cmisVersion, typeManager);
200        }
201        return typeManager;
202    }
203
204    protected TypeManagerImpl initializeTypes(CmisVersion cmisVersion) {
205        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
206        // scan the types to find super/inherited relationships
207        Map<String, List<String>> typesChildren = new HashMap<>();
208        for (DocumentType dt : schemaManager.getDocumentTypes()) {
209            org.nuxeo.ecm.core.schema.types.Type st = dt.getSuperType();
210            if (st == null) {
211                continue;
212            }
213            String name = st.getName();
214            List<String> siblings = typesChildren.get(name);
215            if (siblings == null) {
216                siblings = new LinkedList<>();
217                typesChildren.put(name, siblings);
218            }
219            siblings.add(dt.getName());
220        }
221        // convert the transitive closure for Folder and Document subtypes
222        Set<String> done = new HashSet<>();
223        TypeManagerImpl typeManager = new TypeManagerImpl();
224        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_DOCUMENT, schemaManager, cmisVersion));
225        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_FOLDER, schemaManager, cmisVersion));
226        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_RELATIONSHIP, schemaManager, cmisVersion));
227        if (cmisVersion != CmisVersion.CMIS_1_0) {
228            typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_SECONDARY, schemaManager, cmisVersion));
229        }
230        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_DOCUMENT, typesChildren, done, schemaManager, cmisVersion);
231        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_FOLDER, typesChildren, done, schemaManager, cmisVersion);
232        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_RELATION, typesChildren, done, schemaManager, cmisVersion);
233        if (cmisVersion != CmisVersion.CMIS_1_0) {
234            addSecondaryTypes(typeManager, schemaManager, cmisVersion);
235        }
236        return typeManager;
237    }
238
239    protected void addTypesRecursively(TypeManagerImpl typeManager, String name,
240            Map<String, List<String>> typesChildren, Set<String> done, SchemaManager schemaManager,
241            CmisVersion cmisVersion) {
242        if (done.contains(name)) {
243            return;
244        }
245        done.add(name);
246        DocumentType dt = schemaManager.getDocumentType(name);
247        String parentTypeId = NuxeoTypeHelper.getParentTypeId(dt);
248        if (parentTypeId != null) {
249            TypeDefinitionContainer parentType = typeManager.getTypeById(parentTypeId);
250            if (parentType == null) {
251                // if parent was ignored, reparent under cmis:document
252                parentTypeId = BaseTypeId.CMIS_DOCUMENT.value();
253            } else {
254                if (parentType.getTypeDefinition().getBaseTypeId() != BaseTypeId.CMIS_FOLDER && dt.isFolder()) {
255                    // reparent Folderish but child of Document under
256                    // cmis:folder
257                    parentTypeId = BaseTypeId.CMIS_FOLDER.value();
258                }
259            }
260            typeManager.addTypeDefinition(NuxeoTypeHelper.constructDocumentType(dt, parentTypeId, cmisVersion));
261        }
262        // recurse in children
263        List<String> children = typesChildren.get(name);
264        if (children == null) {
265            return;
266        }
267        for (String sub : children) {
268            addTypesRecursively(typeManager, sub, typesChildren, done, schemaManager, cmisVersion);
269        }
270    }
271
272    protected void addSecondaryTypes(TypeManagerImpl typeManager, SchemaManager schemaManager,
273            CmisVersion cmisVersion) {
274        for (CompositeType type : schemaManager.getFacets()) {
275            typeManager.addTypeDefinition(NuxeoTypeHelper.constructSecondaryType(type, cmisVersion), false);
276        }
277    }
278
279    public String getRootFolderId() {
280        return rootFolderId;
281    }
282
283    public RepositoryInfo getRepositoryInfo(String latestChangeLogToken, CallContext callContext) {
284        CmisVersion cmisVersion = callContext.getCmisVersion();
285
286        RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl();
287        repositoryInfo.setId(repositoryId);
288        repositoryInfo.setName("Nuxeo Repository " + repositoryId);
289        repositoryInfo.setDescription("Nuxeo Repository " + repositoryId);
290        repositoryInfo.setCmisVersionSupported(cmisVersion.value());
291        repositoryInfo.setPrincipalAnonymous("Guest"); // TODO
292        repositoryInfo.setPrincipalAnyone(SecurityConstants.EVERYONE);
293        repositoryInfo.setThinClientUri(getBaseURL(callContext));
294        repositoryInfo.setChangesIncomplete(Boolean.FALSE);
295        repositoryInfo.setChangesOnType(Arrays.asList(BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER));
296        repositoryInfo.setLatestChangeLogToken(latestChangeLogToken);
297        repositoryInfo.setVendorName("Nuxeo");
298        repositoryInfo.setProductName("Nuxeo OpenCMIS Connector");
299        String version = Framework.getProperty(Environment.DISTRIBUTION_VERSION, "5.5 dev");
300        repositoryInfo.setProductVersion(version);
301        repositoryInfo.setRootFolder(rootFolderId);
302        repositoryInfo.setExtensionFeature(Collections.<ExtensionFeature> emptyList());
303
304        // capabilities
305
306        RepositoryCapabilitiesImpl caps = new RepositoryCapabilitiesImpl();
307        caps.setAllVersionsSearchable(Boolean.TRUE);
308        caps.setCapabilityAcl(CapabilityAcl.MANAGE);
309        caps.setCapabilityChanges(CapabilityChanges.OBJECTIDSONLY);
310        caps.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.PWCONLY);
311        caps.setCapabilityJoin(supportsJoins ? CapabilityJoin.INNERANDOUTER : CapabilityJoin.NONE);
312        caps.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
313        caps.setCapabilityRendition(CapabilityRenditions.READ);
314        caps.setIsPwcSearchable(Boolean.TRUE);
315        caps.setIsPwcUpdatable(Boolean.TRUE);
316        caps.setSupportsGetDescendants(Boolean.TRUE);
317        caps.setSupportsGetFolderTree(Boolean.TRUE);
318        caps.setSupportsMultifiling(Boolean.FALSE);
319        caps.setSupportsUnfiling(Boolean.FALSE);
320        caps.setSupportsVersionSpecificFiling(Boolean.FALSE);
321        caps.setNewTypeSettableAttributes(new NewTypeSettableAttributesImpl());
322        caps.setCreatablePropertyTypes(new CreatablePropertyTypesImpl());
323        repositoryInfo.setCapabilities(caps);
324
325        // ACL capabilities
326
327        AclCapabilitiesDataImpl aclCaps = new AclCapabilitiesDataImpl();
328        aclCaps.setAclPropagation(AclPropagation.PROPAGATE);
329        aclCaps.setSupportedPermissions(SupportedPermissions.REPOSITORY);
330
331        List<PermissionDefinition> permDefs = new ArrayList<>();
332        addPermissionDefinitions(permDefs);
333        aclCaps.setPermissionDefinitionData(permDefs);
334
335        Map<String, PermissionMapping> permMap = new HashMap<>();
336        addPermissionMapping(permMap, CAN_GET_DESCENDENTS_FOLDER, READ);
337        addPermissionMapping(permMap, CAN_GET_CHILDREN_FOLDER, READ);
338        addPermissionMapping(permMap, CAN_GET_PARENTS_FOLDER, READ);
339        addPermissionMapping(permMap, CAN_GET_FOLDER_PARENT_OBJECT, READ);
340        addPermissionMapping(permMap, CAN_CREATE_DOCUMENT_FOLDER, WRITE);
341        addPermissionMapping(permMap, CAN_CREATE_FOLDER_FOLDER, WRITE);
342        // no CAN_CREATE_POLICY_FOLDER due to spec bug
343        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_SOURCE, READ);
344        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_TARGET, READ);
345        addPermissionMapping(permMap, CAN_GET_PROPERTIES_OBJECT, READ);
346        addPermissionMapping(permMap, CAN_VIEW_CONTENT_OBJECT, READ);
347        addPermissionMapping(permMap, CAN_UPDATE_PROPERTIES_OBJECT, WRITE);
348        addPermissionMapping(permMap, CAN_MOVE_OBJECT, WRITE);
349        addPermissionMapping(permMap, CAN_MOVE_TARGET, WRITE);
350        addPermissionMapping(permMap, CAN_MOVE_SOURCE, WRITE);
351        addPermissionMapping(permMap, CAN_DELETE_OBJECT, WRITE);
352        addPermissionMapping(permMap, CAN_DELETE_TREE_FOLDER, WRITE);
353        addPermissionMapping(permMap, CAN_SET_CONTENT_DOCUMENT, WRITE);
354        addPermissionMapping(permMap, CAN_DELETE_CONTENT_DOCUMENT, WRITE);
355        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_OBJECT, WRITE);
356        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_FOLDER, WRITE);
357        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_OBJECT, WRITE);
358        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_FOLDER, WRITE);
359        addPermissionMapping(permMap, CAN_CHECKOUT_DOCUMENT, WRITE);
360        addPermissionMapping(permMap, CAN_CANCEL_CHECKOUT_DOCUMENT, WRITE);
361        addPermissionMapping(permMap, CAN_CHECKIN_DOCUMENT, WRITE);
362        addPermissionMapping(permMap, CAN_GET_ALL_VERSIONS_VERSION_SERIES, READ);
363        addPermissionMapping(permMap, CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, READ);
364        addPermissionMapping(permMap, CAN_ADD_POLICY_OBJECT, WRITE);
365        addPermissionMapping(permMap, CAN_ADD_POLICY_POLICY, WRITE);
366        addPermissionMapping(permMap, CAN_REMOVE_POLICY_OBJECT, WRITE);
367        addPermissionMapping(permMap, CAN_REMOVE_POLICY_POLICY, WRITE);
368        addPermissionMapping(permMap, CAN_GET_APPLIED_POLICIES_OBJECT, READ);
369        addPermissionMapping(permMap, CAN_GET_ACL_OBJECT, READ);
370        addPermissionMapping(permMap, CAN_APPLY_ACL_OBJECT, ALL);
371        aclCaps.setPermissionMappingData(permMap);
372
373        repositoryInfo.setAclCapabilities(aclCaps);
374
375        return repositoryInfo;
376    }
377
378    @SuppressWarnings("unchecked")
379    protected static void addPermissionDefinitions(List<PermissionDefinition> permDefs) {
380        addPermissionDefinition(permDefs, READ, "Read"); // = Nuxeo Read
381        addPermissionDefinition(permDefs, WRITE, "Write"); // = Nuxeo ReadWrite
382        addPermissionDefinition(permDefs, ALL, "All"); // = Nuxeo Everything
383        addPermissionDefinition(permDefs, NUXEO_READ_REMOVE, "Remove");
384
385        Set<String> done = new HashSet<>();
386        done.add(SecurityConstants.READ);
387        done.add(SecurityConstants.READ_WRITE);
388        done.add(SecurityConstants.EVERYTHING);
389        done.add(NUXEO_READ_REMOVE);
390
391        /*
392         * Add Nuxeo-specific permissions registered through the permissionsVisibility extension point.
393         */
394
395        DefaultPermissionProvider permissionProvider = (DefaultPermissionProvider) Framework.getService(PermissionProvider.class);
396        permissionProvider.getUserVisiblePermissionDescriptors(); // init var
397        Map<String, PermissionVisibilityDescriptor> map;
398        try {
399            Field f;
400            f = DefaultPermissionProvider.class.getDeclaredField("mergedPermissionsVisibility");
401            f.setAccessible(true);
402            map = (Map<String, PermissionVisibilityDescriptor>) f.get(permissionProvider);
403        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
404            throw new RuntimeException(e);
405        }
406        // iterate for all types regisited, not just the default ""
407        for (Entry<String, PermissionVisibilityDescriptor> en : map.entrySet()) {
408            for (String permission : en.getValue().getSortedItems()) {
409                if (!done.add(permission)) {
410                    continue;
411                }
412                addPermissionDefinition(permDefs, permission, permission);
413            }
414        }
415    }
416
417    protected static void addPermissionDefinition(List<PermissionDefinition> permDefs, String permission,
418            String description) {
419        PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
420        pd.setId(permission);
421        pd.setDescription(description);
422        permDefs.add(pd);
423    }
424
425    protected static void addPermissionMapping(Map<String, PermissionMapping> permMap, String key, String permission) {
426        PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
427        pm.setKey(key);
428        pm.setPermissions(Collections.singletonList(permission));
429        permMap.put(key, pm);
430    }
431
432    /** Returns the server base URL (including context). */
433    private static String getBaseURL(CallContext callContext) {
434        HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST);
435        if (request != null) {
436            String baseURL = getServerURL(request);
437            String contextPath = request.getContextPath();
438            if (contextPath == null) {
439                contextPath = Framework.getProperty(NUXEO_CONTEXT_PATH_PROP, NUXEO_CONTEXT_PATH_DEFAULT);
440            }
441            // add context path
442            return baseURL + contextPath + '/';
443        } else {
444            return Framework.getProperty(NUXEO_URL_PROP);
445        }
446    }
447
448    /**
449     * Returns the server URL according to virtual hosting headers (without trailing slash).
450     */
451    private static String getServerURL(HttpServletRequest request) {
452        String url = null;
453        // Detect Nuxeo specific header for VH
454        String nuxeoVH = request.getHeader(NUXEO_VH_HEADER);
455        if (nuxeoVH == null) {
456            nuxeoVH = Framework.getProperty(VH_PARAM);
457        }
458        if (nuxeoVH != null && nuxeoVH.startsWith("http")) {
459            url = nuxeoVH;
460        } else {
461            // default values
462            String scheme = request.getScheme();
463            String serverName = request.getServerName();
464            int serverPort = request.getServerPort();
465            // Detect virtual hosting based in standard header
466            String forwardedHost = request.getHeader(X_FORWARDED_HOST);
467            if (forwardedHost != null) {
468                if (forwardedHost.contains(":")) {
469                    String[] split = forwardedHost.split(":");
470                    serverName = split[0];
471                    serverPort = Integer.parseInt(split[1]);
472                } else {
473                    serverName = forwardedHost;
474                    serverPort = 80; // fallback
475                }
476            }
477            url = buildURL(scheme, serverName, serverPort);
478        }
479        // strip trailing slash
480        if (url.endsWith("/")) {
481            url = url.substring(0, url.length() - 1);
482        }
483        return url;
484    }
485
486    /** Builds an URL (without trailing slash). */
487    private static String buildURL(String scheme, String serverName, int serverPort) {
488        StringBuilder sb = new StringBuilder();
489        sb.append(scheme);
490        sb.append("://");
491        sb.append(serverName);
492        if (serverPort != 0) {
493            if ("http".equals(scheme) && serverPort != 80 || "https".equals(scheme) && serverPort != 443) {
494                sb.append(':');
495                sb.append(serverPort);
496            }
497        }
498        return sb.toString();
499    }
500
501}