001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.cmis;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.CmsVfsResourceNotFoundException;
034import org.opencms.lock.CmsLock;
035import org.opencms.main.CmsException;
036import org.opencms.main.CmsIllegalArgumentException;
037import org.opencms.security.CmsAccessControlEntry;
038import org.opencms.security.CmsPermissionSet;
039import org.opencms.security.CmsPrincipal;
040import org.opencms.security.CmsRole;
041import org.opencms.security.CmsSecurityException;
042import org.opencms.util.CmsUUID;
043
044import java.math.BigDecimal;
045import java.math.BigInteger;
046import java.util.ArrayList;
047import java.util.GregorianCalendar;
048import java.util.LinkedHashSet;
049import java.util.List;
050import java.util.Set;
051import java.util.TimeZone;
052
053import org.apache.chemistry.opencmis.commons.PropertyIds;
054import org.apache.chemistry.opencmis.commons.data.Properties;
055import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
056import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
057import org.apache.chemistry.opencmis.commons.enums.Action;
058import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
059import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
060import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
061import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException;
062import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
063import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
064import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
065import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
066import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
067import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
068import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
069import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
070import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
071
072/**
073 * Utility class for operations which are frequently used by CMIS service methods.<p>
074 */
075public final class CmsCmisUtil {
076
077    /**
078     * Private constructor to prevent instantiation.<p>
079     */
080    private CmsCmisUtil() { /* Prevent instantiation. */
081
082    }
083
084    /**
085     * Adds an action to a set of actions if a condition is fulfilled.<p>
086     *
087     * @param aas the set of actions
088     * @param action the action to add
089     * @param condition the value of the condition for adding the action
090     */
091    public static void addAction(Set<Action> aas, Action action, boolean condition) {
092
093        if (condition) {
094            aas.add(action);
095        }
096    }
097
098    /**
099     * Helper method to add the dynamic properties for a resource.<p>
100     *
101     * @param cms the current CMS context
102     * @param typeManager the type manager instance
103     * @param props the properties to which the dynamic properties should be added
104     * @param typeId the type id
105     * @param resource the resource
106     * @param filter the property filter
107     */
108    public static void addDynamicProperties(
109        CmsObject cms,
110        CmsCmisTypeManager typeManager,
111        PropertiesImpl props,
112        String typeId,
113        CmsResource resource,
114        Set<String> filter) {
115
116        List<I_CmsPropertyProvider> providers = typeManager.getPropertyProviders();
117        for (I_CmsPropertyProvider provider : providers) {
118            String propertyName = CmsCmisTypeManager.PROPERTY_PREFIX_DYNAMIC + provider.getName();
119            if (!checkAddProperty(typeManager, props, typeId, filter, propertyName)) {
120                continue;
121            }
122            try {
123                String value = provider.getPropertyValue(cms, resource);
124                addPropertyString(typeManager, props, typeId, filter, propertyName, value);
125            } catch (Throwable t) {
126                addPropertyString(typeManager, props, typeId, filter, propertyName, null);
127            }
128        }
129    }
130
131    /**
132     * Adds bigint property to a PropertiesImpl.<p>
133     *
134     *
135     * @param typeManager the type manager
136     * @param props the properties
137     * @param typeId the type id
138     * @param filter the property filter string
139     * @param id the property id
140     * @param value the property value
141     */
142    public static void addPropertyBigInteger(
143        CmsCmisTypeManager typeManager,
144        PropertiesImpl props,
145        String typeId,
146        Set<String> filter,
147        String id,
148        BigInteger value) {
149
150        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
151            return;
152        }
153
154        props.addProperty(new PropertyIntegerImpl(id, value));
155    }
156
157    /**
158     * Adds a boolean property to a PropertiesImpl.<p>
159     *
160     * @param typeManager
161     * @param props the properties
162     * @param typeId the type id
163     * @param filter the property filter string
164     * @param id the property id
165     * @param value the property value
166     */
167    public static void addPropertyBoolean(
168        CmsCmisTypeManager typeManager,
169        PropertiesImpl props,
170        String typeId,
171        Set<String> filter,
172        String id,
173        boolean value) {
174
175        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
176            return;
177        }
178
179        props.addProperty(new PropertyBooleanImpl(id, Boolean.valueOf(value)));
180    }
181
182    /**
183     * Adds a date/time property to a PropertiesImpl.<p>
184     *
185     * @param typeManager the type manager
186     * @param props the properties
187     * @param typeId the type id
188     * @param filter the property filter string
189     * @param id the property id
190     * @param value the property value
191     */
192    public static void addPropertyDateTime(
193        CmsCmisTypeManager typeManager,
194        PropertiesImpl props,
195        String typeId,
196        Set<String> filter,
197        String id,
198        GregorianCalendar value) {
199
200        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
201            return;
202        }
203
204        props.addProperty(new PropertyDateTimeImpl(id, value));
205    }
206
207    /**
208     * Adds the default value of property if defined.
209     *
210     * @param props the Properties object
211     * @param propDef the property definition
212     *
213     * @return true if the property could be added
214     */
215    @SuppressWarnings("unchecked")
216    public static boolean addPropertyDefault(PropertiesImpl props, PropertyDefinition<?> propDef) {
217
218        if ((props == null) || (props.getProperties() == null)) {
219            throw new IllegalArgumentException("Props must not be null!");
220        }
221
222        if (propDef == null) {
223            return false;
224        }
225
226        List<?> defaultValue = propDef.getDefaultValue();
227        if ((defaultValue != null) && (!defaultValue.isEmpty())) {
228            switch (propDef.getPropertyType()) {
229                case BOOLEAN:
230                    props.addProperty(new PropertyBooleanImpl(propDef.getId(), (List<Boolean>)defaultValue));
231                    break;
232                case DATETIME:
233                    props.addProperty(new PropertyDateTimeImpl(propDef.getId(), (List<GregorianCalendar>)defaultValue));
234                    break;
235                case DECIMAL:
236                    props.addProperty(new PropertyDecimalImpl(propDef.getId(), (List<BigDecimal>)defaultValue));
237                    break;
238                case HTML:
239                    props.addProperty(new PropertyHtmlImpl(propDef.getId(), (List<String>)defaultValue));
240                    break;
241                case ID:
242                    props.addProperty(new PropertyIdImpl(propDef.getId(), (List<String>)defaultValue));
243                    break;
244                case INTEGER:
245                    props.addProperty(new PropertyIntegerImpl(propDef.getId(), (List<BigInteger>)defaultValue));
246                    break;
247                case STRING:
248                    props.addProperty(new PropertyStringImpl(propDef.getId(), (List<String>)defaultValue));
249                    break;
250                case URI:
251                    props.addProperty(new PropertyUriImpl(propDef.getId(), (List<String>)defaultValue));
252                    break;
253                default:
254                    throw new RuntimeException("Unknown datatype! Spec change?");
255            }
256
257            return true;
258        }
259
260        return false;
261    }
262
263    /**
264     * Helper method for adding an id-valued property.<p>
265     *
266     * @param typeManager the type manager
267     * @param props the properties to add to
268     * @param typeId the type id
269     * @param filter the property filter
270     * @param id the property id
271     * @param value the property value
272     */
273    public static void addPropertyId(
274        CmsCmisTypeManager typeManager,
275        PropertiesImpl props,
276        String typeId,
277        Set<String> filter,
278        String id,
279        String value) {
280
281        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
282            return;
283        }
284        PropertyIdImpl result = new PropertyIdImpl(id, value);
285        result.setQueryName(id);
286
287        props.addProperty(result);
288    }
289
290    /**
291     * Helper method for adding an id-list-valued property.<p>
292     *
293     * @param typeManager
294     * @param props the properties to add to
295     * @param typeId the type id
296     * @param filter the property filter
297     * @param id the property id
298     * @param value the property value
299     */
300    public static void addPropertyIdList(
301        CmsCmisTypeManager typeManager,
302        PropertiesImpl props,
303        String typeId,
304        Set<String> filter,
305        String id,
306        List<String> value) {
307
308        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
309            return;
310        }
311
312        props.addProperty(new PropertyIdImpl(id, value));
313    }
314
315    /**
316     * Adds an integer property to a PropertiesImpl.<p>
317     *
318     * @param typeManager the type manager
319     * @param props the properties
320     * @param typeId the type id
321     * @param filter the property filter string
322     * @param id the property id
323     * @param value the property value
324     */
325    public static void addPropertyInteger(
326        CmsCmisTypeManager typeManager,
327        PropertiesImpl props,
328        String typeId,
329        Set<String> filter,
330        String id,
331        long value) {
332
333        addPropertyBigInteger(typeManager, props, typeId, filter, id, BigInteger.valueOf(value));
334    }
335
336    /**
337     * Adds a string property to a PropertiesImpl.<p>
338     *
339     * @param typeManager
340     * @param props the properties
341     * @param typeId the type id
342     * @param filter the property filter string
343     * @param id the property id
344     * @param value the property value
345     */
346    public static void addPropertyString(
347        CmsCmisTypeManager typeManager,
348        PropertiesImpl props,
349        String typeId,
350        Set<String> filter,
351        String id,
352        String value) {
353
354        if (!checkAddProperty(typeManager, props, typeId, filter, id)) {
355            return;
356        }
357        PropertyStringImpl result = new PropertyStringImpl(id, value);
358        result.setQueryName(id);
359        props.addProperty(result);
360    }
361
362    /**
363     * Checks whether a property can be added to a Properties.
364     *
365     * @param typeManager
366     * @param properties the properties object
367     * @param typeId the type id
368     * @param filter the property filter
369     * @param id the property id
370     *
371     * @return true if the property should be added
372     */
373    public static boolean checkAddProperty(
374        CmsCmisTypeManager typeManager,
375        Properties properties,
376        String typeId,
377        Set<String> filter,
378        String id) {
379
380        if ((properties == null) || (properties.getProperties() == null)) {
381            throw new IllegalArgumentException("Properties must not be null!");
382        }
383
384        if (id == null) {
385            throw new IllegalArgumentException("Id must not be null!");
386        }
387
388        TypeDefinition type = typeManager.getType(typeId);
389        if (type == null) {
390            throw new IllegalArgumentException("Unknown type: " + typeId);
391        }
392        if (!type.getPropertyDefinitions().containsKey(id)) {
393            throw new IllegalArgumentException("Unknown property: " + id);
394        }
395
396        String queryName = type.getPropertyDefinitions().get(id).getQueryName();
397
398        if ((queryName != null) && (filter != null)) {
399            if (!filter.contains(queryName)) {
400                return false;
401            } else {
402                filter.remove(queryName);
403            }
404        }
405
406        return true;
407    }
408
409    /**
410     * Checks whether a name is a valid OpenCms resource name and throws an exception otherwise.<p>
411     *
412     * @param name the name to check
413     */
414    public static void checkResourceName(String name) {
415
416        try {
417            CmsResource.checkResourceName(name);
418        } catch (CmsIllegalArgumentException e) {
419            throw new CmisNameConstraintViolationException(e.getLocalizedMessage(), e);
420        }
421    }
422
423    /**
424     * Tries to lock a resource and throws an exception if it can't be locked.<p>
425     *
426     * Returns true only if the resource wasn't already locked before.<p>
427     *
428     * @param cms the CMS context
429     * @param resource the resource to lock
430     * @return true if the resource wasn't already locked
431     *
432     * @throws CmsException if something goes wrong
433     */
434    public static boolean ensureLock(CmsObject cms, CmsResource resource) throws CmsException {
435
436        CmsLock lock = cms.getLock(resource);
437        if (lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) {
438            return false;
439        }
440        cms.lockResourceTemporary(resource);
441        return true;
442    }
443
444    /**
445     * Gets a user-readable name for a principal id read from an ACE.<p>
446     *
447     * @param cms the current CMS context
448     * @param principalId the principal id from the ACE
449     * @return the name of the principle
450     */
451    public static String getAcePrincipalName(CmsObject cms, CmsUUID principalId) {
452
453        if (CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.equals(principalId)) {
454            return CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME;
455        }
456        if (CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID.equals(principalId)) {
457            return CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_NAME;
458        }
459        CmsRole role = CmsRole.valueOfId(principalId);
460        if (role != null) {
461            return role.getRoleName();
462        }
463        try {
464            return CmsPrincipal.readPrincipalIncludingHistory(cms, principalId).getName();
465        } catch (CmsException e) {
466            return "" + principalId;
467        }
468    }
469
470    /**
471     * Converts an OpenCms ACE to a list of basic CMIS permissions.<p>
472     *
473     * @param ace the access control entry
474     *
475     * @return the list of permissions
476     */
477    public static List<String> getCmisPermissions(CmsAccessControlEntry ace) {
478
479        int permissionBits = ace.getPermissions().getPermissions();
480        List<String> result = new ArrayList<String>();
481        if (0 != (permissionBits & CmsPermissionSet.PERMISSION_READ)) {
482            result.add(A_CmsCmisRepository.CMIS_READ);
483        }
484        if (0 != (permissionBits & CmsPermissionSet.PERMISSION_WRITE)) {
485            result.add(A_CmsCmisRepository.CMIS_WRITE);
486        }
487        int all = CmsPermissionSet.PERMISSION_WRITE
488            | CmsPermissionSet.PERMISSION_READ
489            | CmsPermissionSet.PERMISSION_CONTROL
490            | CmsPermissionSet.PERMISSION_DIRECT_PUBLISH;
491        if ((permissionBits & all) == all) {
492            result.add(A_CmsCmisRepository.CMIS_ALL);
493        }
494        return result;
495    }
496
497    /**
498     * Converts an OpenCms access control entry to a list of CMIS permissions which represent native OpenCms permissions.<p>
499     *
500     * @param ace the access control entry
501     * @return the list of permissions for the entry
502     */
503    public static List<String> getNativePermissions(CmsAccessControlEntry ace) {
504
505        List<String> result = getNativePermissions(ace.getPermissions().getAllowedPermissions(), false);
506        result.addAll(getNativePermissions(ace.getPermissions().getDeniedPermissions(), true));
507        return result;
508    }
509
510    /**
511     * Converts an OpenCms access control bitset to a list of CMIS permissions representing native OpenCms permissions.<p>
512     *
513     * @param permissionBits the permission bits
514     * @param denied if the permission bitset refers to a list of denied rather than allowed permissions
515     *
516     * @return the list of native permissions
517     */
518    public static List<String> getNativePermissions(int permissionBits, boolean denied) {
519
520        List<String> result = new ArrayList<String>();
521        String prefix = denied ? "opencms:deny-" : "opencms:";
522        if ((permissionBits & CmsPermissionSet.PERMISSION_READ) != 0) {
523            result.add(prefix + "read");
524        }
525        if ((permissionBits & CmsPermissionSet.PERMISSION_WRITE) != 0) {
526            result.add(prefix + "write");
527        }
528
529        if ((permissionBits & CmsPermissionSet.PERMISSION_VIEW) != 0) {
530            result.add(prefix + "view");
531        }
532
533        if ((permissionBits & CmsPermissionSet.PERMISSION_CONTROL) != 0) {
534            result.add(prefix + "control");
535        }
536
537        if ((permissionBits & CmsPermissionSet.PERMISSION_DIRECT_PUBLISH) != 0) {
538            result.add(prefix + "publish");
539        }
540        return result;
541    }
542
543    /**
544     * Wrap OpenCms into OpenCMIS exceptions and rethrow them.<p>
545     *
546     * @param e the exception to handle
547     */
548    public static void handleCmsException(CmsException e) {
549
550        if (e instanceof CmsVfsResourceNotFoundException) {
551            throw new CmisObjectNotFoundException(e.getLocalizedMessage(), e);
552        } else if (e instanceof CmsSecurityException) {
553            throw new CmisUnauthorizedException(e.getLocalizedMessage(), e);
554        } else {
555            throw new CmisRuntimeException(e.getLocalizedMessage(), e);
556        }
557    }
558
559    /**
560     * Checks whether the given resource has any children.<p>
561     *
562     * @param cms the CMS context
563     * @param resource the resource to check
564     *
565     * @return true if the resource has children
566     *
567     * @throws CmsException if something goes wrong
568     */
569    public static boolean hasChildren(CmsObject cms, CmsResource resource) throws CmsException {
570
571        return !cms.getResourcesInFolder(cms.getSitePath(resource), CmsResourceFilter.ALL).isEmpty();
572    }
573
574    /**
575     * Converts milliseconds into a calendar object.
576     *
577     * @param millis a time given in milliseconds after epoch
578     * @return the calendar object for the given time
579     */
580    public static GregorianCalendar millisToCalendar(long millis) {
581
582        GregorianCalendar result = new GregorianCalendar();
583        result.setTimeZone(TimeZone.getTimeZone("GMT"));
584        result.setTimeInMillis((long)(Math.ceil(millis / 1000) * 1000));
585        return result;
586    }
587
588    /**
589     * Splits a filter statement into a collection of properties. If
590     * <code>filter</code> is <code>null</code>, empty or one of the properties
591     * is '*' , an empty collection will be returned.
592     *
593     * @param filter the filter string
594     * @return the set of components of the filter
595     */
596    public static Set<String> splitFilter(String filter) {
597
598        if (filter == null) {
599            return null;
600        }
601
602        if (filter.trim().length() == 0) {
603            return null;
604        }
605
606        Set<String> result = new LinkedHashSet<String>();
607        for (String s : filter.split(",")) {
608            s = s.trim();
609            if (s.equals("*")) {
610                return null;
611            } else if (s.length() > 0) {
612                result.add(s);
613            }
614        }
615
616        // set a few base properties
617        // query name == id (for base type properties)
618        result.add(PropertyIds.OBJECT_ID);
619        result.add(PropertyIds.OBJECT_TYPE_ID);
620        result.add(PropertyIds.BASE_TYPE_ID);
621
622        return result;
623    }
624
625}