1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   * 
13   */
14  
15  package com.liferay.portal.lar;
16  
17  import com.liferay.counter.service.CounterLocalServiceUtil;
18  import com.liferay.portal.LARFileException;
19  import com.liferay.portal.LARTypeException;
20  import com.liferay.portal.LayoutImportException;
21  import com.liferay.portal.NoSuchLayoutException;
22  import com.liferay.portal.PortalException;
23  import com.liferay.portal.SystemException;
24  import com.liferay.portal.kernel.cluster.ClusterLinkUtil;
25  import com.liferay.portal.kernel.cluster.Priority;
26  import com.liferay.portal.kernel.log.Log;
27  import com.liferay.portal.kernel.log.LogFactoryUtil;
28  import com.liferay.portal.kernel.messaging.Message;
29  import com.liferay.portal.kernel.util.ArrayUtil;
30  import com.liferay.portal.kernel.util.FileUtil;
31  import com.liferay.portal.kernel.util.GetterUtil;
32  import com.liferay.portal.kernel.util.LocaleUtil;
33  import com.liferay.portal.kernel.util.LocalizationUtil;
34  import com.liferay.portal.kernel.util.MapUtil;
35  import com.liferay.portal.kernel.util.MethodWrapper;
36  import com.liferay.portal.kernel.util.ReleaseInfo;
37  import com.liferay.portal.kernel.util.StringPool;
38  import com.liferay.portal.kernel.util.StringUtil;
39  import com.liferay.portal.kernel.util.Time;
40  import com.liferay.portal.kernel.util.UnicodeProperties;
41  import com.liferay.portal.kernel.util.Validator;
42  import com.liferay.portal.kernel.xml.Document;
43  import com.liferay.portal.kernel.xml.DocumentException;
44  import com.liferay.portal.kernel.xml.Element;
45  import com.liferay.portal.kernel.xml.SAXReaderUtil;
46  import com.liferay.portal.kernel.zip.ZipReader;
47  import com.liferay.portal.kernel.zip.ZipReaderFactoryUtil;
48  import com.liferay.portal.model.Layout;
49  import com.liferay.portal.model.LayoutSet;
50  import com.liferay.portal.model.LayoutTemplate;
51  import com.liferay.portal.model.LayoutTypePortlet;
52  import com.liferay.portal.model.PortletConstants;
53  import com.liferay.portal.model.User;
54  import com.liferay.portal.model.impl.ColorSchemeImpl;
55  import com.liferay.portal.model.impl.LayoutTypePortletImpl;
56  import com.liferay.portal.service.ImageLocalServiceUtil;
57  import com.liferay.portal.service.LayoutLocalServiceUtil;
58  import com.liferay.portal.service.LayoutSetLocalServiceUtil;
59  import com.liferay.portal.service.LayoutTemplateLocalServiceUtil;
60  import com.liferay.portal.service.ServiceContext;
61  import com.liferay.portal.service.persistence.LayoutUtil;
62  import com.liferay.portal.service.persistence.UserUtil;
63  import com.liferay.portal.theme.ThemeLoader;
64  import com.liferay.portal.theme.ThemeLoaderFactory;
65  import com.liferay.portal.util.PortalUtil;
66  import com.liferay.portal.util.PortletKeys;
67  import com.liferay.portal.util.PropsValues;
68  import com.liferay.portlet.journal.model.JournalArticle;
69  import com.liferay.portlet.tags.DuplicateEntryException;
70  import com.liferay.portlet.tags.DuplicateVocabularyException;
71  import com.liferay.portlet.tags.model.TagsEntryConstants;
72  import com.liferay.portlet.tags.service.TagsEntryLocalServiceUtil;
73  import com.liferay.portlet.tags.service.TagsVocabularyLocalServiceUtil;
74  
75  import java.io.File;
76  import java.io.IOException;
77  import java.io.InputStream;
78  
79  import java.util.ArrayList;
80  import java.util.Date;
81  import java.util.HashSet;
82  import java.util.List;
83  import java.util.Locale;
84  import java.util.Map;
85  import java.util.Set;
86  
87  import org.apache.commons.lang.time.StopWatch;
88  
89  /**
90   * <a href="LayoutImporter.java.html"><b><i>View Source</i></b></a>
91   *
92   * @author Brian Wing Shun Chan
93   * @author Joel Kozikowski
94   * @author Charles May
95   * @author Raymond Augé
96   * @author Jorge Ferrer
97   * @author Bruno Farache
98   * @author Wesley Gong
99   * @author Zsigmond Rab
100  * @author Douglas Wong
101  */
102 public class LayoutImporter {
103 
104     public void importLayouts(
105             long userId, long groupId, boolean privateLayout,
106             Map<String, String[]> parameterMap, File file)
107         throws PortalException, SystemException {
108 
109         boolean deleteMissingLayouts = MapUtil.getBoolean(
110             parameterMap, PortletDataHandlerKeys.DELETE_MISSING_LAYOUTS,
111             Boolean.TRUE.booleanValue());
112         boolean deletePortletData = MapUtil.getBoolean(
113             parameterMap, PortletDataHandlerKeys.DELETE_PORTLET_DATA);
114         boolean importCategories = MapUtil.getBoolean(
115             parameterMap, PortletDataHandlerKeys.CATEGORIES);
116         boolean importPermissions = MapUtil.getBoolean(
117             parameterMap, PortletDataHandlerKeys.PERMISSIONS);
118         boolean importUserPermissions = MapUtil.getBoolean(
119             parameterMap, PortletDataHandlerKeys.PERMISSIONS);
120         boolean importPortletData = MapUtil.getBoolean(
121             parameterMap, PortletDataHandlerKeys.PORTLET_DATA);
122         boolean importPortletSetup = MapUtil.getBoolean(
123             parameterMap, PortletDataHandlerKeys.PORTLET_SETUP);
124         boolean importPortletArchivedSetups = MapUtil.getBoolean(
125             parameterMap, PortletDataHandlerKeys.PORTLET_ARCHIVED_SETUPS);
126         boolean importPortletUserPreferences = MapUtil.getBoolean(
127             parameterMap, PortletDataHandlerKeys.PORTLET_USER_PREFERENCES);
128         boolean importTheme = MapUtil.getBoolean(
129             parameterMap, PortletDataHandlerKeys.THEME);
130         String layoutsImportMode = MapUtil.getString(
131             parameterMap, PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE,
132             PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE_MERGE_BY_LAYOUT_ID);
133         String portletsMergeMode = MapUtil.getString(
134             parameterMap, PortletDataHandlerKeys.PORTLETS_MERGE_MODE,
135             PortletDataHandlerKeys.PORTLETS_MERGE_MODE_REPLACE);
136         String userIdStrategy = MapUtil.getString(
137             parameterMap, PortletDataHandlerKeys.USER_ID_STRATEGY);
138 
139         if (_log.isDebugEnabled()) {
140             _log.debug("Delete portlet data " + deletePortletData);
141             _log.debug("Import categories " + importCategories);
142             _log.debug("Import permissions " + importPermissions);
143             _log.debug("Import user permissions " + importUserPermissions);
144             _log.debug("Import portlet data " + importPortletData);
145             _log.debug("Import portlet setup " + importPortletSetup);
146             _log.debug(
147                 "Import portlet archived setups " +
148                     importPortletArchivedSetups);
149             _log.debug(
150                 "Import portlet user preferences " +
151                     importPortletUserPreferences);
152             _log.debug("Import theme " + importTheme);
153         }
154 
155         StopWatch stopWatch = null;
156 
157         if (_log.isInfoEnabled()) {
158             stopWatch = new StopWatch();
159 
160             stopWatch.start();
161         }
162 
163         LayoutCache layoutCache = new LayoutCache();
164 
165         LayoutSet layoutSet = LayoutSetLocalServiceUtil.getLayoutSet(
166             groupId, privateLayout);
167 
168         long companyId = layoutSet.getCompanyId();
169 
170         User user = UserUtil.findByPrimaryKey(userId);
171 
172         UserIdStrategy strategy = _portletImporter.getUserIdStrategy(
173             user, userIdStrategy);
174 
175         ZipReader zipReader = ZipReaderFactoryUtil.getZipReader(file);
176 
177         PortletDataContext context = new PortletDataContextImpl(
178             companyId, groupId, parameterMap, new HashSet<String>(), strategy,
179             zipReader);
180 
181         context.setPrivateLayout(privateLayout);
182 
183         // Zip
184 
185         Element root = null;
186         InputStream themeZip = null;
187 
188         // Manifest
189 
190         String xml = context.getZipEntryAsString("/manifest.xml");
191 
192         if (xml == null) {
193             throw new LARFileException("manifest.xml not found in the LAR");
194         }
195 
196         try {
197             Document doc = SAXReaderUtil.read(xml);
198 
199             root = doc.getRootElement();
200         }
201         catch (Exception e) {
202             throw new LARFileException(e);
203         }
204 
205         // Build compatibility
206 
207         Element header = root.element("header");
208 
209         int buildNumber = ReleaseInfo.getBuildNumber();
210 
211         int importBuildNumber = GetterUtil.getInteger(
212             header.attributeValue("build-number"));
213 
214         if (buildNumber != importBuildNumber) {
215             throw new LayoutImportException(
216                 "LAR build number " + importBuildNumber + " does not match " +
217                     "portal build number " + buildNumber);
218         }
219 
220         // Type compatibility
221 
222         String larType = header.attributeValue("type");
223 
224         if (!larType.equals("layout-set")) {
225             throw new LARTypeException(
226                 "Invalid type of LAR file (" + larType + ")");
227         }
228 
229         // Import GroupId
230 
231         long sourceGroupId = GetterUtil.getLong(
232             header.attributeValue("group-id"));
233 
234         context.setSourceGroupId(sourceGroupId);
235 
236         // Look and feel
237 
238         if (importTheme) {
239             themeZip = context.getZipEntryAsInputStream("theme.zip");
240         }
241 
242         // Look and feel
243 
244         String themeId = header.attributeValue("theme-id");
245         String colorSchemeId = header.attributeValue("color-scheme-id");
246 
247         boolean useThemeZip = false;
248 
249         if (themeZip != null) {
250             try {
251                 String importThemeId = importTheme(layoutSet, themeZip);
252 
253                 if (importThemeId != null) {
254                     themeId = importThemeId;
255                     colorSchemeId =
256                         ColorSchemeImpl.getDefaultRegularColorSchemeId();
257 
258                     useThemeZip = true;
259                 }
260 
261                 if (_log.isDebugEnabled()) {
262                     _log.debug(
263                         "Importing theme takes " + stopWatch.getTime() + " ms");
264                 }
265             }
266             catch (Exception e) {
267                 throw new SystemException(e);
268             }
269         }
270 
271         boolean wapTheme = false;
272 
273         LayoutSetLocalServiceUtil.updateLookAndFeel(
274             groupId, privateLayout, themeId, colorSchemeId, StringPool.BLANK,
275             wapTheme);
276 
277         // Read categories, comments, ratings, and tags to make them available
278         // to the data handlers through the context
279 
280         if (importCategories) {
281             importCategories(context);
282         }
283 
284         _portletImporter.readCategories(context, root);
285         _portletImporter.readComments(context, root);
286 
287         if (importPermissions) {
288             _permissionImporter.readPortletDataPermissions(context);
289         }
290 
291         _portletImporter.readRatings(context, root);
292         _portletImporter.readTags(context, root);
293 
294         // Layouts
295 
296         List<Layout> previousLayouts = LayoutUtil.findByG_P(
297             groupId, privateLayout);
298 
299         List<Layout> newLayouts = new ArrayList<Layout>();
300 
301         Set<Long> newLayoutIds = new HashSet<Long>();
302 
303         Map<Long, Long> newLayoutIdPlidMap =
304             (Map<Long, Long>)context.getNewPrimaryKeysMap(Layout.class);
305 
306         List<Element> layoutEls = root.element("layouts").elements("layout");
307 
308         if (_log.isDebugEnabled()) {
309             if (layoutEls.size() > 0) {
310                 _log.debug("Importing layouts");
311             }
312         }
313 
314         for (Element layoutRefEl : layoutEls) {
315             long layoutId = GetterUtil.getInteger(
316                 layoutRefEl.attributeValue("layout-id"));
317 
318             long oldLayoutId = layoutId;
319 
320             boolean deleteLayout = GetterUtil.getBoolean(
321                 layoutRefEl.attributeValue("delete"));
322 
323             if (deleteLayout) {
324                 try {
325                     LayoutLocalServiceUtil.deleteLayout(
326                         context.getGroupId(), privateLayout, oldLayoutId);
327                 }
328                 catch (NoSuchLayoutException nsle) {
329                     _log.warn(
330                         "Error deleting layout for {" + sourceGroupId + ", " +
331                             privateLayout + ", " + oldLayoutId + "}");
332                 }
333 
334                 continue;
335             }
336 
337             String layoutPath = layoutRefEl.attributeValue("path");
338 
339             Element layoutEl = null;
340 
341             try {
342                 Document layoutDoc = SAXReaderUtil.read(
343                     context.getZipEntryAsString(layoutPath));
344 
345                 layoutEl = layoutDoc.getRootElement();
346             }
347             catch (DocumentException de) {
348                 throw new SystemException(de);
349             }
350 
351             long parentLayoutId = GetterUtil.getInteger(
352                 layoutEl.elementText("parent-layout-id"));
353 
354             if (_log.isDebugEnabled()) {
355                 _log.debug(
356                     "Importing layout with layout id " + layoutId +
357                         " and parent layout id " + parentLayoutId);
358             }
359 
360             long oldPlid = GetterUtil.getInteger(
361                 layoutEl.attributeValue("old-plid"));
362 
363             String name = layoutEl.elementText("name");
364             String title = layoutEl.elementText("title");
365             String description = layoutEl.elementText("description");
366             String type = layoutEl.elementText("type");
367             String typeSettings = layoutEl.elementText("type-settings");
368             boolean hidden = GetterUtil.getBoolean(
369                 layoutEl.elementText("hidden"));
370             String friendlyURL = layoutEl.elementText("friendly-url");
371             boolean iconImage = GetterUtil.getBoolean(
372                 layoutEl.elementText("icon-image"));
373 
374             byte[] iconBytes = null;
375 
376             if (iconImage) {
377                 String path = layoutEl.elementText("icon-image-path");
378 
379                 iconBytes = context.getZipEntryAsByteArray(path);
380             }
381 
382             if (useThemeZip) {
383                 themeId = StringPool.BLANK;
384                 colorSchemeId = StringPool.BLANK;
385             }
386             else {
387                 themeId = layoutEl.elementText("theme-id");
388                 colorSchemeId = layoutEl.elementText("color-scheme-id");
389             }
390 
391             String wapThemeId = layoutEl.elementText("wap-theme-id");
392             String wapColorSchemeId = layoutEl.elementText(
393                 "wap-color-scheme-id");
394             String css = layoutEl.elementText("css");
395             int priority = GetterUtil.getInteger(
396                 layoutEl.elementText("priority"));
397 
398             Layout layout = null;
399 
400             if (layoutsImportMode.equals(
401                     PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE_ADD_AS_NEW)) {
402 
403                 layoutId = LayoutLocalServiceUtil.getNextLayoutId(
404                     groupId, privateLayout);
405                 friendlyURL = StringPool.SLASH + layoutId;
406             }
407             else if (layoutsImportMode.equals(
408                     PortletDataHandlerKeys.
409                         LAYOUTS_IMPORT_MODE_MERGE_BY_LAYOUT_NAME)) {
410 
411                 Locale locale = LocaleUtil.getDefault();
412 
413                 String localizedName = LocalizationUtil.getLocalization(
414                     name, LocaleUtil.toLanguageId(locale));
415 
416                 for (Layout curLayout : previousLayouts) {
417                     if (curLayout.getName(locale).equals(localizedName)) {
418                         layout = curLayout;
419 
420                         break;
421                     }
422                 }
423 
424                 if (layout == null) {
425                     layoutId = LayoutLocalServiceUtil.getNextLayoutId(
426                         groupId, privateLayout);
427                 }
428             }
429             else {
430                 layout = LayoutUtil.fetchByG_P_L(
431                     groupId, privateLayout, layoutId);
432 
433                 if (layout == null) {
434                     layoutId = LayoutLocalServiceUtil.getNextLayoutId(
435                         groupId, privateLayout);
436                 }
437             }
438 
439             if (_log.isDebugEnabled()) {
440                 if (layout == null) {
441                     _log.debug(
442                         "Layout with {groupId=" + groupId + ",privateLayout=" +
443                             privateLayout + ",layoutId=" + layoutId +
444                                 "} does not exist");
445                 }
446                 else {
447                     _log.debug(
448                         "Layout with {groupId=" + groupId + ",privateLayout=" +
449                             privateLayout + ",layoutId=" + layoutId +
450                                 "} exists");
451                 }
452             }
453 
454             if (layout == null) {
455                 long plid = CounterLocalServiceUtil.increment();
456 
457                 layout = LayoutUtil.create(plid);
458 
459                 layout.setGroupId(groupId);
460                 layout.setPrivateLayout(privateLayout);
461                 layout.setLayoutId(layoutId);
462             }
463 
464             layout.setCompanyId(user.getCompanyId());
465             layout.setParentLayoutId(parentLayoutId);
466             layout.setName(name);
467             layout.setTitle(title);
468             layout.setDescription(description);
469             layout.setType(type);
470 
471             if (layout.isTypePortlet() &&
472                 Validator.isNotNull(layout.getTypeSettings()) &&
473                 !portletsMergeMode.equals(
474                     PortletDataHandlerKeys.PORTLETS_MERGE_MODE_REPLACE)) {
475 
476                 mergePortlets(layout, typeSettings, portletsMergeMode);
477             }
478             else {
479                 layout.setTypeSettings(typeSettings);
480             }
481 
482             layout.setHidden(hidden);
483             layout.setFriendlyURL(friendlyURL);
484 
485             if (iconImage) {
486                 layout.setIconImage(iconImage);
487 
488                 if (layout.isNew()) {
489                     long iconImageId = CounterLocalServiceUtil.increment();
490 
491                     layout.setIconImageId(iconImageId);
492                 }
493             }
494 
495             layout.setThemeId(themeId);
496             layout.setColorSchemeId(colorSchemeId);
497             layout.setWapThemeId(wapThemeId);
498             layout.setWapColorSchemeId(wapColorSchemeId);
499             layout.setCss(css);
500             layout.setPriority(priority);
501 
502             fixTypeSettings(layout);
503 
504             LayoutUtil.update(layout, false);
505 
506             if ((iconBytes != null) && (iconBytes.length > 0)) {
507                 ImageLocalServiceUtil.updateImage(
508                     layout.getIconImageId(), iconBytes);
509             }
510 
511             context.setPlid(layout.getPlid());
512             context.setOldPlid(oldPlid);
513 
514             newLayoutIdPlidMap.put(oldLayoutId, layout.getPlid());
515 
516             newLayoutIds.add(layoutId);
517 
518             newLayouts.add(layout);
519 
520             // Layout permissions
521 
522             if (importPermissions) {
523                 _permissionImporter.importLayoutPermissions(
524                     layoutCache, companyId, groupId, userId, layout, layoutEl,
525                     root, importUserPermissions);
526             }
527 
528             _portletImporter.importPortletData(
529                 context, PortletKeys.LAYOUT_CONFIGURATION, null, layoutEl);
530         }
531 
532         List<Element> portletEls = root.element("portlets").elements("portlet");
533 
534         // Delete portlet data
535 
536         if (deletePortletData) {
537             if (_log.isDebugEnabled()) {
538                 if (portletEls.size() > 0) {
539                     _log.debug("Deleting portlet data");
540                 }
541             }
542 
543             for (Element portletRefEl : portletEls) {
544                 String portletId = portletRefEl.attributeValue("portlet-id");
545                 long layoutId = GetterUtil.getLong(
546                     portletRefEl.attributeValue("layout-id"));
547                 long plid = newLayoutIdPlidMap.get(layoutId);
548 
549                 context.setPlid(plid);
550 
551                 _portletImporter.deletePortletData(context, portletId, plid);
552             }
553         }
554 
555         // Import portlets
556 
557         if (_log.isDebugEnabled()) {
558             if (portletEls.size() > 0) {
559                 _log.debug("Importing portlets");
560             }
561         }
562 
563         for (Element portletRefEl : portletEls) {
564             String portletPath = portletRefEl.attributeValue("path");
565             String portletId = portletRefEl.attributeValue("portlet-id");
566             long layoutId = GetterUtil.getLong(
567                 portletRefEl.attributeValue("layout-id"));
568             long plid = newLayoutIdPlidMap.get(layoutId);
569             long oldPlid = GetterUtil.getLong(
570                 portletRefEl.attributeValue("old-plid"));
571 
572             Layout layout = LayoutUtil.findByPrimaryKey(plid);
573 
574             context.setPlid(plid);
575             context.setOldPlid(oldPlid);
576 
577             Element portletEl = null;
578 
579             try {
580                 Document portletDoc = SAXReaderUtil.read(
581                     context.getZipEntryAsString(portletPath));
582 
583                 portletEl = portletDoc.getRootElement();
584             }
585             catch (DocumentException de) {
586                 throw new SystemException(de);
587             }
588 
589             // The order of the import is important. You must always import
590             // the portlet preferences first, then the portlet data, then
591             // the portlet permissions. The import of the portlet data
592             // assumes that portlet preferences already exist.
593 
594             // Portlet preferences
595 
596             _portletImporter.importPortletPreferences(
597                 context, layoutSet.getCompanyId(), layout.getGroupId(),
598                 layout, null, portletEl, importPortletSetup,
599                 importPortletArchivedSetups, importPortletUserPreferences,
600                 false);
601 
602             // Portlet data scope
603 
604             long scopeLayoutId = GetterUtil.getLong(
605                 portletEl.attributeValue("scope-layout-id"));
606 
607             context.setScopeLayoutId(scopeLayoutId);
608 
609             // Portlet data
610 
611             Element portletDataEl = portletEl.element("portlet-data");
612 
613             if (importPortletData && portletDataEl != null) {
614                 _portletImporter.importPortletData(
615                     context, portletId, plid, portletDataEl);
616             }
617 
618             // Portlet permissions
619 
620             if (importPermissions) {
621                 _permissionImporter.importPortletPermissions(
622                     layoutCache, companyId, groupId, userId, layout, portletEl,
623                     portletId, importUserPermissions);
624             }
625 
626             // Archived setups
627 
628             _portletImporter.importPortletPreferences(
629                 context, layoutSet.getCompanyId(), groupId, null, null,
630                 portletEl, importPortletSetup, importPortletArchivedSetups,
631                 importPortletUserPreferences, false);
632         }
633 
634         // Delete missing layouts
635 
636         if (deleteMissingLayouts) {
637             deleteMissingLayouts(
638                 groupId, privateLayout, newLayoutIds, previousLayouts);
639         }
640 
641         // Page count
642 
643         LayoutSetLocalServiceUtil.updatePageCount(groupId, privateLayout);
644 
645         if (_log.isInfoEnabled()) {
646             _log.info("Importing layouts takes " + stopWatch.getTime() + " ms");
647         }
648 
649         // Web content layout type
650 
651         for (Layout layout : newLayouts) {
652             UnicodeProperties typeSettingsProperties =
653                 layout.getTypeSettingsProperties();
654 
655             String articleId = typeSettingsProperties.getProperty("article-id");
656 
657             if (Validator.isNotNull(articleId)) {
658                 Map<String, String> articleIds =
659                     (Map<String, String>)context.getNewPrimaryKeysMap(
660                         JournalArticle.class);
661 
662                 typeSettingsProperties.setProperty(
663                     "article-id",
664                     MapUtil.getString(articleIds, articleId, articleId));
665 
666                 LayoutUtil.update(layout, false);
667             }
668         }
669 
670         zipReader.close();
671     }
672 
673     protected String[] appendPortletIds(
674         String[] portletIds, String[] newPortletIds, String portletsMergeMode) {
675 
676         for (String portletId : newPortletIds) {
677             if (ArrayUtil.contains(portletIds, portletId)) {
678                 continue;
679             }
680 
681             if (portletsMergeMode.equals(
682                     PortletDataHandlerKeys.PORTLETS_MERGE_MODE_ADD_TO_BOTTOM)) {
683 
684                 portletIds = ArrayUtil.append(portletIds, portletId);
685             }
686             else {
687                 portletIds = ArrayUtil.append(
688                     new String[] {portletId}, portletIds);
689             }
690         }
691 
692         return portletIds;
693     }
694 
695     protected void deleteMissingLayouts(
696             long groupId, boolean privateLayout, Set<Long> newLayoutIds,
697             List<Layout> previousLayouts)
698         throws PortalException, SystemException {
699 
700         // Layouts
701 
702         if (_log.isDebugEnabled()) {
703             if (newLayoutIds.size() > 0) {
704                 _log.debug("Delete missing layouts");
705             }
706         }
707 
708         for (Layout layout : previousLayouts) {
709             if (!newLayoutIds.contains(layout.getLayoutId())) {
710                 try {
711                     LayoutLocalServiceUtil.deleteLayout(layout, false);
712                 }
713                 catch (NoSuchLayoutException nsle) {
714                 }
715             }
716         }
717 
718         // Layout set
719 
720         LayoutSetLocalServiceUtil.updatePageCount(groupId, privateLayout);
721     }
722 
723     protected void fixTypeSettings(Layout layout) {
724         if (!layout.isTypeURL()) {
725             return;
726         }
727 
728         UnicodeProperties typeSettings = layout.getTypeSettingsProperties();
729 
730         String url = GetterUtil.getString(typeSettings.getProperty("url"));
731 
732         String friendlyURLPrivateGroupPath =
733             PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING;
734         String friendlyURLPrivateUserPath =
735             PropsValues.LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING;
736         String friendlyURLPublicPath =
737             PropsValues.LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING;
738 
739         if (!url.startsWith(friendlyURLPrivateGroupPath) &&
740             !url.startsWith(friendlyURLPrivateUserPath) &&
741             !url.startsWith(friendlyURLPublicPath)) {
742 
743             return;
744         }
745 
746         int x = url.indexOf(StringPool.SLASH, 1);
747 
748         if (x == -1) {
749             return;
750         }
751 
752         int y = url.indexOf(StringPool.SLASH, x + 1);
753 
754         if (y == -1) {
755             return;
756         }
757 
758         String friendlyURL = url.substring(x, y);
759 
760         if (!friendlyURL.equals(LayoutExporter.SAME_GROUP_FRIENDLY_URL)) {
761             return;
762         }
763 
764         typeSettings.setProperty(
765             "url",
766             url.substring(0, x) + layout.getGroup().getFriendlyURL() +
767                 url.substring(y));
768     }
769 
770     protected void importCategories(PortletDataContext context)
771         throws SystemException {
772 
773         try {
774             String xml = context.getZipEntryAsString(
775                 context.getSourceRootPath() + "/categories-hierarchy.xml");
776 
777             if (Validator.isNull(xml)) {
778                 return;
779             }
780 
781             Document doc = SAXReaderUtil.read(xml);
782 
783             Element root = doc.getRootElement();
784 
785             List<Element> vocabularies = root.elements("vocabulary");
786 
787             for (Element vocabularyEl : vocabularies) {
788                 String vocabularyName = GetterUtil.getString(
789                     vocabularyEl.attributeValue("name"));
790                 String userUuid = GetterUtil.getString(
791                     vocabularyEl.attributeValue("userUuid"));
792 
793                 ServiceContext serviceContext = new ServiceContext();
794 
795                 serviceContext.setAddCommunityPermissions(true);
796                 serviceContext.setAddGuestPermissions(true);
797                 serviceContext.setScopeGroupId(context.getGroupId());
798 
799                 try {
800                     TagsVocabularyLocalServiceUtil.addVocabulary(
801                         context.getUserId(userUuid), vocabularyName,
802                         TagsEntryConstants.FOLKSONOMY_CATEGORY, serviceContext);
803                 }
804                 catch (DuplicateVocabularyException dve) {
805                 }
806 
807                 List<Element> categories = vocabularyEl.elements("category");
808 
809                 for (Element category : categories) {
810                     String categoryName = GetterUtil.getString(
811                         category.attributeValue("name"));
812                     String parentEntryName = GetterUtil.getString(
813                         category.attributeValue("parentEntryName"));
814                     String[] properties = null;
815 
816                     try {
817                         TagsEntryLocalServiceUtil.addEntry(
818                             context.getUserId(userUuid), parentEntryName,
819                             categoryName, vocabularyName, properties,
820                             serviceContext);
821                     }
822                     catch (DuplicateEntryException dee) {
823                     }
824                 }
825             }
826         }
827         catch (Exception e) {
828             throw new SystemException(e);
829         }
830     }
831 
832     protected String importTheme(LayoutSet layoutSet, InputStream themeZip)
833         throws IOException {
834 
835         ThemeLoader themeLoader = ThemeLoaderFactory.getDefaultThemeLoader();
836 
837         if (themeLoader == null) {
838             _log.error("No theme loaders are deployed");
839 
840             return null;
841         }
842 
843         ZipReader zipReader = ZipReaderFactoryUtil.getZipReader(themeZip);
844 
845         String lookAndFeelXML = zipReader.getEntryAsString(
846             "liferay-look-and-feel.xml");
847 
848         String themeId = String.valueOf(layoutSet.getGroupId());
849 
850         if (layoutSet.isPrivateLayout()) {
851             themeId += "-private";
852         }
853         else {
854             themeId += "-public";
855         }
856 
857         if (PropsValues.THEME_LOADER_NEW_THEME_ID_ON_IMPORT) {
858             Date now = new Date();
859 
860             themeId += "-" + Time.getShortTimestamp(now);
861         }
862 
863         String themeName = themeId;
864 
865         lookAndFeelXML = StringUtil.replace(
866             lookAndFeelXML,
867             new String[] {
868                 "[$GROUP_ID$]", "[$THEME_ID$]", "[$THEME_NAME$]"
869             },
870             new String[] {
871                 String.valueOf(layoutSet.getGroupId()), themeId, themeName
872             }
873         );
874 
875         FileUtil.deltree(
876             themeLoader.getFileStorage() + StringPool.SLASH + themeId);
877 
878         List<String> zipEntries = zipReader.getEntries();
879 
880         for (String zipEntry : zipEntries) {
881             String key = zipEntry;
882 
883             if (key.contains(StringPool.SLASH)) {
884                 key = key.substring(key.lastIndexOf(StringPool.SLASH));
885             }
886 
887             if (key.equals("liferay-look-and-feel.xml")) {
888                 FileUtil.write(
889                     themeLoader.getFileStorage() + StringPool.SLASH + themeId +
890                         StringPool.SLASH + key,
891                     lookAndFeelXML.getBytes());
892             }
893             else {
894                 InputStream is = zipReader.getEntryAsInputStream(zipEntry);
895 
896                 FileUtil.write(
897                     themeLoader.getFileStorage() + StringPool.SLASH + themeId +
898                         StringPool.SLASH + key,
899                     is);
900             }
901         }
902 
903         themeLoader.loadThemes();
904 
905         MethodWrapper methodWrapper = new MethodWrapper(
906             ThemeLoaderFactory.class.getName(), "loadThemes");
907 
908         Message message = new Message();
909         message.setPayload(methodWrapper);
910 
911         ClusterLinkUtil.sendMulticastMessage(message, Priority.LEVEL5);
912 
913         themeId +=
914             PortletConstants.WAR_SEPARATOR +
915                 themeLoader.getServletContextName();
916 
917         return PortalUtil.getJsSafePortletId(themeId);
918     }
919 
920     protected void mergePortlets(
921         Layout layout, String newTypeSettings, String portletsMergeMode) {
922 
923         try {
924             UnicodeProperties previousProps =
925                 layout.getTypeSettingsProperties();
926             LayoutTypePortlet previousLayoutType =
927                 (LayoutTypePortlet)layout.getLayoutType();
928             List<String> previousColumns =
929                 previousLayoutType.getLayoutTemplate().getColumns();
930 
931             UnicodeProperties newProps = new UnicodeProperties(true);
932 
933             newProps.load(newTypeSettings);
934 
935             String layoutTemplateId = newProps.getProperty(
936                     LayoutTypePortletImpl.LAYOUT_TEMPLATE_ID);
937 
938             LayoutTemplate newLayoutTemplate =
939                 LayoutTemplateLocalServiceUtil.getLayoutTemplate(
940                     layoutTemplateId, false, null);
941 
942             String[] lostPortletIds = new String[0];
943 
944             for (String columnId : newLayoutTemplate.getColumns()) {
945                 String columnValue =
946                     newProps.getProperty(columnId);
947 
948                 String[] portletIds = StringUtil.split(columnValue);
949 
950                 if (!previousColumns.contains(columnId)) {
951                     lostPortletIds = ArrayUtil.append(
952                         lostPortletIds, portletIds);
953                 }
954                 else {
955 
956                     String[] previousPortletIds = StringUtil.split(
957                         previousProps.getProperty(columnId));
958 
959                     portletIds = appendPortletIds(
960                         previousPortletIds, portletIds, portletsMergeMode);
961 
962                     previousProps.setProperty(
963                         columnId, StringUtil.merge(portletIds));
964                 }
965             }
966 
967             // Add portlets in non-existent column to the first column
968 
969             String columnId = previousColumns.get(0);
970 
971             String[] portletIds = StringUtil.split(
972                 previousProps.getProperty(columnId));
973 
974             appendPortletIds(portletIds, lostPortletIds, portletsMergeMode);
975 
976             previousProps.setProperty(
977                 columnId, StringUtil.merge(portletIds));
978 
979             layout.setTypeSettings(previousProps.toString());
980 
981         }
982         catch (IOException e) {
983             layout.setTypeSettings(newTypeSettings);
984         }
985     }
986 
987     private static Log _log = LogFactoryUtil.getLog(LayoutImporter.class);
988 
989     private PermissionImporter _permissionImporter = new PermissionImporter();
990     private PortletImporter _portletImporter = new PortletImporter();
991 
992 }