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