001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 GmbH & Co. KG, 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.file.wrapper;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProperty;
033import org.opencms.file.CmsRequestContext;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResource.CmsResourceCopyMode;
036import org.opencms.file.CmsResource.CmsResourceDeleteMode;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.file.CmsUser;
039import org.opencms.file.I_CmsResource;
040import org.opencms.file.types.CmsResourceTypeJsp;
041import org.opencms.file.types.CmsResourceTypePlain;
042import org.opencms.file.types.CmsResourceTypeXmlContent;
043import org.opencms.file.types.CmsResourceTypeXmlPage;
044import org.opencms.file.types.I_CmsResourceType;
045import org.opencms.i18n.CmsEncoder;
046import org.opencms.i18n.CmsLocaleManager;
047import org.opencms.loader.CmsLoaderException;
048import org.opencms.lock.CmsLock;
049import org.opencms.main.CmsException;
050import org.opencms.main.CmsIllegalArgumentException;
051import org.opencms.main.CmsLog;
052import org.opencms.main.OpenCms;
053import org.opencms.util.CmsUUID;
054
055import java.util.ArrayList;
056import java.util.Collections;
057import java.util.Iterator;
058import java.util.List;
059
060import org.apache.commons.logging.Log;
061
062/**
063 * This class contains a subset of the methods of {@link CmsObject} and uses the
064 * configured resource wrappers ({@link I_CmsResourceWrapper}) to change the view
065 * to the existing resources in the VFS.<p>
066 *
067 * Almost every method in this class iterates through the configured list of
068 * {@link I_CmsResourceWrapper} and calls the same method there. The first resource
069 * wrapper in the list which feels responsible for that action handles it and the
070 * iteration ends. So the resource wrappers should check in every method if it is
071 * responsible or not. Be careful if there are more than one resource wrapper for
072 * the same resource in the VFS, because the first in the list wins. If the iteration is
073 * finished and no resource wrapper felt responsible the default action is to call the
074 * method in the {@link CmsObject}.<p>
075 *
076 * It is possible to create an unchanged access to the resource in the VFS by creating
077 * a new instance of the CmsObjectWrapper with an empty list of resource wrappers.<p>
078 *
079 * @since 6.2.4
080 */
081public class CmsObjectWrapper {
082
083    /** The name of the attribute in the {@link CmsRequestContext} where the current CmsObjectWrapper can be found. */
084    public static final String ATTRIBUTE_NAME = "org.opencms.file.wrapper.CmsObjectWrapper";
085
086    /** The log object for this class. */
087    private static final Log LOG = CmsLog.getLog(CmsObjectWrapper.class);
088
089    /** Flag to contro whether byte order marks should be added to plaintext files. */
090    private boolean m_addByteOrderMark = true;
091
092    /** The initialized CmsObject. */
093    private CmsObject m_cms;
094
095    /** The list with the configured wrappers (entries of type {@link I_CmsResourceWrapper}). */
096    private List<I_CmsResourceWrapper> m_wrappers;
097
098    /**
099     * Constructor with the CmsObject to wrap and the resource wrappers to use.<p>
100     *
101     * @param cms the initialized CmsObject
102     * @param wrappers the configured wrappers to use (entries of type {@link I_CmsResourceWrapper})
103     */
104    public CmsObjectWrapper(CmsObject cms, List<I_CmsResourceWrapper> wrappers) {
105
106        m_cms = cms;
107        m_wrappers = wrappers;
108    }
109
110    /**
111     * Copies a resource.<p>
112     *
113     * Iterates through all configured resource wrappers till the first returns <code>true</code>.<p>
114     *
115     * @see I_CmsResourceWrapper#copyResource(CmsObject, String, String, CmsResource.CmsResourceCopyMode)
116     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
117     *
118     * @param source the name of the resource to copy (full path)
119     * @param destination the name of the copy destination (full path)
120     * @param siblingMode indicates how to handle siblings during copy
121     *
122     * @throws CmsException if something goes wrong
123     * @throws CmsIllegalArgumentException if the <code>destination</code> argument is null or of length 0
124     */
125    public void copyResource(String source, String destination, CmsResourceCopyMode siblingMode)
126    throws CmsException, CmsIllegalArgumentException {
127
128        boolean exec = false;
129
130        // iterate through all wrappers and call "copyResource" till one does not return null
131        List<I_CmsResourceWrapper> wrappers = getWrappers();
132        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
133        while (iter.hasNext()) {
134            I_CmsResourceWrapper wrapper = iter.next();
135            exec = wrapper.copyResource(m_cms, source, destination, siblingMode);
136            if (exec) {
137                break;
138            }
139        }
140
141        // delegate the call to the CmsObject
142        if (!exec) {
143            m_cms.copyResource(source, destination, siblingMode);
144        }
145
146    }
147
148    /**
149     * Creates a new resource of the given resource type with empty content and no properties.<p>
150     *
151     * @see #createResource(String, int, byte[], List)
152     *
153     * @param resourcename the name of the resource to create (full path)
154     * @param type the type of the resource to create
155     *
156     * @return the created resource
157     *
158     * @throws CmsException if something goes wrong
159     * @throws CmsIllegalArgumentException if the given <code>resourcename</code> is null or of length 0
160     */
161    public CmsResource createResource(String resourcename, int type) throws CmsException, CmsIllegalArgumentException {
162
163        return createResource(resourcename, type, new byte[0], new ArrayList<CmsProperty>(0));
164    }
165
166    /**
167     * Creates a new resource of the given resource type with the provided content and properties.<p>
168     *
169     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
170     *
171     * @see I_CmsResourceWrapper#createResource(CmsObject, String, int, byte[], List)
172     * @see CmsObject#createResource(String, int, byte[], List)
173     *
174     * @param resourcename the name of the resource to create (full path)
175     * @param type the type of the resource to create
176     * @param content the contents for the new resource
177     * @param properties the properties for the new resource
178     *
179     * @return the created resource
180     *
181     * @throws CmsException if something goes wrong
182     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
183     */
184    public CmsResource createResource(String resourcename, int type, byte[] content, List<CmsProperty> properties)
185    throws CmsException, CmsIllegalArgumentException {
186
187        CmsResource res = null;
188
189        // iterate through all wrappers and call "createResource" till one does not return null
190        List<I_CmsResourceWrapper> wrappers = getWrappers();
191        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
192        while (iter.hasNext()) {
193            I_CmsResourceWrapper wrapper = iter.next();
194            res = wrapper.createResource(m_cms, resourcename, type, content, properties);
195            if (res != null) {
196                break;
197            }
198        }
199
200        // delegate the call to the CmsObject
201        if (res == null) {
202            res = m_cms.createResource(resourcename, type, content, properties);
203        }
204
205        return res;
206    }
207
208    /**
209     * Deletes a resource given its name.<p>
210     *
211     * Iterates through all configured resource wrappers till the first returns <code>true</code>.<p>
212     *
213     * @see I_CmsResourceWrapper#deleteResource(CmsObject, String, CmsResource.CmsResourceDeleteMode)
214     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
215     *
216     * @param resourcename the name of the resource to delete (full path)
217     * @param siblingMode indicates how to handle siblings of the deleted resource
218     *
219     * @throws CmsException if something goes wrong
220     */
221    public void deleteResource(String resourcename, CmsResourceDeleteMode siblingMode) throws CmsException {
222
223        boolean exec = false;
224
225        // iterate through all wrappers and call "deleteResource" till one does not return false
226        List<I_CmsResourceWrapper> wrappers = getWrappers();
227        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
228        while (iter.hasNext()) {
229            I_CmsResourceWrapper wrapper = iter.next();
230            exec = wrapper.deleteResource(m_cms, resourcename, siblingMode);
231            if (exec) {
232                break;
233            }
234        }
235
236        // delegate the call to the CmsObject
237        if (!exec) {
238            m_cms.deleteResource(resourcename, siblingMode);
239        }
240    }
241
242    /**
243     * Checks the availability of a resource in the VFS,
244     * using the {@link CmsResourceFilter#DEFAULT} filter.<p>
245     *
246     * Here it will be first checked if the resource exists in the VFS by calling
247     * {@link org.opencms.file.CmsObject#existsResource(String)}. Only if it doesn't exist
248     * in the VFS the method {@link I_CmsResourceWrapper#readResource(CmsObject, String, CmsResourceFilter)}
249     * in the configured resource wrappers are called till the first does not throw an exception or returns
250     * <code>null</code>.<p>
251     *
252     * @param resourcename the name of the resource to check (full path)
253     *
254     * @return <code>true</code> if the resource is available
255     */
256    public boolean existsResource(String resourcename) {
257
258        // first try to find the resource
259        boolean ret = m_cms.existsResource(resourcename);
260
261        // if not exists, ask the resource type wrappers
262        if (!ret) {
263
264            List<I_CmsResourceWrapper> wrappers = getWrappers();
265            Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
266            while (iter.hasNext()) {
267                I_CmsResourceWrapper wrapper = iter.next();
268                try {
269                    CmsResource res = wrapper.readResource(m_cms, resourcename, CmsResourceFilter.DEFAULT);
270                    if (res != null) {
271                        ret = true;
272                        break;
273                    }
274                } catch (CmsException ex) {
275                    // noop
276                }
277            }
278
279        }
280
281        return ret;
282    }
283
284    /**
285     * Returns the lock state for a specified resource.<p>
286     *
287     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
288     *
289     * @see I_CmsResourceWrapper#getLock(CmsObject, CmsResource)
290     * @see CmsObject#getLock(CmsResource)
291     *
292     * @param resource the resource to return the lock state for
293     *
294     * @return the lock state for the specified resource
295     *
296     * @throws CmsException if something goes wrong
297     */
298    public CmsLock getLock(CmsResource resource) throws CmsException {
299
300        CmsLock lock = null;
301
302        // iterate through all wrappers and call "getLock" till one does not return null
303        List<I_CmsResourceWrapper> wrappers = getWrappers();
304        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
305        while (iter.hasNext()) {
306            I_CmsResourceWrapper wrapper = iter.next();
307            lock = wrapper.getLock(m_cms, resource);
308            if (lock != null) {
309                break;
310            }
311        }
312
313        // delegate the call to the CmsObject
314        if (lock == null) {
315            lock = m_cms.getLock(resource);
316        }
317
318        return lock;
319    }
320
321    /**
322     * Delegate method for {@link CmsObject#getRequestContext()}.<p>
323     *
324     * @see CmsObject#getRequestContext()
325
326     * @return the current users request context
327     */
328    public CmsRequestContext getRequestContext() {
329
330        return m_cms.getRequestContext();
331    }
332
333    /**
334     * Returns all child resources of a resource, that is the resources
335     * contained in a folder.<p>
336     *
337     * First fetch all child resources from VFS by calling {@link CmsObject#getResourcesInFolder(String, CmsResourceFilter)}.
338     * After that all resource wrapper are called {@link I_CmsResourceWrapper#addResourcesToFolder(CmsObject, String, CmsResourceFilter)}
339     * to have the chance to add additional resources to those already existing. In that list every resource is given to
340     * the appropriate resource wrapper ({@link I_CmsResourceWrapper#wrapResource(CmsObject, CmsResource)}) to have the
341     * possibility to change the existing resources. The matching resource wrapper for a resource is found by a call to
342     * {@link I_CmsResourceWrapper#isWrappedResource(CmsObject, CmsResource)}.<p>
343     *
344     * @see I_CmsResourceWrapper#addResourcesToFolder(CmsObject, String, CmsResourceFilter)
345     * @see CmsObject#getResourcesInFolder(String, CmsResourceFilter)
346     *
347     * @param resourcename the full path of the resource to return the child resources for
348     * @param filter the resource filter to use
349     *
350     * @return a list of all child <code>{@link CmsResource}</code>s
351     *
352     * @throws CmsException if something goes wrong
353     */
354    public List<CmsResource> getResourcesInFolder(String resourcename, CmsResourceFilter filter) throws CmsException {
355
356        List<CmsResource> list = new ArrayList<CmsResource>();
357
358        // read children existing in the VFS
359        try {
360            list.addAll(m_cms.getResourcesInFolder(resourcename, filter));
361        } catch (CmsException ex) {
362            //noop
363        }
364
365        // iterate through all wrappers and call "addResourcesToFolder" and add the results to the list
366        List<I_CmsResourceWrapper> wrappers = getWrappers();
367        Iterator<I_CmsResourceWrapper> iter1 = wrappers.iterator();
368        while (iter1.hasNext()) {
369            I_CmsResourceWrapper wrapper = iter1.next();
370            List<CmsResource> added = wrapper.addResourcesToFolder(m_cms, resourcename, filter);
371            if (added != null) {
372                list.addAll(added);
373            }
374        }
375
376        // create a new list to add all resources
377        ArrayList<CmsResource> wrapped = new ArrayList<CmsResource>();
378
379        // eventually wrap the found resources
380        Iterator<CmsResource> iter2 = list.iterator();
381        while (iter2.hasNext()) {
382            CmsResource res = iter2.next();
383
384            // correct the length of the content if an UTF-8 marker would be added later
385            if (needUtf8Marker(res) && !startsWithUtf8Marker(res)) {
386                CmsWrappedResource wrap = new CmsWrappedResource(res);
387                wrap.setLength(res.getLength() + CmsResourceWrapperUtils.UTF8_MARKER.length);
388
389                res = wrap.getResource();
390            }
391
392            // get resource type wrapper for the resource
393            I_CmsResourceWrapper resWrapper = getResourceTypeWrapper(res);
394
395            if (resWrapper != null) {
396
397                // adds the wrapped resources
398                wrapped.add(resWrapper.wrapResource(m_cms, res));
399            } else {
400
401                // add the resource unwrapped
402                wrapped.add(res);
403            }
404        }
405
406        // sort the wrapped list correctly
407        Collections.sort(wrapped, I_CmsResource.COMPARE_ROOT_PATH_IGNORE_CASE_FOLDERS_FIRST);
408
409        return wrapped;
410    }
411
412    /**
413     * Delegate method for {@link CmsObject#getSitePath(CmsResource)}.<p>
414     *
415     * @see CmsObject#getSitePath(org.opencms.file.CmsResource)
416     *
417     * @param resource the resource to get the adjusted site root path for
418     *
419     * @return the absolute resource path adjusted for the current site
420     */
421    public String getSitePath(CmsResource resource) {
422
423        return m_cms.getSitePath(resource);
424    }
425
426    /**
427     * Returns the configured resource wrappers used by this instance.<p>
428     *
429     * Entries in list are from type {@link I_CmsResourceWrapper}.<p>
430     *
431     * @return the configured resource wrappers for this instance
432     */
433    public List<I_CmsResourceWrapper> getWrappers() {
434
435        return m_wrappers;
436    }
437
438    /**
439     * Locks a resource.<p>
440     *
441     * Iterates through all configured resource wrappers till the first returns <code>true</code>.<p>
442     *
443     * @see CmsObject#lockResource(String)
444     *
445     * @param resourcename the name of the resource to lock (full path)
446     *
447     * @throws CmsException if something goes wrong
448     */
449    public void lockResource(String resourcename) throws CmsException {
450
451        boolean exec = false;
452
453        // iterate through all wrappers and call "lockResource" till one does not return false
454        List<I_CmsResourceWrapper> wrappers = getWrappers();
455        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
456        while (iter.hasNext()) {
457            I_CmsResourceWrapper wrapper = iter.next();
458            exec = wrapper.lockResource(m_cms, resourcename, false);
459            if (exec) {
460                break;
461            }
462        }
463
464        // delegate the call to the CmsObject
465        if (!exec) {
466            m_cms.lockResource(resourcename);
467        }
468    }
469
470    /**
471     * Locks a resource temporarily.<p>
472     *
473     * @param resourceName the name of the resource to lock
474     *
475     * @throws CmsException if something goes wrong
476     */
477    public void lockResourceTemporary(String resourceName) throws CmsException {
478
479        boolean exec = false;
480        // iterate through all wrappers and call "lockResource" till one does not return false
481        List<I_CmsResourceWrapper> wrappers = getWrappers();
482        for (I_CmsResourceWrapper wrapper : wrappers) {
483            exec = wrapper.lockResource(m_cms, resourceName, true);
484            if (exec) {
485                break;
486            }
487        }
488
489        // delegate the call to the CmsObject
490        if (!exec) {
491            m_cms.lockResourceTemporary(resourceName);
492        }
493    }
494
495    /**
496     * Moves a resource to the given destination.<p>
497     *
498     * Iterates through all configured resource wrappers till the first returns <code>true</code>.<p>
499     *
500     * @see I_CmsResourceWrapper#moveResource(CmsObject, String, String)
501     * @see CmsObject#moveResource(String, String)
502     *
503     * @param source the name of the resource to move (full path)
504     * @param destination the destination resource name (full path)
505     *
506     * @throws CmsException if something goes wrong
507     */
508    public void moveResource(String source, String destination) throws CmsException {
509
510        boolean exec = false;
511
512        // iterate through all wrappers and call "moveResource" till one does not return false
513        List<I_CmsResourceWrapper> wrappers = getWrappers();
514        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
515        while (iter.hasNext()) {
516            I_CmsResourceWrapper wrapper = iter.next();
517            exec = wrapper.moveResource(m_cms, source, destination);
518            if (exec) {
519                break;
520            }
521        }
522
523        // delegate the call to the CmsObject
524        if (!exec) {
525            m_cms.moveResource(source, destination);
526        }
527    }
528
529    /**
530     * Reads a file resource (including it's binary content) from the VFS,
531     * using the specified resource filter.<p>
532     *
533     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
534     *
535     * If the resource contains textual content and the encoding is UTF-8, then the byte order mask
536     * for UTF-8 is added at the start of the content to make sure that a client using this content
537     * displays it correctly.<p>
538     *
539     * @see I_CmsResourceWrapper#readFile(CmsObject, String, CmsResourceFilter)
540     * @see CmsObject#readFile(String, CmsResourceFilter)
541     *
542     * @param resourcename the name of the resource to read (full path)
543     * @param filter the resource filter to use while reading
544     *
545     * @return the file resource that was read
546     *
547     * @throws CmsException if the file resource could not be read for any reason
548     */
549    public CmsFile readFile(String resourcename, CmsResourceFilter filter) throws CmsException {
550
551        CmsFile res = null;
552
553        // iterate through all wrappers and call "readFile" till one does not return null
554        List<I_CmsResourceWrapper> wrappers = getWrappers();
555        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
556        while (iter.hasNext()) {
557            I_CmsResourceWrapper wrapper = iter.next();
558            res = wrapper.readFile(m_cms, resourcename, filter);
559            if (res != null) {
560                break;
561            }
562        }
563
564        // delegate the call to the CmsObject
565        if (res == null) {
566            res = m_cms.readFile(resourcename, filter);
567        }
568
569        // for text based resources which are encoded in UTF-8 add the UTF marker at the start
570        // of the content
571        if (needUtf8Marker(res)) {
572
573            if (LOG.isDebugEnabled()) {
574                LOG.debug(Messages.get().getBundle().key(Messages.LOG_ADD_UTF8_MARKER_1, res.getRootPath()));
575            }
576
577            res.setContents(CmsResourceWrapperUtils.addUtf8Marker(res.getContents()));
578        }
579
580        return res;
581    }
582
583    /**
584     * Delegate method for {@link CmsObject#readPropertyObject(CmsResource, String, boolean)}.<p>
585     *
586     * @see CmsObject#readPropertyObject(CmsResource, String, boolean)
587     *
588     * @param resource the resource where the property is attached to
589     * @param property the property name
590     * @param search if true, the property is searched on all parent folders of the resource,
591     *      if it's not found attached directly to the resource
592     *
593     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
594     *
595     * @throws CmsException if something goes wrong
596     */
597    public CmsProperty readPropertyObject(CmsResource resource, String property, boolean search) throws CmsException {
598
599        return m_cms.readPropertyObject(resource, property, search);
600    }
601
602    /**
603     * Delegate method for {@link CmsObject#readResource(CmsUUID, CmsResourceFilter)}.<p>
604     *
605     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
606     *
607     * @param structureID the ID of the structure to read
608     * @param filter the resource filter to use while reading
609     *
610     * @return the resource that was read
611     *
612     * @throws CmsException if the resource could not be read for any reason
613     */
614    public CmsResource readResource(CmsUUID structureID, CmsResourceFilter filter) throws CmsException {
615
616        return m_cms.readResource(structureID, filter);
617    }
618
619    /**
620     * Reads a resource from the VFS,
621     * using the <code>{@link CmsResourceFilter#DEFAULT}</code> filter.<p>
622     *
623     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
624     *
625     * @see I_CmsResourceWrapper#readResource(CmsObject, String, CmsResourceFilter)
626     * @see CmsObject#readResource(String, CmsResourceFilter)
627     *
628     * @param resourcename The name of the resource to read (full path)
629     * @param filter the resource filter to use while reading
630     *
631     * @return the resource that was read
632     *
633     * @throws CmsException if the resource could not be read for any reason
634     */
635    public CmsResource readResource(String resourcename, CmsResourceFilter filter) throws CmsException {
636
637        CmsResource res = null;
638
639        // iterate through all wrappers and call "readResource" till one does not return null
640        List<I_CmsResourceWrapper> wrappers = getWrappers();
641        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
642        while (iter.hasNext()) {
643            I_CmsResourceWrapper wrapper = iter.next();
644            res = wrapper.readResource(m_cms, resourcename, filter);
645            if (res != null) {
646                break;
647            }
648        }
649
650        // delegate the call to the CmsObject
651        if (res == null) {
652            res = m_cms.readResource(resourcename, filter);
653        }
654
655        // correct the length of the content if an UTF-8 marker would be added later
656        if (needUtf8Marker(res) && !startsWithUtf8Marker(res)) {
657            CmsWrappedResource wrap = new CmsWrappedResource(res);
658            wrap.setLength(res.getLength() + CmsResourceWrapperUtils.UTF8_MARKER.length);
659
660            return wrap.getResource();
661        }
662
663        return res;
664    }
665
666    /**
667     * Delegate method for {@link CmsObject#readUser(CmsUUID)}.<p>
668     *
669     * @see CmsObject#readUser(CmsUUID)
670     *
671     * @param userId the id of the user to be read
672     *
673     * @return the user with the given id
674     *
675     * @throws CmsException if something goes wrong
676     */
677    public CmsUser readUser(CmsUUID userId) throws CmsException {
678
679        return m_cms.readUser(userId);
680    }
681
682    /**
683     * Returns a link to an existing resource in the VFS.<p>
684     *
685     * Because it is possible through the <code>CmsObjectWrapper</code> to create "virtual" resources,
686     * which can not be found in the VFS, it is necessary to change the links in pages
687     * as well, so that they point to resources which really exists in the VFS.<p>
688     *
689     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
690     *
691     * @see #rewriteLink(String)
692     * @see I_CmsResourceWrapper#restoreLink(CmsObject, String)
693     *
694     * @param path the path to the resource
695     *
696     * @return the path for the resource which exists in the VFS
697     */
698    public String restoreLink(String path) {
699
700        if ((path != null) && (path.startsWith("#"))) {
701            return path;
702        }
703
704        String ret = null;
705
706        // iterate through all wrappers and call "restoreLink" till one does not return null
707        List<I_CmsResourceWrapper> wrappers = getWrappers();
708        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
709        while (iter.hasNext()) {
710            I_CmsResourceWrapper wrapper = iter.next();
711            ret = wrapper.restoreLink(m_cms, m_cms.getRequestContext().removeSiteRoot(path));
712            if (ret != null) {
713                return ret;
714            }
715        }
716
717        return path;
718    }
719
720    /**
721     * Returns a link to a resource after it was wrapped by the CmsObjectWrapper.<p>
722     *
723     * Because it is possible to change the names of resources inside the VFS by this
724     * <code>CmsObjectWrapper</code>, it is necessary to change the links used in pages
725     * as well, so that they point to the changed name of the resource.<p>
726     *
727     * For example: <code>/sites/default/index.html</code> becomes to
728     * <code>/sites/default/index.html.jsp</code>, because it is a jsp page, the links
729     * in pages where corrected so that they point to the new name (with extension "jsp").<p>
730     *
731     * Used for the link processing in the class {@link org.opencms.relations.CmsLink}.<p>
732     *
733     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
734     *
735     * @see #restoreLink(String)
736     * @see I_CmsResourceWrapper#rewriteLink(CmsObject, CmsResource)
737     *
738     * @param path the full path where to find the resource
739     *
740     * @return the rewritten link for the resource
741     */
742    public String rewriteLink(String path) {
743
744        CmsResource res = null;
745
746        try {
747            res = readResource(m_cms.getRequestContext().removeSiteRoot(path), CmsResourceFilter.ALL);
748            if (res != null) {
749                String ret = null;
750
751                // iterate through all wrappers and call "rewriteLink" till one does not return null
752                List<I_CmsResourceWrapper> wrappers = getWrappers();
753                Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
754                while (iter.hasNext()) {
755                    I_CmsResourceWrapper wrapper = iter.next();
756                    ret = wrapper.rewriteLink(m_cms, res);
757                    if (ret != null) {
758                        return ret;
759                    }
760                }
761            }
762        } catch (CmsException ex) {
763            // noop
764        }
765
766        return path;
767    }
768
769    /**
770     * Enables or disables the automatic adding of byte order marks to plaintext files.<p>
771     *
772     * @param addByteOrderMark true if byte order marks should be added to plaintext files automatically
773     */
774    public void setAddByteOrderMark(boolean addByteOrderMark) {
775
776        m_addByteOrderMark = addByteOrderMark;
777    }
778
779    /**
780     * Unlocks a resource.<p>
781     *
782     * Iterates through all configured resource wrappers till the first returns <code>true</code>.<p>
783     *
784     * @see I_CmsResourceWrapper#unlockResource(CmsObject, String)
785     * @see CmsObject#unlockResource(String)
786     *
787     * @param resourcename the name of the resource to unlock (full path)
788     *
789     * @throws CmsException if something goes wrong
790     */
791    public void unlockResource(String resourcename) throws CmsException {
792
793        boolean exec = false;
794
795        // iterate through all wrappers and call "lockResource" till one does not return false
796        List<I_CmsResourceWrapper> wrappers = getWrappers();
797        Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
798        while (iter.hasNext()) {
799            I_CmsResourceWrapper wrapper = iter.next();
800            exec = wrapper.unlockResource(m_cms, resourcename);
801            if (exec) {
802                break;
803            }
804        }
805
806        // delegate the call to the CmsObject
807        if (!exec) {
808            m_cms.unlockResource(resourcename);
809        }
810    }
811
812    /**
813     * Writes a resource to the OpenCms VFS, including it's content.<p>
814     *
815     * Iterates through all configured resource wrappers till the first returns not <code>null</code>.<p>
816     *
817     * @see I_CmsResourceWrapper#writeFile(CmsObject, CmsFile)
818     * @see CmsObject#writeFile(CmsFile)
819     *
820     * @param resource the resource to write
821     *
822     * @return the written resource (may have been modified)
823     *
824     * @throws CmsException if something goes wrong
825     */
826    public CmsFile writeFile(CmsFile resource) throws CmsException {
827
828        CmsFile res = null;
829
830        // remove the added UTF-8 marker
831        if (needUtf8Marker(resource)) {
832            resource.setContents(CmsResourceWrapperUtils.removeUtf8Marker(resource.getContents()));
833        }
834
835        String resourcename = m_cms.getSitePath(resource);
836        if (!m_cms.existsResource(resourcename)) {
837
838            // iterate through all wrappers and call "writeFile" till one does not return null
839            List<I_CmsResourceWrapper> wrappers = getWrappers();
840            Iterator<I_CmsResourceWrapper> iter = wrappers.iterator();
841            while (iter.hasNext()) {
842                I_CmsResourceWrapper wrapper = iter.next();
843                res = wrapper.writeFile(m_cms, resource);
844                if (res != null) {
845                    break;
846                }
847            }
848
849            // delegate the call to the CmsObject
850            if (res == null) {
851                res = m_cms.writeFile(resource);
852            }
853        } else {
854            res = m_cms.writeFile(resource);
855        }
856
857        return res;
858    }
859
860    /**
861     * Try to find a resource type wrapper for the resource.<p>
862     *
863     * Takes all configured resource type wrappers and ask if one of them is responsible
864     * for that resource. The first in the list which feels responsible is returned.
865     * If no wrapper could be found null will be returned.<p>
866     *
867     * @see I_CmsResourceWrapper#isWrappedResource(CmsObject, CmsResource)
868     *
869     * @param res the resource to find a resource type wrapper for
870     *
871     * @return the found resource type wrapper for the resource or null if not found
872     */
873    private I_CmsResourceWrapper getResourceTypeWrapper(CmsResource res) {
874
875        Iterator<I_CmsResourceWrapper> iter = getWrappers().iterator();
876        while (iter.hasNext()) {
877            I_CmsResourceWrapper wrapper = iter.next();
878
879            if (wrapper.isWrappedResource(m_cms, res)) {
880                return wrapper;
881            }
882        }
883
884        return null;
885    }
886
887    /**
888     * Checks if the resource type needs an UTF-8 marker.<p>
889     *
890     * If the encoding of the resource is "UTF-8" and the resource
891     * type is one of the following:<br/>
892     * <ul>
893     * <li>{@link CmsResourceTypeJsp}</li>
894     * <li>{@link CmsResourceTypePlain}</li>
895     * <li>{@link CmsResourceTypeXmlContent}</li>
896     * <li>{@link CmsResourceTypeXmlPage}</li>
897     * </ul>
898     *
899     * it needs an UTF-8 marker.<p>
900     *
901     * @param res the resource to check if the content needs a UTF-8 marker
902     *
903     * @return <code>true</code> if the resource needs an UTF-8 maker otherwise <code>false</code>
904     */
905    private boolean needUtf8Marker(CmsResource res) {
906
907        if (!m_addByteOrderMark) {
908            return false;
909        }
910        // if the encoding of the resource is not UTF-8 return false
911        String encoding = CmsLocaleManager.getResourceEncoding(m_cms, res);
912        boolean result = false;
913        if (CmsEncoder.ENCODING_UTF_8.equals(encoding)) {
914            try {
915                I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(res.getTypeId());
916                if (resType instanceof CmsResourceTypeJsp) {
917                    result = true;
918                } else if (resType instanceof CmsResourceTypePlain) {
919                    result = true;
920                } else if (resType instanceof CmsResourceTypeXmlContent) {
921                    result = true;
922                } else if (resType instanceof CmsResourceTypeXmlPage) {
923                    result = true;
924                }
925            } catch (CmsLoaderException e) {
926                LOG.debug(e);
927            }
928        }
929        return result;
930    }
931
932    /**
933     * Checks if the file content already contains the UTF8 marker.<p>
934     *
935     * @param res the resource to check
936     *
937     * @return <code>true</code> if the file content already contains the UTF8 marker
938     */
939    private boolean startsWithUtf8Marker(CmsResource res) {
940
941        boolean result = false;
942        try {
943            if (res.isFile()) {
944                CmsFile file = m_cms.readFile(res);
945                if ((file.getContents().length >= 3)
946                    && (file.getContents()[0] == CmsResourceWrapperUtils.UTF8_MARKER[0])
947                    && (file.getContents()[1] == CmsResourceWrapperUtils.UTF8_MARKER[1])
948                    && (file.getContents()[2] == CmsResourceWrapperUtils.UTF8_MARKER[2])) {
949                    result = true;
950                }
951            }
952        } catch (CmsException e) {
953            LOG.debug(e);
954        }
955        return result;
956    }
957}