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.CmsPropertyDefinition;
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.CmsVfsResourceNotFoundException;
039import org.opencms.file.types.CmsResourceTypeFolder;
040import org.opencms.file.types.CmsResourceTypePlain;
041import org.opencms.file.types.CmsResourceTypeXmlPage;
042import org.opencms.file.types.I_CmsResourceType;
043import org.opencms.i18n.CmsEncoder;
044import org.opencms.i18n.CmsLocaleManager;
045import org.opencms.loader.CmsLoaderException;
046import org.opencms.loader.CmsResourceManager;
047import org.opencms.lock.CmsLock;
048import org.opencms.main.CmsException;
049import org.opencms.main.CmsIllegalArgumentException;
050import org.opencms.main.OpenCms;
051import org.opencms.util.CmsFileUtil;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.xml.page.CmsXmlPage;
054import org.opencms.xml.page.CmsXmlPageFactory;
055
056import java.io.UnsupportedEncodingException;
057import java.util.ArrayList;
058import java.util.Iterator;
059import java.util.List;
060import java.util.Locale;
061
062/**
063 * A resource type wrapper for xml page files, which explodes the xml pages to folders.<p>
064 *
065 * Every resource of type "xmlpage" becomes a folder with the same name. That folder
066 * contains the locales of the xml page as folders too. In the locale folder there are
067 * the elements for that locale as files. The files have the names of the elements with the
068 * extension "html". Additionaly there is a file in the root folder of that xml page that
069 * contains the controlcode of the xml page. This file has the name "controlcode.xml".<p>
070 *
071 * @since 6.5.6
072 */
073public class CmsResourceWrapperXmlPage extends A_CmsResourceWrapper {
074
075    /** The extension to use for elements. */
076    private static final String EXTENSION_ELEMENT = "html";
077
078    /** The name of the element to use for the controlcode. */
079    private static final String NAME_ELEMENT_CONTROLCODE = "controlcode.xml";
080
081    /** Table with the states of the virtual files. */
082    private static final List<String> TMP_FILE_TABLE = new ArrayList<String>();
083
084    /**
085     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#addResourcesToFolder(CmsObject, String, CmsResourceFilter)
086     */
087    @Override
088    public List<CmsResource> addResourcesToFolder(CmsObject cms, String resourcename, CmsResourceFilter filter)
089    throws CmsException {
090
091        CmsResource xmlPage = findXmlPage(cms, resourcename);
092        if (xmlPage != null) {
093            String path = getSubPath(cms, xmlPage, resourcename);
094            String rootPath = cms.getRequestContext().removeSiteRoot(xmlPage.getRootPath());
095
096            ArrayList<CmsResource> ret = new ArrayList<CmsResource>();
097
098            CmsFile file = cms.readFile(xmlPage);
099            CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
100
101            if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
102
103                // sub path is empty -> return all existing locales for the resource
104                if (file.getLength() == 0) {
105                    return ret;
106                }
107
108                List<Locale> locales = xml.getLocales();
109                Iterator<Locale> iter1 = locales.iterator();
110                while (iter1.hasNext()) {
111                    Locale locale = iter1.next();
112                    ret.add(getResourceForLocale(xmlPage, locale));
113                }
114
115                int plainId = OpenCms.getResourceManager().getResourceType(
116                    CmsResourceTypePlain.getStaticTypeName()).getTypeId();
117                // check temp file table to add virtual file
118                Iterator<String> iter2 = getVirtualFiles().iterator();
119                while (iter2.hasNext()) {
120
121                    String virtualFileName = iter2.next();
122                    String virtualFilePath = rootPath + "/" + virtualFileName;
123
124                    if (!TMP_FILE_TABLE.contains(virtualFilePath)) {
125
126                        // read the control code resource
127                        if (virtualFileName.equals(NAME_ELEMENT_CONTROLCODE)) {
128
129                            CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
130                            wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
131                            wrap.setTypeId(plainId);
132
133                            CmsFile tmpFile = wrap.getFile();
134                            tmpFile.setContents(file.getContents());
135                            ret.add(tmpFile);
136                        }
137                    }
138                }
139            } else {
140                // sub path is a locale -> return all elements for this locale
141                Locale locale = CmsLocaleManager.getLocale(path);
142                List<String> names = xml.getNames(locale);
143                Iterator<String> iter = names.iterator();
144                while (iter.hasNext()) {
145                    String name = iter.next();
146                    String content = xml.getStringValue(cms, name, locale);
147                    String fullPath = xmlPage.getRootPath() + "/" + path + "/" + name + "." + EXTENSION_ELEMENT;
148                    content = prepareContent(content, cms, xmlPage, fullPath);
149
150                    int length = content.length();
151                    try {
152                        length = content.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage)).length;
153                    } catch (UnsupportedEncodingException e) {
154                        // this will never happen since UTF-8 is always supported
155                    }
156
157                    ret.add(getResourceForElement(xmlPage, fullPath, length));
158                }
159
160            }
161
162            return ret;
163        }
164
165        return null;
166    }
167
168    /**
169     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#copyResource(org.opencms.file.CmsObject, java.lang.String, java.lang.String, org.opencms.file.CmsResource.CmsResourceCopyMode)
170     */
171    @Override
172    public boolean copyResource(CmsObject cms, String source, String destination, CmsResourceCopyMode siblingMode)
173    throws CmsException, CmsIllegalArgumentException {
174
175        // only allow copying of xml pages at whole or locales and elements inside the same xml page
176        CmsResource srcXmlPage = findXmlPage(cms, source);
177
178        if (srcXmlPage != null) {
179            String srcPath = getSubPath(cms, srcXmlPage, source);
180
181            // if the source is the xml page itself just copy the resource
182            if (CmsStringUtil.isEmptyOrWhitespaceOnly(srcPath)) {
183                cms.copyResource(source, destination, siblingMode);
184                return true;
185            } else {
186
187                // only a locale or an element should be copied
188                CmsResource destXmlPage = findXmlPage(cms, destination);
189                if (srcXmlPage.equals(destXmlPage)) {
190
191                    // copying inside the same xml page resource
192                    String destPath = getSubPath(cms, destXmlPage, destination);
193
194                    String[] srcTokens = srcPath.split("/");
195                    String[] destTokens = destPath.split("/");
196
197                    if (srcTokens.length == destTokens.length) {
198
199                        CmsFile srcFile = cms.readFile(srcXmlPage);
200                        CmsXmlPage srcXml = CmsXmlPageFactory.unmarshal(cms, srcFile);
201
202                        if (srcTokens.length == 1) {
203
204                            if (srcTokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
205
206                                // do nothing
207                            } else {
208
209                                // copy locale
210                                srcXml.copyLocale(
211                                    CmsLocaleManager.getLocale(srcTokens[0]),
212                                    CmsLocaleManager.getLocale(destTokens[0]));
213                            }
214                        } else if (srcTokens.length == 2) {
215
216                            // TODO: copy element
217                        }
218
219                        // write files
220                        srcFile.setContents(srcXml.marshal());
221                        cms.writeFile(srcFile);
222
223                        return true;
224                    } else {
225
226                        // TODO: error: destination path is invalid
227                    }
228                } else {
229
230                    // TODO: error: copying only allowed inside the same xml page
231                }
232            }
233        }
234
235        return false;
236    }
237
238    /**
239     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#createResource(org.opencms.file.CmsObject, java.lang.String, int, byte[], java.util.List)
240     */
241    @Override
242    public CmsResource createResource(
243        CmsObject cms,
244        String resourcename,
245        int type,
246        byte[] content,
247        List<CmsProperty> properties) throws CmsException, CmsIllegalArgumentException {
248
249        // cut off trailing slash
250        if (resourcename.endsWith("/")) {
251            resourcename = resourcename.substring(0, resourcename.length() - 1);
252        }
253
254        // creating new xml pages if type is a folder and the name ends with .html
255        if (resourcename.endsWith(".html")
256            && (type == OpenCms.getResourceManager().getResourceType(
257                CmsResourceTypeFolder.getStaticTypeName()).getTypeId())) {
258
259            // mark in temp file table that the visual files does not exist yet
260            Iterator<String> iter = getVirtualFiles().iterator();
261            while (iter.hasNext()) {
262                TMP_FILE_TABLE.add(resourcename + "/" + iter.next());
263            }
264
265            return cms.createResource(
266                resourcename,
267                OpenCms.getResourceManager().getResourceType(CmsResourceTypeXmlPage.getStaticTypeName()).getTypeId());
268        }
269
270        // find the xml page this is for
271        CmsResource xmlPage = findXmlPage(cms, resourcename);
272        if (xmlPage != null) {
273
274            // get the path below the xml page
275            String path = getSubPath(cms, xmlPage, resourcename);
276
277            // and the path without the site root
278            String rootPath = cms.getRequestContext().removeSiteRoot(xmlPage.getRootPath());
279
280            CmsFile file = cms.readFile(xmlPage);
281            CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
282
283            // mark virtual files as created in temp file table
284            if (getVirtualFiles().contains(path)) {
285                TMP_FILE_TABLE.remove(resourcename);
286
287                // at least lock file, because creating resources usually locks the resource
288                cms.lockResource(rootPath);
289
290                return file;
291            }
292
293            String[] tokens = path.split("/");
294            if (tokens.length == 1) {
295
296                Locale locale = CmsLocaleManager.getLocale(tokens[0]);
297
298                // workaround: empty xmlpages always have the default locale "en" set
299                if (file.getLength() == 0) {
300                    Iterator<Locale> iter = xml.getLocales().iterator();
301                    while (iter.hasNext()) {
302                        xml.removeLocale(iter.next());
303                    }
304                }
305
306                // create new locale
307                xml.addLocale(cms, locale);
308
309                // save the xml page
310                file.setContents(xml.marshal());
311
312                // lock the resource
313                cms.lockResource(rootPath);
314
315                // write file
316                cms.writeFile(file);
317
318            } else if (tokens.length == 2) {
319
320                String name = tokens[1];
321                if (name.endsWith(EXTENSION_ELEMENT)) {
322                    name = name.substring(0, name.length() - EXTENSION_ELEMENT.length() - 1);
323                }
324
325                // create new element
326                xml.addValue(name, CmsLocaleManager.getLocale(tokens[0]));
327
328                // set the content
329                xml.setStringValue(
330                    cms,
331                    name,
332                    CmsLocaleManager.getLocale(tokens[0]),
333                    getStringValue(cms, file, content));
334
335                // save the xml page
336                file.setContents(xml.marshal());
337
338                // lock the resource
339                cms.lockResource(rootPath);
340
341                // write file
342                cms.writeFile(file);
343            }
344
345            return file;
346        }
347
348        return null;
349    }
350
351    /**
352     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#deleteResource(CmsObject, String, org.opencms.file.CmsResource.CmsResourceDeleteMode)
353     */
354    @Override
355    public boolean deleteResource(CmsObject cms, String resourcename, CmsResourceDeleteMode siblingMode)
356    throws CmsException {
357
358        // find the xml page this is for
359        CmsResource xmlPage = findXmlPage(cms, resourcename);
360        if (xmlPage != null) {
361
362            // cut off trailing slash
363            if (resourcename.endsWith("/")) {
364                resourcename = resourcename.substring(0, resourcename.length() - 1);
365            }
366
367            // get the path below the xml page
368            String path = getSubPath(cms, xmlPage, resourcename);
369
370            // if sub path is empty
371            if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
372
373                // delete the xml page itself
374                cms.deleteResource(resourcename, siblingMode);
375
376                // remove all virtual files for this resource
377                Iterator<String> iter = getVirtualFiles().iterator();
378                while (iter.hasNext()) {
379                    TMP_FILE_TABLE.remove(resourcename + "/" + iter.next());
380                }
381
382                return true;
383            }
384
385            CmsFile file = cms.readFile(xmlPage);
386            CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
387
388            String[] tokens = path.split("/");
389            if (tokens.length == 1) {
390
391                // deleting a virtual file
392                if (getVirtualFiles().contains(tokens[0])) {
393
394                    // mark the virtual file in the temp file table as deleted
395                    TMP_FILE_TABLE.add(resourcename);
396                } else {
397
398                    // delete locale
399                    xml.removeLocale(CmsLocaleManager.getLocale(tokens[0]));
400
401                    // save the xml page
402                    file.setContents(xml.marshal());
403                    cms.writeFile(file);
404                }
405
406            } else if (tokens.length == 2) {
407
408                String name = tokens[1];
409                if (name.endsWith(EXTENSION_ELEMENT)) {
410                    name = name.substring(0, name.length() - EXTENSION_ELEMENT.length() - 1);
411                }
412
413                // delete element
414                xml.removeValue(name, CmsLocaleManager.getLocale(tokens[0]));
415
416                // save the xml page
417                file.setContents(xml.marshal());
418                cms.writeFile(file);
419            }
420
421            return true;
422        }
423
424        return false;
425    }
426
427    /**
428     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#getLock(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
429     */
430    @Override
431    public CmsLock getLock(CmsObject cms, CmsResource resource) throws CmsException {
432
433        CmsResource xmlPage = cms.readResource(resource.getStructureId());
434        //CmsResource xmlPage = findXmlPage(cms, resource.getRootPath());
435        if (xmlPage != null) {
436
437            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(xmlPage.getTypeId());
438            if (resType instanceof CmsResourceTypeXmlPage) {
439                return cms.getLock(xmlPage);
440            }
441        }
442
443        return null;
444    }
445
446    /**
447     * @see org.opencms.file.wrapper.I_CmsResourceWrapper#isWrappedResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
448     */
449    public boolean isWrappedResource(CmsObject cms, CmsResource res) {
450
451        try {
452            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(res.getTypeId());
453            if (resType instanceof CmsResourceTypeXmlPage) {
454                return true;
455            }
456        } catch (CmsException ex) {
457            // noop
458        }
459
460        return false;
461    }
462
463    /**
464     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#lockResource(org.opencms.file.CmsObject, java.lang.String, boolean)
465     */
466    @Override
467    public boolean lockResource(CmsObject cms, String resourcename, boolean temporary) throws CmsException {
468
469        CmsResource res = findXmlPage(cms, resourcename);
470        if (res != null) {
471            String path = cms.getRequestContext().removeSiteRoot(res.getRootPath());
472            if (temporary) {
473                cms.lockResourceTemporary(path);
474            } else {
475                cms.lockResource(path);
476            }
477            return true;
478        }
479
480        return false;
481    }
482
483    /**
484     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#moveResource(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
485     */
486    @Override
487    public boolean moveResource(CmsObject cms, String source, String destination)
488    throws CmsException, CmsIllegalArgumentException {
489
490        // only allow copying of xml pages at whole or locales and elements inside the same xml page
491        CmsResource srcXmlPage = findXmlPage(cms, source);
492
493        if (srcXmlPage != null) {
494            String srcPath = getSubPath(cms, srcXmlPage, source);
495
496            // if the source is the xml page itself just copy the resource
497            if (CmsStringUtil.isEmptyOrWhitespaceOnly(srcPath)) {
498                cms.moveResource(source, destination);
499                return true;
500            } else {
501
502                // only a locale or an element should be copied
503                CmsResource destXmlPage = findXmlPage(cms, destination);
504                if (srcXmlPage.equals(destXmlPage)) {
505
506                    // copying inside the same xml page resource
507                    String destPath = getSubPath(cms, destXmlPage, destination);
508
509                    String[] srcTokens = srcPath.split("/");
510                    String[] destTokens = destPath.split("/");
511
512                    if (srcTokens.length == destTokens.length) {
513
514                        CmsFile srcFile = cms.readFile(srcXmlPage);
515                        CmsXmlPage srcXml = CmsXmlPageFactory.unmarshal(cms, srcFile);
516
517                        if (srcTokens.length == 1) {
518
519                            // copy locale
520                            srcXml.moveLocale(
521                                CmsLocaleManager.getLocale(srcTokens[0]),
522                                CmsLocaleManager.getLocale(destTokens[0]));
523                        } else if (srcTokens.length == 2) {
524
525                            // TODO: move element
526                        }
527
528                        // write file
529                        srcFile.setContents(srcXml.marshal());
530                        cms.writeFile(srcFile);
531                    } else {
532
533                        // TODO: error: destination path is invalid
534                    }
535                } else {
536
537                    // TODO: error: moving only allowed inside the same xml page
538                }
539            }
540
541            return true;
542        }
543
544        return false;
545    }
546
547    /**
548     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readFile(CmsObject, String, CmsResourceFilter)
549     */
550    @Override
551    public CmsFile readFile(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException {
552
553        // find the xml page this is for
554        CmsResource xmlPage = findXmlPage(cms, resourcename);
555        if (xmlPage != null) {
556
557            // cut off trailing slash
558            if (resourcename.endsWith("/")) {
559                resourcename = resourcename.substring(0, resourcename.length() - 1);
560            }
561
562            // get the path below the xml page
563            String path = getSubPath(cms, xmlPage, resourcename);
564
565            String[] tokens = path.split("/");
566            if (tokens.length == 1) {
567
568                CmsFile file = cms.readFile(xmlPage);
569
570                // check temp file table to remove deleted virtual files
571                if (TMP_FILE_TABLE.contains(resourcename)) {
572                    return null;
573                }
574
575                // read the control code resource
576                if (tokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
577
578                    CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
579                    wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
580
581                    CmsFile ret = wrap.getFile();
582                    ret.setContents(file.getContents());
583                    return ret;
584                }
585            } else if (tokens.length == 2) {
586
587                CmsFile file = cms.readFile(xmlPage);
588                CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
589
590                // cut off the html suffix
591                String name = tokens[1];
592                if (name.endsWith("." + EXTENSION_ELEMENT)) {
593                    name = name.substring(0, name.length() - 5);
594                }
595
596                if (xml.hasValue(name, CmsLocaleManager.getLocale(tokens[0]))) {
597
598                    String contentString = xml.getStringValue(cms, name, CmsLocaleManager.getLocale(tokens[0]));
599                    String fullPath = xmlPage.getRootPath() + "/" + tokens[0] + "/" + name + "." + EXTENSION_ELEMENT;
600                    contentString = prepareContent(contentString, cms, xmlPage, fullPath);
601
602                    byte[] content;
603                    try {
604                        content = contentString.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage));
605                    } catch (UnsupportedEncodingException e) {
606                        // should never happen
607                        content = contentString.getBytes();
608                    }
609                    CmsResource resElem = getResourceForElement(xmlPage, fullPath, content.length);
610                    CmsFile fileElem = new CmsFile(resElem);
611
612                    fileElem.setContents(content);
613                    return fileElem;
614                }
615            }
616        }
617
618        return null;
619    }
620
621    /**
622     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#readResource(CmsObject, String, CmsResourceFilter)
623     */
624    @Override
625    public CmsResource readResource(CmsObject cms, String resourcename, CmsResourceFilter filter) throws CmsException {
626
627        try {
628
629            // try to read the resource for the resource name
630            CmsResource res = null;
631            try {
632                // catch this exception to try to read the resource again if it fails
633                res = cms.readResource(resourcename, filter);
634            } catch (CmsException e) {
635                // read resource failed, so check if the resource name ends with a slash
636                if (resourcename.endsWith("/")) {
637                    // try to read resource without a slash
638                    resourcename = CmsFileUtil.removeTrailingSeparator(resourcename);
639                    // try to read the resource name without ending slash
640                    res = cms.readResource(resourcename, filter);
641                } else {
642                    // throw the exception which caused this catch block
643                    throw e;
644                }
645            }
646            if (CmsResourceTypeXmlPage.isXmlPage(res)) {
647                // return the xml page resource as a folder
648                return wrapResource(cms, res);
649            }
650
651            return null;
652        } catch (CmsVfsResourceNotFoundException ex) {
653
654            // find the xml page this is for
655            CmsResource xmlPage = findXmlPage(cms, resourcename);
656            if (xmlPage != null) {
657
658                // cut off trailing slash
659                if (resourcename.endsWith("/")) {
660                    resourcename = resourcename.substring(0, resourcename.length() - 1);
661                }
662
663                // get the path below the xml page
664                String path = getSubPath(cms, xmlPage, resourcename);
665
666                CmsFile file = cms.readFile(xmlPage);
667                CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
668
669                String[] tokens = path.split("/");
670                if (tokens.length == 1) {
671
672                    // check temp file table to remove deleted virtual files
673                    if (TMP_FILE_TABLE.contains(resourcename)) {
674                        return null;
675                    }
676
677                    // read the control code resource
678                    if (tokens[0].equals(NAME_ELEMENT_CONTROLCODE)) {
679
680                        CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
681                        wrap.setRootPath(xmlPage.getRootPath() + "/" + NAME_ELEMENT_CONTROLCODE);
682                        return wrap.getResource();
683                    } else {
684
685                        Locale locale = CmsLocaleManager.getLocale(tokens[0]);
686                        if (xml.hasLocale(locale) && (file.getLength() > 0)) {
687                            return getResourceForLocale(xmlPage, locale);
688                        }
689                    }
690                } else if (tokens.length == 2) {
691
692                    // cut off the html suffix
693                    String name = tokens[1];
694                    if (name.endsWith("." + EXTENSION_ELEMENT)) {
695                        name = name.substring(0, name.length() - 5);
696                    }
697
698                    Locale locale = CmsLocaleManager.getLocale(tokens[0]);
699                    if (xml.hasValue(name, locale)) {
700                        String content = xml.getStringValue(cms, name, locale);
701                        String fullPath = xmlPage.getRootPath()
702                            + "/"
703                            + tokens[0]
704                            + "/"
705                            + name
706                            + "."
707                            + EXTENSION_ELEMENT;
708                        content = prepareContent(content, cms, xmlPage, fullPath);
709
710                        int length = content.length();
711                        try {
712                            length = content.getBytes(CmsLocaleManager.getResourceEncoding(cms, xmlPage)).length;
713                        } catch (UnsupportedEncodingException e) {
714                            // this will never happen since UTF-8 is always supported
715                        }
716
717                        return getResourceForElement(xmlPage, fullPath, length);
718                    }
719                }
720
721            }
722
723            return null;
724        }
725    }
726
727    /**
728     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#restoreLink(org.opencms.file.CmsObject, java.lang.String)
729     */
730    @Override
731    public String restoreLink(CmsObject cms, String uri) {
732
733        CmsResource res = findXmlPage(cms, uri);
734        if (res != null) {
735            return res.getRootPath();
736        }
737
738        return null;
739    }
740
741    /**
742     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#rewriteLink(CmsObject, CmsResource)
743     */
744    @Override
745    public String rewriteLink(CmsObject cms, CmsResource res) {
746
747        if (isWrappedResource(cms, res)) {
748            String path = res.getRootPath();
749            if (!path.endsWith("/")) {
750                path += "/";
751            }
752
753            return path + NAME_ELEMENT_CONTROLCODE;
754        }
755
756        return null;
757    }
758
759    /**
760     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#unlockResource(org.opencms.file.CmsObject, java.lang.String)
761     */
762    @Override
763    public boolean unlockResource(CmsObject cms, String resourcename) throws CmsException {
764
765        CmsResource res = findXmlPage(cms, resourcename);
766        if (res != null) {
767            cms.unlockResource(cms.getRequestContext().removeSiteRoot(res.getRootPath()));
768            return true;
769        }
770
771        return false;
772    }
773
774    /**
775     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#wrapResource(CmsObject, CmsResource)
776     */
777    @Override
778    public CmsResource wrapResource(CmsObject cms, CmsResource res) {
779
780        CmsWrappedResource wrap = new CmsWrappedResource(res);
781        wrap.setFolder(true);
782        return wrap.getResource();
783    }
784
785    /**
786     * @see org.opencms.file.wrapper.A_CmsResourceWrapper#writeFile(org.opencms.file.CmsObject, org.opencms.file.CmsFile)
787     */
788    @Override
789    public CmsFile writeFile(CmsObject cms, CmsFile resource) throws CmsException {
790
791        CmsResource xmlPage = cms.readResource(resource.getStructureId());
792        //CmsResource xmlPage = findXmlPage(cms, resource.getRootPath());
793        if (xmlPage != null) {
794
795            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(xmlPage.getTypeId());
796            if (resType instanceof CmsResourceTypeXmlPage) {
797
798                String path = getSubPath(cms, xmlPage, cms.getRequestContext().removeSiteRoot(resource.getRootPath()));
799
800                CmsFile file = cms.readFile(xmlPage);
801
802                String[] tokens = path.split("/");
803                if (tokens.length == 2) {
804
805                    CmsXmlPage xml = CmsXmlPageFactory.unmarshal(cms, file);
806
807                    // cut off the html suffix
808                    String name = tokens[1];
809                    if (name.endsWith("." + EXTENSION_ELEMENT)) {
810                        name = name.substring(0, name.length() - 5);
811                    }
812
813                    // set content
814                    String content = getStringValue(cms, file, resource.getContents());
815                    content = CmsStringUtil.extractHtmlBody(content).trim();
816                    xml.setStringValue(cms, name, CmsLocaleManager.getLocale(tokens[0]), content);
817
818                    // write file
819                    file.setContents(xml.marshal());
820                    cms.writeFile(file);
821
822                }
823
824                return file;
825            }
826        }
827
828        return null;
829    }
830
831    /**
832     * Returns the OpenCms VFS uri of the style sheet of the resource.<p>
833     *
834     * @param cms the initialized CmsObject
835     * @param res the resource where to read the style sheet for
836     *
837     * @return the OpenCms VFS uri of the style sheet of resource
838     */
839    protected String getUriStyleSheet(CmsObject cms, CmsResource res) {
840
841        String result = "";
842        try {
843            String currentTemplate = getUriTemplate(cms, res);
844            if (!"".equals(currentTemplate)) {
845                // read the stylesheet from the template file
846                result = cms.readPropertyObject(
847                    currentTemplate,
848                    CmsPropertyDefinition.PROPERTY_TEMPLATE,
849                    false).getValue("");
850            }
851        } catch (CmsException e) {
852            // noop
853        }
854        return result;
855    }
856
857    /**
858     * Returns the OpenCms VFS uri of the template of the resource.<p>
859     *
860     * @param cms the initialized CmsObject
861     * @param res the resource where to read the template for
862     *
863     * @return the OpenCms VFS uri of the template of the resource
864     */
865    protected String getUriTemplate(CmsObject cms, CmsResource res) {
866
867        String result = "";
868        try {
869            result = cms.readPropertyObject(
870                cms.getRequestContext().removeSiteRoot(res.getRootPath()),
871                CmsPropertyDefinition.PROPERTY_TEMPLATE,
872                true).getValue("");
873        } catch (CmsException e) {
874            // noop
875        }
876        return result;
877    }
878
879    /**
880     * Prepare the content of a xml page before returning.<p>
881     *
882     * Mainly adds the basic html structure and the css style sheet.<p>
883     *
884     * @param content the origin content of the xml page element
885     * @param cms the initialized CmsObject
886     * @param xmlPage the xml page resource
887     * @param path the full path to set as the title in the html head
888     *
889     * @return the prepared content with the added html structure
890     */
891    protected String prepareContent(String content, CmsObject cms, CmsResource xmlPage, String path) {
892
893        // cut off eventually existing html skeleton
894        content = CmsStringUtil.extractHtmlBody(content);
895
896        // add tags for stylesheet
897        String stylesheet = getUriStyleSheet(cms, xmlPage);
898
899        // content-type
900        String encoding = CmsLocaleManager.getResourceEncoding(cms, xmlPage);
901        String contentType = CmsResourceManager.MIMETYPE_HTML + "; charset=" + encoding;
902
903        content = CmsEncoder.adjustHtmlEncoding(content, encoding);
904
905        // rewrite uri
906        Object obj = cms.getRequestContext().getAttribute(CmsObjectWrapper.ATTRIBUTE_NAME);
907        if (obj != null) {
908            CmsObjectWrapper wrapper = (CmsObjectWrapper)obj;
909            stylesheet = wrapper.rewriteLink(stylesheet);
910        }
911
912        String base = OpenCms.getSystemInfo().getOpenCmsContext();
913
914        StringBuffer result = new StringBuffer(content.length() + 1024);
915        result.append("<html><head>");
916
917        // meta: content-type
918        result.append("<meta http-equiv=\"content-type\" content=\"");
919        result.append(contentType);
920        result.append("\">");
921
922        // title as full path
923        result.append("<title>");
924        result.append(cms.getRequestContext().removeSiteRoot(path));
925        result.append("</title>");
926
927        // stylesheet
928        if (!"".equals(stylesheet)) {
929            result.append("<link href=\"");
930            result.append(base);
931            result.append(stylesheet);
932            result.append("\" rel=\"stylesheet\" type=\"text/css\">");
933        }
934
935        result.append("</head><body>");
936        result.append(content);
937        result.append("</body></html>");
938        content = result.toString();
939
940        return content.trim();
941    }
942
943    /**
944     * Returns the {@link CmsResource} of the xml page which belongs to
945     * the given resource name (full path).<p>
946     *
947     * It works up the path till a resource for the path exists in the VFS.
948     * If the found resource is a xml page, this resource is returned. If
949     * the path does not belong to a xml page <code>null</code> will be returned.<p>
950     *
951     * @param cms the initialized CmsObject
952     * @param resourcename the name of the resource (full path) to check
953     *
954     * @return the found resource of type xml page or null if not found
955     */
956    private CmsResource findXmlPage(CmsObject cms, String resourcename) {
957
958        // get the full folder path of the resource to start from
959        String path = cms.getRequestContext().removeSiteRoot(resourcename);
960        // the path without the trailing slash
961        // for example: .../xmlpage.xml/ -> .../xmlpagepage.xml
962        String reducedPath = CmsFileUtil.removeTrailingSeparator(path);
963        do {
964
965            // check if a resource without the trailing shalsh exists
966            boolean existResource = cms.existsResource(reducedPath);
967            // check if the current folder exists
968            if (cms.existsResource(path) || existResource) {
969                // prove if a resource without the trailing slash does exist
970                if (existResource) {
971                    // a resource without the trailing slash does exist, so take the path without the trailing slash
972                    path = reducedPath;
973                }
974                try {
975                    CmsResource res = cms.readResource(path);
976                    I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(res.getTypeId());
977                    if (resType instanceof CmsResourceTypeXmlPage) {
978                        return res;
979                    } else {
980                        break;
981                    }
982                } catch (CmsException ex) {
983                    break;
984                }
985
986            } else {
987
988                // folder does not exist, go up one folder
989                path = CmsResource.getParentFolder(path);
990                if (path.endsWith("/")) {
991                    path = path.substring(0, path.length() - 1);
992                }
993            }
994
995            if (CmsStringUtil.isEmpty(path)) {
996
997                // site root or root folder reached and no folder found
998                break;
999            }
1000        } while (true);
1001
1002        return null;
1003    }
1004
1005    /**
1006     * Returns a virtual resource for an element inside a locale.<p>
1007     *
1008     * A new (virtual) resource is created with the given path and length. The
1009     * new created resource uses the values of the origin resource of the xml page
1010     * where it is possible.<p>
1011     *
1012     * @param xmlPage the xml page resource with the element to create a virtual resource
1013     * @param path the full path to set for the resource
1014     * @param length the length of the element content
1015     *
1016     * @return a new created virtual {@link CmsResource}
1017     */
1018    private CmsResource getResourceForElement(CmsResource xmlPage, String path, int length) {
1019
1020        CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
1021        wrap.setRootPath(path);
1022        int plainId;
1023        try {
1024            plainId = OpenCms.getResourceManager().getResourceType(
1025                CmsResourceTypePlain.getStaticTypeName()).getTypeId();
1026        } catch (CmsLoaderException e) {
1027            // this should really never happen
1028            plainId = CmsResourceTypePlain.getStaticTypeId();
1029        }
1030        wrap.setTypeId(plainId);
1031        wrap.setFolder(false);
1032        wrap.setLength(length);
1033
1034        return wrap.getResource();
1035    }
1036
1037    /**
1038     * Creates a new virtual resource for the locale in the xml page as a folder.<p>
1039     *
1040     * The new created resource uses the values of the origin resource of the xml page where it is possible.<p>
1041     *
1042     * @param xmlPage the xml page resource with the locale to create a resource of
1043     * @param locale the locale in the xml page to use for the new resource
1044     *
1045     * @return a new created CmsResource
1046     */
1047    private CmsResource getResourceForLocale(CmsResource xmlPage, Locale locale) {
1048
1049        CmsWrappedResource wrap = new CmsWrappedResource(xmlPage);
1050        wrap.setRootPath(xmlPage.getRootPath() + "/" + locale.getLanguage() + "/");
1051        int plainId;
1052        try {
1053            plainId = OpenCms.getResourceManager().getResourceType(
1054                CmsResourceTypePlain.getStaticTypeName()).getTypeId();
1055        } catch (CmsLoaderException e) {
1056            // this should really never happen
1057            plainId = CmsResourceTypePlain.getStaticTypeId();
1058        }
1059        wrap.setTypeId(plainId);
1060        wrap.setFolder(true);
1061
1062        return wrap.getResource();
1063    }
1064
1065    /**
1066     * Returns the content as a string while using the correct encoding.<p>
1067     *
1068     * @param cms the initialized CmsObject
1069     * @param resource the resource where the content belongs to
1070     * @param content the byte array which should be converted into a string
1071     *
1072     * @return the content as a string
1073     *
1074     * @throws CmsException if something goes wrong
1075     */
1076    private String getStringValue(CmsObject cms, CmsResource resource, byte[] content) throws CmsException {
1077
1078        // get the encoding for the resource
1079        CmsProperty prop = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true);
1080        String enc = prop.getValue();
1081        if (enc == null) {
1082            enc = OpenCms.getSystemInfo().getDefaultEncoding();
1083        }
1084
1085        // create a String with the right encoding
1086        return CmsEncoder.createString(content, enc);
1087    }
1088
1089    /**
1090     * Returns the path inside a xml page.<p>
1091     *
1092     * The remaining path inside a xml page can be the locale and the element name
1093     * or the name of the control code file.<p>
1094     *
1095     * @param cms the initialized CmsObject
1096     * @param xmlPage the xml page where the resourcename belongs to
1097     * @param resourcename the full path of the resource (pointing inside the xml page)
1098     *
1099     * @return the remaining path inside the xml page without the leading slash
1100     */
1101    private String getSubPath(CmsObject cms, CmsResource xmlPage, String resourcename) {
1102
1103        if (xmlPage != null) {
1104            String rootPath = cms.getRequestContext().addSiteRoot(resourcename);
1105            String path = rootPath.substring(xmlPage.getRootPath().length());
1106
1107            if (path.startsWith("/")) {
1108                path = path.substring(1);
1109            }
1110
1111            if (path.endsWith("/")) {
1112                path = path.substring(0, path.length() - 1);
1113            }
1114
1115            return path;
1116        }
1117
1118        return null;
1119    }
1120
1121    /**
1122     * Returns a list with virtual file names for the xml page.<p>
1123     *
1124     * Actually that is only the name of the control code file.<p>
1125     *
1126     * @return a list containing strings with the names of the virtual files
1127     */
1128    private List<String> getVirtualFiles() {
1129
1130        ArrayList<String> list = new ArrayList<String>();
1131        list.add(NAME_ELEMENT_CONTROLCODE);
1132
1133        return list;
1134    }
1135}