1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.portal.lar;
24  
25  import com.liferay.portal.PortalException;
26  import com.liferay.portal.SystemException;
27  import com.liferay.portal.kernel.io.FileCacheOutputStream;
28  import com.liferay.portal.kernel.log.Log;
29  import com.liferay.portal.kernel.log.LogFactoryUtil;
30  import com.liferay.portal.kernel.util.FileUtil;
31  import com.liferay.portal.kernel.util.MapUtil;
32  import com.liferay.portal.kernel.util.ReleaseInfo;
33  import com.liferay.portal.kernel.util.StringPool;
34  import com.liferay.portal.kernel.util.StringUtil;
35  import com.liferay.portal.kernel.util.Time;
36  import com.liferay.portal.kernel.xml.Document;
37  import com.liferay.portal.kernel.xml.Element;
38  import com.liferay.portal.kernel.xml.SAXReaderUtil;
39  import com.liferay.portal.kernel.zip.ZipWriter;
40  import com.liferay.portal.model.Group;
41  import com.liferay.portal.model.GroupConstants;
42  import com.liferay.portal.model.Image;
43  import com.liferay.portal.model.Layout;
44  import com.liferay.portal.model.LayoutConstants;
45  import com.liferay.portal.model.LayoutSet;
46  import com.liferay.portal.model.LayoutTypePortlet;
47  import com.liferay.portal.model.Portlet;
48  import com.liferay.portal.model.PortletConstants;
49  import com.liferay.portal.model.Resource;
50  import com.liferay.portal.model.ResourceConstants;
51  import com.liferay.portal.model.Theme;
52  import com.liferay.portal.service.GroupLocalServiceUtil;
53  import com.liferay.portal.service.ImageLocalServiceUtil;
54  import com.liferay.portal.service.LayoutLocalServiceUtil;
55  import com.liferay.portal.service.LayoutSetLocalServiceUtil;
56  import com.liferay.portal.service.PortletLocalServiceUtil;
57  import com.liferay.portal.service.UserLocalServiceUtil;
58  import com.liferay.portal.service.permission.PortletPermissionUtil;
59  import com.liferay.portal.service.persistence.LayoutUtil;
60  import com.liferay.portal.theme.ThemeLoader;
61  import com.liferay.portal.theme.ThemeLoaderFactory;
62  import com.liferay.portal.util.ContentUtil;
63  import com.liferay.portal.util.PortletKeys;
64  import com.liferay.portal.util.PropsValues;
65  import com.liferay.portal.velocity.VelocityContextPool;
66  
67  import java.io.File;
68  import java.io.IOException;
69  import java.io.InputStream;
70  
71  import java.util.Date;
72  import java.util.HashSet;
73  import java.util.LinkedHashMap;
74  import java.util.List;
75  import java.util.Map;
76  
77  import javax.servlet.ServletContext;
78  
79  import org.apache.commons.lang.time.StopWatch;
80  
81  /**
82   * <a href="LayoutExporter.java.html"><b><i>View Source</i></b></a>
83   *
84   * @author Brian Wing Shun Chan
85   * @author Joel Kozikowski
86   * @author Charles May
87   * @author Raymond Augé
88   * @author Jorge Ferrer
89   * @author Bruno Farache
90   *
91   */
92  public class LayoutExporter {
93  
94      public byte[] exportLayouts(
95              long groupId, boolean privateLayout, long[] layoutIds,
96              Map<String, String[]> parameterMap, Date startDate, Date endDate)
97          throws PortalException, SystemException {
98  
99          FileCacheOutputStream fcos = exportLayoutsAsStream(
100             groupId, privateLayout, layoutIds, parameterMap, startDate,
101             endDate);
102 
103         try {
104             return fcos.getBytes();
105         }
106         catch (IOException ioe) {
107             throw new SystemException(ioe);
108         }
109     }
110 
111     public FileCacheOutputStream exportLayoutsAsStream(
112             long groupId, boolean privateLayout, long[] layoutIds,
113             Map<String, String[]> parameterMap, Date startDate, Date endDate)
114         throws PortalException, SystemException {
115 
116         boolean exportPermissions = MapUtil.getBoolean(
117             parameterMap, PortletDataHandlerKeys.PERMISSIONS);
118         boolean exportUserPermissions = MapUtil.getBoolean(
119             parameterMap, PortletDataHandlerKeys.USER_PERMISSIONS);
120         boolean exportPortletArchivedSetups = MapUtil.getBoolean(
121             parameterMap, PortletDataHandlerKeys.PORTLET_ARCHIVED_SETUPS);
122         boolean exportPortletUserPreferences = MapUtil.getBoolean(
123             parameterMap, PortletDataHandlerKeys.PORTLET_USER_PREFERENCES);
124         boolean exportTheme = MapUtil.getBoolean(
125             parameterMap, PortletDataHandlerKeys.THEME);
126 
127         if (_log.isDebugEnabled()) {
128             _log.debug("Export permissions " + exportPermissions);
129             _log.debug("Export user permissions " + exportUserPermissions);
130             _log.debug(
131                 "Export portlet archived setups " +
132                     exportPortletArchivedSetups);
133             _log.debug(
134                 "Export portlet user preferences " +
135                     exportPortletUserPreferences);
136             _log.debug("Export theme " + exportTheme);
137         }
138 
139         StopWatch stopWatch = null;
140 
141         if (_log.isInfoEnabled()) {
142             stopWatch = new StopWatch();
143 
144             stopWatch.start();
145         }
146 
147         LayoutCache layoutCache = new LayoutCache();
148 
149         LayoutSet layoutSet = LayoutSetLocalServiceUtil.getLayoutSet(
150             groupId, privateLayout);
151 
152         long companyId = layoutSet.getCompanyId();
153         long defaultUserId = UserLocalServiceUtil.getDefaultUserId(companyId);
154 
155         ZipWriter zipWriter = null;
156 
157         try {
158             zipWriter = new ZipWriter();
159         }
160         catch (IOException ioe) {
161             throw new SystemException(ioe);
162         }
163 
164         PortletDataContext context = new PortletDataContextImpl(
165             companyId, groupId, parameterMap, new HashSet<String>(), startDate,
166             endDate, zipWriter);
167 
168         Group guestGroup = GroupLocalServiceUtil.getGroup(
169             companyId, GroupConstants.GUEST);
170 
171         // Build compatibility
172 
173         Document doc = SAXReaderUtil.createDocument();
174 
175         Element root = doc.addElement("root");
176 
177         Element header = root.addElement("header");
178 
179         header.addAttribute(
180             "build-number", String.valueOf(ReleaseInfo.getBuildNumber()));
181         header.addAttribute("export-date", Time.getRFC822());
182 
183         if (context.hasDateRange()) {
184             header.addAttribute(
185                 "start-date", String.valueOf(context.getStartDate()));
186             header.addAttribute(
187                 "end-date", String.valueOf(context.getEndDate()));
188         }
189 
190         header.addAttribute("type", "layout-set");
191         header.addAttribute("group-id", String.valueOf(groupId));
192         header.addAttribute("private-layout", String.valueOf(privateLayout));
193         header.addAttribute("theme-id", layoutSet.getThemeId());
194         header.addAttribute("color-scheme-id", layoutSet.getColorSchemeId());
195 
196         // Layout Configuration Portlet
197 
198         Portlet layoutConfigurationPortlet =
199             PortletLocalServiceUtil.getPortletById(
200                 context.getCompanyId(), PortletKeys.LAYOUT_CONFIGURATION);
201 
202         // Layouts
203 
204         Map<String, Object[]> portletIds =
205             new LinkedHashMap<String, Object[]>();
206 
207         List<Layout> layouts = null;
208 
209         if ((layoutIds == null) || (layoutIds.length == 0)) {
210             layouts = LayoutLocalServiceUtil.getLayouts(groupId, privateLayout);
211         }
212         else {
213             layouts = LayoutLocalServiceUtil.getLayouts(
214                 groupId, privateLayout, layoutIds);
215         }
216 
217         Element layoutsEl = root.addElement("layouts");
218 
219         for (Layout layout : layouts) {
220             context.setPlid(layout.getPlid());
221 
222             Document layoutDoc = SAXReaderUtil.createDocument();
223 
224             Element layoutEl = layoutDoc.addElement("layout");
225 
226             layoutEl.addAttribute("old-plid", String.valueOf(layout.getPlid()));
227             layoutEl.addAttribute(
228                 "layout-id", String.valueOf(layout.getLayoutId()));
229             layoutEl.addElement("parent-layout-id").addText(
230                 String.valueOf(layout.getParentLayoutId()));
231             layoutEl.addElement("name").addCDATA(layout.getName());
232             layoutEl.addElement("title").addCDATA(layout.getTitle());
233             layoutEl.addElement("description").addText(layout.getDescription());
234             layoutEl.addElement("type").addText(layout.getType());
235             layoutEl.addElement("type-settings").addCDATA(
236                 layout.getTypeSettings());
237             layoutEl.addElement("hidden").addText(
238                 String.valueOf(layout.getHidden()));
239             layoutEl.addElement("friendly-url").addText(
240                 layout.getFriendlyURL());
241             layoutEl.addElement("icon-image").addText(
242                 String.valueOf(layout.getIconImage()));
243 
244             if (layout.isIconImage()) {
245                 Image image = ImageLocalServiceUtil.getImage(
246                     layout.getIconImageId());
247 
248                 if (image != null) {
249                     String iconPath = getLayoutIconPath(context, layout, image);
250 
251                     layoutEl.addElement("icon-image-path").addText(
252                         iconPath);
253 
254                     context.addZipEntry(iconPath, image.getTextObj());
255                 }
256             }
257 
258             layoutEl.addElement("theme-id").addText(layout.getThemeId());
259             layoutEl.addElement("color-scheme-id").addText(
260                 layout.getColorSchemeId());
261             layoutEl.addElement("wap-theme-id").addText(layout.getWapThemeId());
262             layoutEl.addElement("wap-color-scheme-id").addText(
263                 layout.getWapColorSchemeId());
264             layoutEl.addElement("css").addCDATA(layout.getCss());
265             layoutEl.addElement("priority").addText(
266                 String.valueOf(layout.getPriority()));
267 
268             // Layout permissions
269 
270             if (exportPermissions) {
271                 Element permissionsEl = layoutEl.addElement("permissions");
272 
273                 String resourceName = Layout.class.getName();
274                 String resourcePrimKey = String.valueOf(layout.getPlid());
275 
276                 if (PropsValues.PERMISSIONS_USER_CHECK_ALGORITHM == 5) {
277                     exportLayoutPermissions_5(
278                         layoutCache, companyId, groupId, resourceName,
279                         resourcePrimKey, permissionsEl);
280                 }
281                 else if (PropsValues.PERMISSIONS_USER_CHECK_ALGORITHM == 6) {
282                     exportLayoutPermissions_6(
283                         layoutCache, companyId, groupId, resourceName,
284                         resourcePrimKey, permissionsEl);
285                 }
286                 else {
287                     exportLayoutPermissions_4(
288                         layoutCache, companyId, groupId, guestGroup,
289                         resourceName, resourcePrimKey, permissionsEl,
290                         exportUserPermissions);
291                 }
292             }
293 
294             if (layout.getType().equals(LayoutConstants.TYPE_PORTLET)) {
295                 LayoutTypePortlet layoutTypePortlet =
296                     (LayoutTypePortlet)layout.getLayoutType();
297 
298                 for (String portletId : layoutTypePortlet.getPortletIds()) {
299                     String key = PortletPermissionUtil.getPrimaryKey(
300                         layout.getPlid(), portletId);
301 
302                     portletIds.put(
303                         key, new Object[] {portletId, layout.getPlid()});
304                 }
305             }
306 
307             String layoutPath = context.getLayoutPath(layout.getLayoutId()) +
308                 "/layout.xml";
309 
310             Element el = layoutsEl.addElement("layout");
311 
312             el.addAttribute("layout-id", String.valueOf(layout.getLayoutId()));
313             el.addAttribute("path", layoutPath);
314 
315             _portletExporter.exportPortletData(
316                 context, layoutConfigurationPortlet, layout, null, layoutEl);
317 
318             try {
319                 context.addZipEntry(layoutPath, layoutDoc.formattedString());
320             }
321             catch (IOException ioe) {
322             }
323         }
324 
325         if (PropsValues.PERMISSIONS_USER_CHECK_ALGORITHM < 5) {
326             Element rolesEl = root.addElement("roles");
327 
328             // Layout roles
329 
330             if (exportPermissions) {
331                 exportLayoutRoles(layoutCache, companyId, groupId, rolesEl);
332             }
333         }
334 
335         // Export Portlets
336 
337         Element portletsEl = root.addElement("portlets");
338 
339         for (Map.Entry<String, Object[]> portletIdsEntry :
340                 portletIds.entrySet()) {
341 
342             String portletId = (String)portletIdsEntry.getValue()[0];
343             long plid = (Long)portletIdsEntry.getValue()[1];
344 
345             Layout layout = LayoutUtil.findByPrimaryKey(plid);
346 
347             context.setPlid(layout.getPlid());
348             context.setOldPlid(layout.getPlid());
349 
350             boolean[] exportPortletControls = getExportPortletControls(
351                 context.getCompanyId(), portletId, context, parameterMap);
352 
353             _portletExporter.exportPortlet(
354                 context, layoutCache, portletId, layout, portletsEl,
355                 defaultUserId, exportPermissions, exportPortletArchivedSetups,
356                 exportPortletControls[0], exportPortletControls[1],
357                 exportPortletUserPreferences, exportUserPermissions);
358         }
359 
360         // Comments
361 
362         _portletExporter.exportComments(context, root);
363 
364         // Ratings
365 
366         _portletExporter.exportRatings(context, root);
367 
368         // Tags
369 
370         _portletExporter.exportTags(context, root);
371 
372         // Look and feel
373 
374         InputStream themeZip = null;
375 
376         try {
377             if (exportTheme) {
378                 themeZip = exportTheme(layoutSet).getFileInputStream();
379             }
380         }
381         catch (IOException ioe) {
382             throw new SystemException(ioe);
383         }
384 
385         // Log
386 
387         if (_log.isInfoEnabled()) {
388             _log.info("Exporting layouts takes " + stopWatch.getTime() + " ms");
389         }
390 
391         // Zip
392 
393         try {
394             context.addZipEntry("/manifest.xml", doc.formattedString());
395 
396             if (themeZip != null) {
397                 context.addZipEntry("/theme.zip", themeZip);
398             }
399 
400             return zipWriter.finishWithStream();
401         }
402         catch (IOException ioe) {
403             throw new SystemException(ioe);
404         }
405     }
406 
407     protected void exportLayoutPermissions_4(
408             LayoutCache layoutCache, long companyId, long groupId,
409             Group guestGroup, String resourceName, String resourcePrimKey,
410             Element permissionsEl, boolean exportUserPermissions)
411         throws SystemException {
412 
413         _portletExporter.exportGroupPermissions(
414             companyId, groupId, resourceName, resourcePrimKey, permissionsEl,
415             "community-actions");
416 
417         if (groupId != guestGroup.getGroupId()) {
418             _portletExporter.exportGroupPermissions(
419                 companyId, guestGroup.getGroupId(), resourceName,
420                 resourcePrimKey, permissionsEl, "guest-actions");
421         }
422 
423         if (exportUserPermissions) {
424             _portletExporter.exportUserPermissions(
425                 layoutCache, companyId, groupId, resourceName, resourcePrimKey,
426                 permissionsEl);
427         }
428 
429         _portletExporter.exportInheritedPermissions(
430             layoutCache, companyId, resourceName, resourcePrimKey,
431             permissionsEl, "organization");
432 
433         _portletExporter.exportInheritedPermissions(
434             layoutCache, companyId, resourceName, resourcePrimKey,
435             permissionsEl, "location");
436 
437         _portletExporter.exportInheritedPermissions(
438             layoutCache, companyId, resourceName, resourcePrimKey,
439             permissionsEl, "user-group");
440     }
441 
442     protected void exportLayoutPermissions_5(
443             LayoutCache layoutCache, long companyId, long groupId,
444             String resourceName, String resourcePrimKey, Element permissionsEl)
445         throws PortalException, SystemException {
446 
447         boolean portletActions = false;
448 
449         Resource resource = layoutCache.getResource(
450             companyId, groupId, resourceName,
451             ResourceConstants.SCOPE_INDIVIDUAL, resourcePrimKey,
452             portletActions);
453 
454         _portletExporter.exportPermissions_5(
455             layoutCache, groupId, resourceName, resource.getResourceId(),
456             permissionsEl);
457     }
458 
459     protected void exportLayoutPermissions_6(
460             LayoutCache layoutCache, long companyId, long groupId,
461             String resourceName, String resourcePrimKey, Element permissionsEl)
462         throws PortalException, SystemException {
463 
464         boolean portletActions = false;
465 
466         _portletExporter.exportPermissions_6(
467             layoutCache, companyId, groupId, resourceName, resourcePrimKey,
468             permissionsEl, portletActions);
469     }
470 
471     protected void exportLayoutRoles(
472             LayoutCache layoutCache, long companyId, long groupId,
473             Element rolesEl)
474         throws SystemException {
475 
476         String resourceName = Layout.class.getName();
477 
478         _portletExporter.exportGroupRoles(
479             layoutCache, companyId, groupId, resourceName, "community",
480             rolesEl);
481 
482         _portletExporter.exportUserRoles(
483         layoutCache, companyId, groupId, resourceName, rolesEl);
484 
485         _portletExporter.exportInheritedRoles(
486             layoutCache, companyId, groupId, resourceName, "organization",
487             rolesEl);
488 
489         _portletExporter.exportInheritedRoles(
490             layoutCache, companyId, groupId, resourceName, "location", rolesEl);
491 
492         _portletExporter.exportInheritedRoles(
493             layoutCache, companyId, groupId, resourceName, "user-group",
494             rolesEl);
495     }
496 
497     protected FileCacheOutputStream exportTheme(LayoutSet layoutSet)
498         throws IOException {
499 
500         Theme theme = layoutSet.getTheme();
501 
502         ZipWriter zipWriter = new ZipWriter();
503 
504         String lookAndFeelXML = ContentUtil.get(
505             "com/liferay/portal/dependencies/liferay-look-and-feel.xml.tmpl");
506 
507         lookAndFeelXML = StringUtil.replace(
508             lookAndFeelXML,
509             new String[] {
510                 "[$TEMPLATE_EXTENSION$]", "[$VIRTUAL_PATH$]"
511             },
512             new String[] {
513                 theme.getTemplateExtension(), theme.getVirtualPath()
514             }
515         );
516 
517         zipWriter.addEntry("liferay-look-and-feel.xml", lookAndFeelXML);
518 
519         String servletContextName = theme.getServletContextName();
520 
521         ServletContext servletContext = VelocityContextPool.get(
522             servletContextName);
523 
524         if (servletContext == null) {
525             if (_log.isWarnEnabled()) {
526                 _log.warn(
527                     "Servlet context not found for theme " +
528                         theme.getThemeId());
529             }
530 
531             return null;
532         }
533 
534         File cssPath = null;
535         File imagesPath = null;
536         File javaScriptPath = null;
537         File templatesPath = null;
538 
539         if (!theme.isLoadFromServletContext()) {
540             ThemeLoader themeLoader = ThemeLoaderFactory.getThemeLoader(
541                 servletContextName);
542 
543             if (themeLoader == null) {
544                 _log.error(
545                     servletContextName + " does not map to a theme loader");
546             }
547             else {
548                 String realPath =
549                     themeLoader.getFileStorage().getPath() + "/" +
550                         theme.getName();
551 
552                 cssPath = new File(realPath + "/css");
553                 imagesPath = new File(realPath + "/images");
554                 javaScriptPath = new File(realPath + "/javascript");
555                 templatesPath = new File(realPath + "/templates");
556             }
557         }
558         else {
559             cssPath = new File(servletContext.getRealPath(theme.getCssPath()));
560             imagesPath = new File(
561                 servletContext.getRealPath(theme.getImagesPath()));
562             javaScriptPath = new File(
563                 servletContext.getRealPath(theme.getJavaScriptPath()));
564             templatesPath = new File(
565                 servletContext.getRealPath(theme.getTemplatesPath()));
566         }
567 
568         exportThemeFiles("css", cssPath, zipWriter);
569         exportThemeFiles("images", imagesPath, zipWriter);
570         exportThemeFiles("javascript", javaScriptPath, zipWriter);
571         exportThemeFiles("templates", templatesPath, zipWriter);
572 
573         return zipWriter.finishWithStream();
574     }
575 
576     protected void exportThemeFiles(String path, File dir, ZipWriter zipWriter)
577         throws IOException {
578 
579         if ((dir == null) || (!dir.exists())) {
580             return;
581         }
582 
583         File[] files = dir.listFiles();
584 
585         for (int i = 0; i < files.length; i++) {
586             File file = files[i];
587 
588             if (file.isDirectory()) {
589                 exportThemeFiles(path + "/" + file.getName(), file, zipWriter);
590             }
591             else {
592                 zipWriter.addEntry(
593                     path + "/" + file.getName(), FileUtil.getBytes(file));
594             }
595         }
596     }
597 
598     protected boolean[] getExportPortletControls(
599             long companyId, String portletId, PortletDataContext context,
600             Map<String, String[]> parameterMap)
601         throws SystemException {
602 
603         boolean exportPortletData = MapUtil.getBoolean(
604             parameterMap, PortletDataHandlerKeys.PORTLET_DATA);
605         boolean exportPortletDataAll = MapUtil.getBoolean(
606             parameterMap, PortletDataHandlerKeys.PORTLET_DATA_ALL);
607         boolean exportPortletSetup = MapUtil.getBoolean(
608             parameterMap, PortletDataHandlerKeys.PORTLET_SETUP);
609 
610         if (_log.isDebugEnabled()) {
611             _log.debug("Export portlet data " + exportPortletData);
612             _log.debug("Export all portlet data " + exportPortletDataAll);
613             _log.debug("Export portlet setup " + exportPortletSetup);
614         }
615 
616         boolean exportCurPortletData = exportPortletData;
617         boolean exportCurPortletSetup = exportPortletSetup;
618 
619         if (exportPortletDataAll) {
620             exportCurPortletData = true;
621             exportCurPortletSetup = true;
622         }
623         else {
624             Portlet portlet = PortletLocalServiceUtil.getPortletById(
625                 companyId, portletId);
626 
627             if (portlet != null) {
628                 String portletDataHandlerClass =
629                     portlet.getPortletDataHandlerClass();
630 
631                 if (portletDataHandlerClass != null) {
632                     String rootPortletId = PortletConstants.getRootPortletId(
633                         portletId);
634 
635                     exportCurPortletData =
636                         exportPortletData &&
637                         MapUtil.getBoolean(
638                             parameterMap,
639                             PortletDataHandlerKeys.PORTLET_DATA +
640                                 StringPool.UNDERLINE + rootPortletId);
641 
642                     exportCurPortletSetup =
643                         exportPortletData &&
644                         MapUtil.getBoolean(
645                             parameterMap,
646                             PortletDataHandlerKeys.PORTLET_SETUP +
647                                 StringPool.UNDERLINE + rootPortletId);
648                 }
649             }
650         }
651 
652         return new boolean[] {exportCurPortletData, exportCurPortletSetup};
653     }
654 
655     protected String getLayoutIconPath(
656         PortletDataContext context, Layout layout, Image image) {
657 
658         StringBuilder sb = new StringBuilder();
659 
660         sb.append(context.getLayoutPath(layout.getLayoutId()));
661         sb.append("/icons/");
662         sb.append(image.getImageId());
663         sb.append(StringPool.PERIOD);
664         sb.append(image.getType());
665 
666         return sb.toString();
667     }
668 
669     private static Log _log = LogFactoryUtil.getLog(LayoutExporter.class);
670 
671     private PortletExporter _portletExporter = new PortletExporter();
672 
673 }