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, 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.ui.apps.sitemanager;
029
030import org.opencms.ade.configuration.CmsADEManager;
031import org.opencms.file.CmsDataAccessException;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsProject;
035import org.opencms.file.CmsProperty;
036import org.opencms.file.CmsPropertyDefinition;
037import org.opencms.file.CmsResource;
038import org.opencms.file.CmsVfsResourceAlreadyExistsException;
039import org.opencms.file.CmsVfsResourceNotFoundException;
040import org.opencms.file.types.CmsResourceTypeFolder;
041import org.opencms.file.types.CmsResourceTypeFolderSubSitemap;
042import org.opencms.file.types.CmsResourceTypeImage;
043import org.opencms.file.types.I_CmsResourceType;
044import org.opencms.i18n.CmsEncoder;
045import org.opencms.loader.CmsLoaderException;
046import org.opencms.lock.CmsLockException;
047import org.opencms.main.CmsException;
048import org.opencms.main.CmsIllegalArgumentException;
049import org.opencms.main.CmsLog;
050import org.opencms.main.OpenCms;
051import org.opencms.report.A_CmsReportThread;
052import org.opencms.report.I_CmsReport;
053import org.opencms.security.I_CmsPrincipal;
054import org.opencms.site.CmsSite;
055import org.opencms.ui.apps.I_CmsCRUDApp;
056import org.opencms.ui.apps.Messages;
057import org.opencms.util.CmsMacroResolver;
058import org.opencms.util.CmsStringUtil;
059import org.opencms.xml.content.CmsXmlContent;
060import org.opencms.xml.content.CmsXmlContentFactory;
061import org.opencms.xml.types.I_CmsXmlContentValue;
062
063import java.io.ByteArrayOutputStream;
064import java.util.Locale;
065import java.util.Map;
066
067import org.apache.commons.logging.Log;
068
069/**
070 * Report thread to save site configurations.<p>
071 */
072public class CmsCreateSiteThread extends A_CmsReportThread {
073
074    /** The logger for this class. */
075    static Log LOG = CmsLog.getLog(CmsCreateSiteThread.class.getName());
076
077    /** Constant. */
078    private static final String BLANK_HTML = "blank.html";
079
080    /**default index.html which gets created.*/
081    private static final String INDEX_HTML = "index.html";
082
083    /** Constant. */
084    private static final String MODEL_PAGE = "ModelPage";
085
086    /** Constant. */
087    private static final String MODEL_PAGE_PAGE = "ModelPage/Page";
088
089    /** Constant. */
090    private static final String NEW = ".templates/";
091
092    /**Map holding key, values for macros.*/
093    private Map<String, String> m_bundle;
094
095    /**CmsObject(root-site).*/
096    private CmsObject m_cms;
097
098    /**CmsObject(root-site,online). */
099    private CmsObject m_cmsOnline;
100
101    /**Indicates if a OU should be created. */
102    private boolean m_createOU;
103
104    /**Runnable which gets used after finishing thread.*/
105    private Runnable m_finished;
106
107    /**Manager app. */
108    private I_CmsCRUDApp<CmsSite> m_manager;
109
110    /**Old version of site to overwrite. */
111    private CmsSite m_oldSite;
112
113    /**FavIcon byte data. */
114    private ByteArrayOutputStream m_os;
115
116    /**Parent OU. */
117    private String m_parentOU;
118
119    /**Selected OU.*/
120    private String m_selectedOU;
121
122    /**Site to save. */
123    private CmsSite m_site;
124
125    /**Source to copy resources from. */
126    private String m_source;
127
128    /**Template to set as property for the site. */
129    private String m_template;
130
131    /**
132     * Constructor for Class.
133     *
134     * @param cms CmsObject
135     * @param manager manager
136     * @param site to be saved
137     * @param oldSite to be overwritten
138     * @param source to copy resources from
139     * @param template to be set as property to root-folder of site
140     * @param createOU indicates if OU should be generated
141     * @param parentOU if createOU==true, sets the parent of the new OU
142     * @param selectedOU set an existing OU
143     * @param os ByteOutputStream with FavIcon data
144     * @param bundle for macro resolving
145     * @param finished runnable which gets called when thread done
146     */
147    protected CmsCreateSiteThread(
148        CmsObject cms,
149        I_CmsCRUDApp<CmsSite> manager,
150        CmsSite site,
151        CmsSite oldSite,
152        String source,
153        String template,
154        boolean createOU,
155        String parentOU,
156        String selectedOU,
157        ByteArrayOutputStream os,
158        Map<String, String> bundle,
159        Runnable finished) {
160
161        super(cms, "createSite");
162        m_cms = cms;
163        m_cmsOnline = getOnlineCmsObject(cms);
164        m_source = source;
165        m_template = template;
166        m_bundle = bundle;
167        m_site = site;
168        m_createOU = createOU;
169        m_oldSite = oldSite;
170        m_parentOU = parentOU;
171        m_selectedOU = selectedOU;
172        m_os = os;
173        m_finished = finished;
174        m_manager = manager;
175        initHtmlReport(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms.getRequestContext()));
176    }
177
178    /**
179     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
180     */
181    @Override
182    public String getReportUpdate() {
183
184        return getReport().getReportUpdate();
185    }
186
187    /**
188     * @see java.lang.Thread#run()
189     */
190    @Override
191    public void run() {
192
193        try {
194
195            if (m_oldSite == null) {
196                getReport().println(
197                    Messages.get().container(Messages.RPT_SITE_START_NEW_1, CmsEncoder.escapeXml(m_site.getTitle())),
198                    I_CmsReport.FORMAT_HEADLINE);
199            } else {
200                getReport().println(
201                    Messages.get().container(Messages.RPT_SITE_START_EDIT_1, CmsEncoder.escapeXml(m_site.getTitle())),
202                    I_CmsReport.FORMAT_HEADLINE);
203            }
204            CmsResource siteRootResource = null;
205
206            if (m_source.isEmpty()) {
207                //Don't copy an existing folder, but create a new one
208                siteRootResource = createSiteRootIfNeeded(m_site.getSiteRoot());
209                String sitePath = m_cms.getSitePath(siteRootResource);
210
211                // create sitemap configuration
212                String contentFolder = CmsStringUtil.joinPaths(sitePath, CmsADEManager.CONTENT_FOLDER_NAME + "/");
213                String sitemapConfig = CmsStringUtil.joinPaths(contentFolder, CmsADEManager.CONFIG_FILE_NAME);
214                if (!m_cms.existsResource(sitemapConfig)) {
215                    createSitemapContentFolder(m_cms, siteRootResource, contentFolder);
216                    createIndexHTML(ensureFoldername(siteRootResource.getRootPath()));
217                }
218            } else {
219                //Copy existing folder to new siteroot and resolve macros
220                CmsMacroResolver.copyAndResolveMacro(
221                    m_cms,
222                    m_source,
223                    m_site.getSiteRoot(),
224                    m_bundle,
225                    true,
226                    CmsResource.COPY_AS_NEW,
227                    getReport());
228
229                siteRootResource = m_cms.readResource(m_site.getSiteRoot());
230
231                adjustFolderType(siteRootResource);
232                setFolderTitle(siteRootResource);
233            }
234
235            setTemplate(siteRootResource);
236
237            saveFavIcon(ensureFoldername(m_site.getSiteRoot()));
238
239            handleOU(siteRootResource);
240
241            try {
242                m_cms.unlockResource(siteRootResource);
243            } catch (CmsLockException e) {
244                LOG.info("Unlock resource failed", e);
245            }
246        } catch (CmsException e) {
247            LOG.error("Error creating site", e);
248            getReport().println(Messages.get().container(Messages.RPT_SITE_ERROR_0), I_CmsReport.FORMAT_ERROR);
249            getReport().println(e);
250            getReport().println();
251            getReport().println(
252                Messages.get().container(Messages.RPT_SITE_FINISH_WARNING_0),
253                I_CmsReport.FORMAT_WARNING);
254            m_finished.run();
255            return;
256        }
257        getReport().println(Messages.get().container(Messages.RPT_SITE_FINISH_0), I_CmsReport.FORMAT_OK);
258        m_finished.run();
259        m_manager.writeElement(m_site);
260    }
261
262    /**
263     * Checks if there are at least one character in the folder name,
264     * also ensures that it ends with a '/' and doesn't start with '/'.<p>
265     *
266     * @param resourcename folder name to check (complete path)
267     * @return the validated folder name
268     * @throws CmsIllegalArgumentException if the folder name is empty or <code>null</code>
269     */
270    String ensureFoldername(String resourcename) {
271
272        if (CmsStringUtil.isEmpty(resourcename)) {
273            return "";
274        }
275        if (!CmsResource.isFolder(resourcename)) {
276            resourcename = resourcename.concat("/");
277        }
278        if (resourcename.charAt(0) == '/') {
279            resourcename = resourcename.substring(1);
280        }
281        return resourcename;
282    }
283
284    /**
285     * Changes the folder type if necessary:<p>
286     *
287     * Type Folder -> Type SubsitemapFolder.<p>
288     * others -> no change.<p>
289     *
290     * @param siteRootResource resource to be changed
291     * @throws CmsLoaderException exception
292     * @throws CmsException exception
293     */
294    @SuppressWarnings("deprecation")
295    private void adjustFolderType(CmsResource siteRootResource) throws CmsLoaderException, CmsException {
296
297        if (OpenCms.getResourceManager().getResourceType(
298            CmsResourceTypeFolder.RESOURCE_TYPE_NAME) == OpenCms.getResourceManager().getResourceType(
299                siteRootResource)) {
300
301            siteRootResource.setType(
302                OpenCms.getResourceManager().getResourceType(
303                    CmsResourceTypeFolderSubSitemap.TYPE_SUBSITEMAP).getTypeId());
304            m_cms.writeResource(siteRootResource);
305        }
306    }
307
308    /**
309     * Creates new index html if no one was found.<p>
310     *
311     * @param siteRoot of new site
312     * @throws CmsIllegalArgumentException exception
313     * @throws CmsException exception
314     */
315    private void createIndexHTML(String siteRoot) throws CmsIllegalArgumentException, CmsException {
316
317        if (!m_cms.existsResource(siteRoot + INDEX_HTML)) {
318            //Create index.html
319            I_CmsResourceType containerType = OpenCms.getResourceManager().getResourceType(
320                org.opencms.file.types.CmsResourceTypeXmlContainerPage.RESOURCE_TYPE_NAME);
321            m_cms.createResource(siteRoot + INDEX_HTML, containerType);
322        }
323    }
324
325    /**
326    * Helper method for creating the .content folder of a sub-sitemap.<p>
327    *
328    * @param cms the current CMS context
329    * @param subSitemapFolder the sub-sitemap folder in which the .content folder should be created
330    * @param contentFolder the content folder path
331    * @throws CmsException if something goes wrong
332    * @throws CmsLoaderException if something goes wrong
333    */
334    private void createSitemapContentFolder(CmsObject cms, CmsResource subSitemapFolder, String contentFolder)
335    throws CmsException, CmsLoaderException {
336
337        CmsResource configFile = null;
338        String sitePath = cms.getSitePath(subSitemapFolder);
339        String folderName = CmsStringUtil.joinPaths(sitePath, CmsADEManager.CONTENT_FOLDER_NAME + "/");
340        String sitemapConfigName = CmsStringUtil.joinPaths(folderName, CmsADEManager.CONFIG_FILE_NAME);
341        if (!cms.existsResource(folderName)) {
342            cms.createResource(
343                folderName,
344                OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_FOLDER_TYPE));
345        }
346        I_CmsResourceType configType = OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_TYPE);
347        if (cms.existsResource(sitemapConfigName)) {
348            configFile = cms.readResource(sitemapConfigName);
349            if (!OpenCms.getResourceManager().getResourceType(configFile).getTypeName().equals(
350                configType.getTypeName())) {
351                throw new CmsException(
352                    Messages.get().container(
353                        Messages.ERR_CREATING_SUB_SITEMAP_WRONG_CONFIG_FILE_TYPE_2,
354                        sitemapConfigName,
355                        CmsADEManager.CONFIG_TYPE));
356            }
357        } else {
358            configFile = cms.createResource(
359                sitemapConfigName,
360                OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_TYPE));
361        }
362
363        if (configFile != null) {
364            try {
365                CmsResource newFolder = m_cms.createResource(
366                    contentFolder + NEW,
367                    OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.RESOURCE_TYPE_NAME));
368                I_CmsResourceType containerType = OpenCms.getResourceManager().getResourceType(
369                    org.opencms.file.types.CmsResourceTypeXmlContainerPage.RESOURCE_TYPE_NAME);
370                CmsResource modelPage = m_cms.createResource(newFolder.getRootPath() + BLANK_HTML, containerType);
371                String defTitle = Messages.get().getBundle(m_cms.getRequestContext().getLocale()).key(
372                    Messages.GUI_DEFAULT_MODEL_TITLE_1,
373                    m_site.getTitle());
374                String defDes = Messages.get().getBundle(m_cms.getRequestContext().getLocale()).key(
375                    Messages.GUI_DEFAULT_MODEL_DESCRIPTION_1,
376                    m_site.getTitle());
377                CmsProperty prop = new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, defTitle, defTitle);
378                m_cms.writePropertyObject(modelPage.getRootPath(), prop);
379                prop = new CmsProperty(CmsPropertyDefinition.PROPERTY_DESCRIPTION, defDes, defDes);
380                m_cms.writePropertyObject(modelPage.getRootPath(), prop);
381                CmsFile file = m_cms.readFile(configFile);
382                CmsXmlContent con = CmsXmlContentFactory.unmarshal(m_cms, file);
383                con.addValue(m_cms, MODEL_PAGE, Locale.ENGLISH, 0);
384                I_CmsXmlContentValue val = con.getValue(MODEL_PAGE_PAGE, Locale.ENGLISH);
385                val.setStringValue(m_cms, modelPage.getRootPath());
386                file.setContents(con.marshal());
387                m_cms.writeFile(file);
388            } catch (CmsException e) {
389                LOG.error(e.getLocalizedMessage(), e);
390            }
391        }
392    }
393
394    /**
395     * Creates root-folder for the new site.<p>
396     * If folder exist, the existing one will be returned.<p>
397     *
398     * @param siteRoot path to be created resp. read.
399     * @return site root folder
400     * @throws CmsException exception
401     */
402    private CmsResource createSiteRootIfNeeded(String siteRoot) throws CmsException {
403
404        CmsResource siteRootResource = null;
405
406        // check if the site root already exists
407        try {
408            // take the existing site and do not perform any OU related actions
409            if (m_cms.existsResource(siteRoot)) {
410                siteRootResource = m_cms.readResource(siteRoot);
411            } else {
412                CmsResource onlineVersion = m_cmsOnline.readResource(siteRoot);
413                siteRootResource = m_cms.readResource(onlineVersion.getStructureId());
414            }
415
416        } catch (CmsVfsResourceNotFoundException e) {
417            // not create a new site folder and the according OU if option is checked checked
418            getReport().println(
419                Messages.get().container(Messages.RPT_SITE_CREATE_RESOURCES_0),
420                I_CmsReport.FORMAT_DEFAULT);
421            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(
422                CmsResourceTypeFolderSubSitemap.TYPE_SUBSITEMAP);
423            siteRootResource = m_cms.createResource(siteRoot, type);
424            CmsProperty folderTitle = new CmsProperty(
425                CmsPropertyDefinition.PROPERTY_TITLE,
426                m_site.getTitle(),
427                m_site.getTitle());
428            m_cms.writePropertyObject(siteRoot, folderTitle);
429
430        }
431        return siteRootResource;
432    }
433
434    /**
435     * Creates online version of given CmsObject.<p>
436     *
437     * @param cms given CmsObject
438     * @return online CmsObject
439     */
440    private CmsObject getOnlineCmsObject(CmsObject cms) {
441
442        CmsObject res = null;
443        try {
444            res = OpenCms.initCmsObject(cms);
445            res.getRequestContext().setCurrentProject(cms.readProject(CmsProject.ONLINE_PROJECT_ID));
446        } catch (CmsException e) {
447            LOG.error("Cannot create CmsObject", e);
448        }
449        return res;
450    }
451
452    /**
453     * Handles OU related operations.<p>
454     * Creates OU, adds site root to existing OU or does nothing.<p>
455     *
456     * @param siteRootResource Resource representing root folder
457     */
458    private void handleOU(CmsResource siteRootResource) {
459
460        String ouName = null;
461        String ouDescription = "OU for: %(site)";
462
463        if (m_createOU) {
464            getReport().println(
465                Messages.get().container(Messages.RPT_SITE_CREATE_OU_1, m_parentOU + siteRootResource.getName()),
466                I_CmsReport.FORMAT_DEFAULT);
467            try {
468                OpenCms.getOrgUnitManager().createOrganizationalUnit(
469                    m_cms,
470                    m_parentOU + siteRootResource.getName(),
471                    ouDescription.replace("%(site)", m_site.getTitle() + " [" + siteRootResource.getRootPath() + "]"),
472                    0,
473                    siteRootResource.getRootPath());
474                ouName = m_parentOU + siteRootResource.getName();
475            } catch (CmsDataAccessException e) {
476                LOG.info("Can't create OU, an OU with same name exists. The existing OU is chosen for the new site");
477                try {
478                    OpenCms.getOrgUnitManager().addResourceToOrgUnit(
479                        m_cms,
480                        m_parentOU + siteRootResource.getName(),
481                        siteRootResource.getRootPath());
482                    ouName = m_parentOU + siteRootResource.getName();
483                } catch (CmsException e2) {
484                    LOG.info("Resource is already added to OU");
485                    ouName = m_parentOU + siteRootResource.getName();
486                }
487            } catch (CmsException e) {
488                LOG.error("Error on creating new OU", e);
489            }
490        }
491
492        if ((m_oldSite == null) & (m_selectedOU != null) & !m_selectedOU.equals("/")) {
493            getReport().println(
494                Messages.get().container(Messages.RPT_SITE_ADD_OU_1, m_selectedOU),
495                I_CmsReport.FORMAT_DEFAULT);
496            try {
497                OpenCms.getOrgUnitManager().addResourceToOrgUnit(m_cms, m_selectedOU, siteRootResource.getRootPath());
498                ouName = m_selectedOU.substring(0, (m_selectedOU).length() - 1);
499            } catch (CmsException e) {
500                LOG.error("Error on adding resource to OU", e);
501            }
502        }
503
504        try {
505            m_cms.lockResource(siteRootResource);
506        } catch (CmsException e) {
507            LOG.error("unable to lock resource", e);
508        }
509
510        if (ouName != null) {
511            try {
512
513                m_cms.chacc(
514                    siteRootResource.getRootPath(),
515                    I_CmsPrincipal.PRINCIPAL_GROUP,
516                    ouName + "/Users",
517                    "+r+w+v+c+i+o+d");
518            } catch (CmsException e) {
519                LOG.error("Error on setting permission for OU.", e);
520            }
521        }
522        try {
523            m_cms.unlockResource(siteRootResource);
524        } catch (CmsException e) {
525            LOG.error("unable to unlock resource");
526        }
527    }
528
529    /**
530     * Saves outputstream of favicon as resource.<p>
531     *
532     * @param siteRoot site root of considered site.
533     */
534    private void saveFavIcon(String siteRoot) {
535
536        if (m_os == null) {
537            return;
538        }
539        if (m_os.size() == 0) {
540            return;
541        }
542
543        getReport().println(Messages.get().container(Messages.RPT_SITE_SET_FAVICON_0), I_CmsReport.FORMAT_DEFAULT);
544        CmsResource favicon = null;
545        try {
546            favicon = m_cms.createResource(
547                siteRoot + CmsSiteManager.FAVICON,
548                OpenCms.getResourceManager().getResourceType(CmsResourceTypeImage.getStaticTypeName()));
549        } catch (CmsVfsResourceAlreadyExistsException e) {
550            //OK, Resource already there
551            try {
552                favicon = m_cms.readResource(siteRoot + CmsSiteManager.FAVICON);
553            } catch (CmsException e2) {
554                //no, it wasn't..
555                getReport().println(
556                    Messages.get().container(Messages.RPT_SITE_ERROR_FAVICON_0),
557                    I_CmsReport.FORMAT_ERROR);
558                getReport().println(e);
559                getReport().println(e2);
560                return;
561            }
562        } catch (CmsIllegalArgumentException | CmsException e) {
563            getReport().println(Messages.get().container(Messages.RPT_SITE_ERROR_FAVICON_0), I_CmsReport.FORMAT_ERROR);
564            getReport().println(e);
565            return;
566        }
567        try {
568            m_cms.lockResource(siteRoot + CmsSiteManager.FAVICON);
569            CmsFile faviconFile = new CmsFile(favicon);
570            faviconFile.setContents(m_os.toByteArray());
571            m_cms.writeFile(faviconFile);
572            m_cms.unlockResource(siteRoot + CmsSiteManager.FAVICON);
573        } catch (CmsException e) {
574            getReport().println(Messages.get().container(Messages.RPT_SITE_ERROR_FAVICON_0), I_CmsReport.FORMAT_ERROR);
575            getReport().println(e);
576            return;
577        }
578
579    }
580
581    /**
582     * Updates title property of site root resource in case of copy from template.<p>
583     *
584     * @param res root resource to set titel for
585     */
586    private void setFolderTitle(CmsResource res) {
587
588        try {
589            CmsProperty titleProperty = m_cms.readPropertyObject(res, CmsPropertyDefinition.PROPERTY_TITLE, false);
590            if (!titleProperty.isNullProperty()) {
591                titleProperty.setValue(m_site.getTitle(), CmsProperty.TYPE_INDIVIDUAL);
592                m_cms.writePropertyObject(res.getRootPath(), titleProperty);
593            } else {
594                LOG.error("Editing title property of site root resource was not possible");
595                getReport().println(
596                    Messages.get().container(Messages.RPT_SITE_ERROR_TITLE_0),
597                    I_CmsReport.FORMAT_ERROR);
598            }
599        } catch (CmsException e) {
600            LOG.error("Editing title property of site root resource was not possible", e);
601            getReport().println(Messages.get().container(Messages.RPT_SITE_ERROR_TITLE_0), I_CmsReport.FORMAT_ERROR);
602            getReport().println(e);
603        }
604
605    }
606
607    /**
608     * Sets the selected template as property to site root folder.<p>
609     *
610     * @param siteRootResource Resource representing root folder
611     */
612    private void setTemplate(CmsResource siteRootResource) {
613
614        try {
615            m_cms.lockResource(siteRootResource);
616            // add template  property
617            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_template)) {
618                CmsProperty prop = new CmsProperty(CmsPropertyDefinition.PROPERTY_TEMPLATE, m_template, null);
619                m_cms.writePropertyObject(siteRootResource.getRootPath(), prop);
620            }
621            m_cms.unlockResource(siteRootResource);
622        } catch (CmsException e) {
623            LOG.error("Error on adding template", e);
624        }
625    }
626
627}