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