001
014
015 package com.liferay.portal.service.impl;
016
017 import com.liferay.portal.kernel.exception.SystemException;
018 import com.liferay.portal.kernel.image.SpriteProcessor;
019 import com.liferay.portal.kernel.image.SpriteProcessorUtil;
020 import com.liferay.portal.kernel.log.Log;
021 import com.liferay.portal.kernel.log.LogFactoryUtil;
022 import com.liferay.portal.kernel.plugin.PluginPackage;
023 import com.liferay.portal.kernel.plugin.Version;
024 import com.liferay.portal.kernel.servlet.ServletContextUtil;
025 import com.liferay.portal.kernel.util.ColorSchemeFactoryUtil;
026 import com.liferay.portal.kernel.util.ContextPathUtil;
027 import com.liferay.portal.kernel.util.GetterUtil;
028 import com.liferay.portal.kernel.util.ListUtil;
029 import com.liferay.portal.kernel.util.ReleaseInfo;
030 import com.liferay.portal.kernel.util.StringPool;
031 import com.liferay.portal.kernel.util.StringUtil;
032 import com.liferay.portal.kernel.util.ThemeFactoryUtil;
033 import com.liferay.portal.kernel.util.UniqueList;
034 import com.liferay.portal.kernel.util.Validator;
035 import com.liferay.portal.kernel.xml.Document;
036 import com.liferay.portal.kernel.xml.Element;
037 import com.liferay.portal.kernel.xml.UnsecureSAXReaderUtil;
038 import com.liferay.portal.model.ColorScheme;
039 import com.liferay.portal.model.PluginSetting;
040 import com.liferay.portal.model.PortletConstants;
041 import com.liferay.portal.model.Theme;
042 import com.liferay.portal.plugin.PluginUtil;
043 import com.liferay.portal.service.base.ThemeLocalServiceBaseImpl;
044 import com.liferay.portal.theme.ThemeCompanyId;
045 import com.liferay.portal.theme.ThemeCompanyLimit;
046 import com.liferay.portal.theme.ThemeGroupId;
047 import com.liferay.portal.theme.ThemeGroupLimit;
048 import com.liferay.portal.util.PortalUtil;
049 import com.liferay.portal.util.PropsValues;
050 import com.liferay.util.ContextReplace;
051
052 import java.net.URL;
053
054 import java.util.ArrayList;
055 import java.util.HashSet;
056 import java.util.Iterator;
057 import java.util.List;
058 import java.util.Map;
059 import java.util.Properties;
060 import java.util.Set;
061 import java.util.concurrent.ConcurrentHashMap;
062
063 import javax.servlet.ServletContext;
064
065
070 public class ThemeLocalServiceImpl extends ThemeLocalServiceBaseImpl {
071
072 @Override
073 public ColorScheme fetchColorScheme(
074 long companyId, String themeId, String colorSchemeId) {
075
076 colorSchemeId = GetterUtil.getString(colorSchemeId);
077
078 Theme theme = fetchTheme(companyId, themeId);
079
080 if (theme == null) {
081 return null;
082 }
083
084 Map<String, ColorScheme> colorSchemesMap = theme.getColorSchemesMap();
085
086 return colorSchemesMap.get(colorSchemeId);
087 }
088
089 @Override
090 public Theme fetchTheme(long companyId, String themeId) {
091 themeId = GetterUtil.getString(themeId);
092
093 Map<String, Theme> themes = _getThemes(companyId);
094
095 return themes.get(themeId);
096 }
097
098 @Override
099 public ColorScheme getColorScheme(
100 long companyId, String themeId, String colorSchemeId,
101 boolean wapTheme)
102 throws SystemException {
103
104 colorSchemeId = GetterUtil.getString(colorSchemeId);
105
106 Theme theme = getTheme(companyId, themeId, wapTheme);
107
108 Map<String, ColorScheme> colorSchemesMap = theme.getColorSchemesMap();
109
110 ColorScheme colorScheme = colorSchemesMap.get(colorSchemeId);
111
112 if (colorScheme != null) {
113 return colorScheme;
114 }
115
116 List<ColorScheme> colorSchemes = theme.getColorSchemes();
117
118 if (!colorSchemes.isEmpty()) {
119 for (int i = (colorSchemes.size() - 1); i >= 0; i--) {
120 colorScheme = colorSchemes.get(i);
121
122 if (colorScheme.isDefaultCs()) {
123 return colorScheme;
124 }
125 }
126 }
127
128 if (colorScheme == null) {
129 if (wapTheme) {
130 colorScheme = ColorSchemeFactoryUtil.getDefaultWapColorScheme();
131 }
132 else {
133 colorScheme =
134 ColorSchemeFactoryUtil.getDefaultRegularColorScheme();
135 }
136 }
137
138 return colorScheme;
139 }
140
141 @Override
142 public Theme getTheme(long companyId, String themeId, boolean wapTheme)
143 throws SystemException {
144
145 themeId = GetterUtil.getString(themeId);
146
147 Map<String, Theme> themes = _getThemes(companyId);
148
149 Theme theme = themes.get(themeId);
150
151 if (theme != null) {
152 return theme;
153 }
154
155 if (_log.isWarnEnabled()) {
156 _log.warn(
157 "No theme found for specified theme id " + themeId +
158 ". Returning the default theme.");
159 }
160
161 if (wapTheme) {
162 themeId = ThemeFactoryUtil.getDefaultWapThemeId(companyId);
163 }
164 else {
165 themeId = ThemeFactoryUtil.getDefaultRegularThemeId(companyId);
166 }
167
168 theme = _themes.get(themeId);
169
170 if (theme != null) {
171 return theme;
172 }
173
174 if (_themes.isEmpty()) {
175 if (_log.isDebugEnabled()) {
176 _log.debug("No themes are installed");
177 }
178
179 return null;
180 }
181
182 if (!themeId.contains(PortletConstants.WAR_SEPARATOR)) {
183 _log.error(
184 "No theme found for default theme id " + themeId +
185 ". Returning a random theme.");
186 }
187
188 for (Map.Entry<String, Theme> entry : _themes.entrySet()) {
189 theme = entry.getValue();
190
191 if ((theme != null) && (theme.isWapTheme() == wapTheme)) {
192 return theme;
193 }
194 }
195
196 return null;
197 }
198
199 @Override
200 public List<Theme> getThemes(long companyId) {
201 Map<String, Theme> themes = _getThemes(companyId);
202
203 List<Theme> themesList = ListUtil.fromMapValues(themes);
204
205 return ListUtil.sort(themesList);
206 }
207
208 @Override
209 public List<Theme> getThemes(
210 long companyId, long groupId, long userId, boolean wapTheme)
211 throws SystemException {
212
213 List<Theme> themes = getThemes(companyId);
214
215 themes = PluginUtil.restrictPlugins(themes, companyId, userId);
216
217 Iterator<Theme> itr = themes.iterator();
218
219 while (itr.hasNext()) {
220 Theme theme = itr.next();
221
222 if (theme.getThemeId().equals("controlpanel") ||
223 !theme.isGroupAvailable(groupId) ||
224 (theme.isWapTheme() != wapTheme)) {
225
226 itr.remove();
227 }
228 }
229
230 return themes;
231 }
232
233 @Override
234 public List<Theme> getWARThemes() {
235 List<Theme> themes = ListUtil.fromMapValues(_themes);
236
237 Iterator<Theme> itr = themes.iterator();
238
239 while (itr.hasNext()) {
240 Theme theme = itr.next();
241
242 if (!theme.isWARFile()) {
243 itr.remove();
244 }
245 }
246
247 return themes;
248 }
249
250 @Override
251 public List<Theme> init(
252 ServletContext servletContext, String themesPath,
253 boolean loadFromServletContext, String[] xmls,
254 PluginPackage pluginPackage) {
255
256 return init(
257 null, servletContext, themesPath, loadFromServletContext, xmls,
258 pluginPackage);
259 }
260
261 @Override
262 public List<Theme> init(
263 String servletContextName, ServletContext servletContext,
264 String themesPath, boolean loadFromServletContext, String[] xmls,
265 PluginPackage pluginPackage) {
266
267 List<Theme> themes = new UniqueList<Theme>();
268
269 try {
270 for (String xml : xmls) {
271 themes.addAll(
272 _readThemes(
273 servletContextName, servletContext, themesPath,
274 loadFromServletContext, xml, pluginPackage));
275 }
276 }
277 catch (Exception e) {
278 _log.error(e, e);
279 }
280
281 _themesPool.clear();
282
283 return themes;
284 }
285
286 @Override
287 public void uninstallThemes(List<Theme> themes) {
288 for (Theme theme : themes) {
289 String themeId = theme.getThemeId();
290
291 _themes.remove(themeId);
292
293 layoutTemplateLocalService.uninstallLayoutTemplates(themeId);
294 }
295
296 _themesPool.clear();
297 }
298
299 private List<ThemeCompanyId> _getCompanyLimitExcludes(Element element) {
300 List<ThemeCompanyId> includes = new ArrayList<ThemeCompanyId>();
301
302 if (element == null) {
303 return includes;
304 }
305
306 List<Element> companyIdsElements = element.elements("company-id");
307
308 for (int i = 0; i < companyIdsElements.size(); i++) {
309 Element companyIdElement = companyIdsElements.get(i);
310
311 String name = companyIdElement.attributeValue("name");
312 String pattern = companyIdElement.attributeValue("pattern");
313
314 ThemeCompanyId themeCompanyId = null;
315
316 if (Validator.isNotNull(name)) {
317 themeCompanyId = new ThemeCompanyId(name, false);
318 }
319 else if (Validator.isNotNull(pattern)) {
320 themeCompanyId = new ThemeCompanyId(pattern, true);
321 }
322
323 if (themeCompanyId != null) {
324 includes.add(themeCompanyId);
325 }
326 }
327
328 return includes;
329 }
330
331 private List<ThemeCompanyId> _getCompanyLimitIncludes(Element element) {
332 return _getCompanyLimitExcludes(element);
333 }
334
335 private List<ThemeGroupId> _getGroupLimitExcludes(Element element) {
336 List<ThemeGroupId> includes = new ArrayList<ThemeGroupId>();
337
338 if (element == null) {
339 return includes;
340 }
341
342 List<Element> groupIdsElements = element.elements("group-id");
343
344 for (int i = 0; i < groupIdsElements.size(); i++) {
345 Element groupIdElement = groupIdsElements.get(i);
346
347 String name = groupIdElement.attributeValue("name");
348 String pattern = groupIdElement.attributeValue("pattern");
349
350 ThemeGroupId themeGroupId = null;
351
352 if (Validator.isNotNull(name)) {
353 themeGroupId = new ThemeGroupId(name, false);
354 }
355 else if (Validator.isNotNull(pattern)) {
356 themeGroupId = new ThemeGroupId(pattern, true);
357 }
358
359 if (themeGroupId != null) {
360 includes.add(themeGroupId);
361 }
362 }
363
364 return includes;
365 }
366
367 private List<ThemeGroupId> _getGroupLimitIncludes(Element element) {
368 return _getGroupLimitExcludes(element);
369 }
370
371 private Map<String, Theme> _getThemes(long companyId) {
372 Map<String, Theme> themes = _themesPool.get(companyId);
373
374 if (themes != null) {
375 return themes;
376 }
377
378 themes = new ConcurrentHashMap<String, Theme>();
379
380 for (Map.Entry<String, Theme> entry : _themes.entrySet()) {
381 String themeId = entry.getKey();
382 Theme theme = entry.getValue();
383
384 if (theme.isCompanyAvailable(companyId)) {
385 themes.put(themeId, theme);
386 }
387 }
388
389 _themesPool.put(companyId, themes);
390
391 return themes;
392 }
393
394 private Version _getVersion(String version) {
395 if (version.equals("${current-version}")) {
396 version = ReleaseInfo.getVersion();
397 }
398
399 return Version.getInstance(version);
400 }
401
402 private void _readColorSchemes(
403 Element themeElement, Map<String, ColorScheme> colorSchemes,
404 ContextReplace themeContextReplace) {
405
406 List<Element> colorSchemeElements = themeElement.elements(
407 "color-scheme");
408
409 for (Element colorSchemeElement : colorSchemeElements) {
410 ContextReplace colorSchemeContextReplace =
411 (ContextReplace)themeContextReplace.clone();
412
413 String id = colorSchemeElement.attributeValue("id");
414
415 colorSchemeContextReplace.addValue("color-scheme-id", id);
416
417 ColorScheme colorSchemeModel = colorSchemes.get(id);
418
419 if (colorSchemeModel == null) {
420 colorSchemeModel = ColorSchemeFactoryUtil.getColorScheme(id);
421 }
422
423 String name = GetterUtil.getString(
424 colorSchemeElement.attributeValue("name"),
425 colorSchemeModel.getName());
426
427 name = colorSchemeContextReplace.replace(name);
428
429 boolean defaultCs = GetterUtil.getBoolean(
430 colorSchemeElement.elementText("default-cs"),
431 colorSchemeModel.isDefaultCs());
432
433 String cssClass = GetterUtil.getString(
434 colorSchemeElement.elementText("css-class"),
435 colorSchemeModel.getCssClass());
436
437 cssClass = colorSchemeContextReplace.replace(cssClass);
438
439 colorSchemeContextReplace.addValue("css-class", cssClass);
440
441 String colorSchemeImagesPath = GetterUtil.getString(
442 colorSchemeElement.elementText("color-scheme-images-path"),
443 colorSchemeModel.getColorSchemeImagesPath());
444
445 colorSchemeImagesPath = colorSchemeContextReplace.replace(
446 colorSchemeImagesPath);
447
448 colorSchemeContextReplace.addValue(
449 "color-scheme-images-path", colorSchemeImagesPath);
450
451 colorSchemeModel.setName(name);
452 colorSchemeModel.setDefaultCs(defaultCs);
453 colorSchemeModel.setCssClass(cssClass);
454 colorSchemeModel.setColorSchemeImagesPath(colorSchemeImagesPath);
455
456 colorSchemes.put(id, colorSchemeModel);
457 }
458 }
459
460 private Set<Theme> _readThemes(
461 String servletContextName, ServletContext servletContext,
462 String themesPath, boolean loadFromServletContext, String xml,
463 PluginPackage pluginPackage)
464 throws Exception {
465
466 Set<Theme> themes = new HashSet<Theme>();
467
468 if (xml == null) {
469 return themes;
470 }
471
472 Document document = UnsecureSAXReaderUtil.read(xml, true);
473
474 Element rootElement = document.getRootElement();
475
476 Version portalVersion = _getVersion(ReleaseInfo.getVersion());
477
478 boolean compatible = false;
479
480 Element compatibilityElement = rootElement.element("compatibility");
481
482 if (compatibilityElement != null) {
483 List<Element> versionElements = compatibilityElement.elements(
484 "version");
485
486 for (Element versionElement : versionElements) {
487 Version version = _getVersion(versionElement.getTextTrim());
488
489 if (version.includes(portalVersion)) {
490 compatible = true;
491
492 break;
493 }
494 }
495 }
496
497 if (!compatible) {
498 _log.error(
499 "Themes in this WAR are not compatible with " +
500 ReleaseInfo.getServerInfo());
501
502 return themes;
503 }
504
505 ThemeCompanyLimit companyLimit = null;
506
507 Element companyLimitElement = rootElement.element("company-limit");
508
509 if (companyLimitElement != null) {
510 companyLimit = new ThemeCompanyLimit();
511
512 Element companyIncludesElement = companyLimitElement.element(
513 "company-includes");
514
515 if (companyIncludesElement != null) {
516 companyLimit.setIncludes(
517 _getCompanyLimitIncludes(companyIncludesElement));
518 }
519
520 Element companyExcludesElement = companyLimitElement.element(
521 "company-excludes");
522
523 if (companyExcludesElement != null) {
524 companyLimit.setExcludes(
525 _getCompanyLimitExcludes(companyExcludesElement));
526 }
527 }
528
529 ThemeGroupLimit groupLimit = null;
530
531 Element groupLimitElement = rootElement.element("group-limit");
532
533 if (groupLimitElement != null) {
534 groupLimit = new ThemeGroupLimit();
535
536 Element groupIncludesElement = groupLimitElement.element(
537 "group-includes");
538
539 if (groupIncludesElement != null) {
540 groupLimit.setIncludes(
541 _getGroupLimitIncludes(groupIncludesElement));
542 }
543
544 Element groupExcludesElement = groupLimitElement.element(
545 "group-excludes");
546
547 if (groupExcludesElement != null) {
548 groupLimit.setExcludes(
549 _getGroupLimitExcludes(groupExcludesElement));
550 }
551 }
552
553 long timestamp = ServletContextUtil.getLastModified(servletContext);
554
555 List<Element> themeElements = rootElement.elements("theme");
556
557 for (Element themeElement : themeElements) {
558 ContextReplace themeContextReplace = new ContextReplace();
559
560 themeContextReplace.addValue("themes-path", themesPath);
561
562 String themeId = themeElement.attributeValue("id");
563
564 if (servletContextName != null) {
565 themeId =
566 themeId + PortletConstants.WAR_SEPARATOR +
567 servletContextName;
568 }
569
570 themeId = PortalUtil.getJsSafePortletId(themeId);
571
572 themeContextReplace.addValue("theme-id", themeId);
573
574 Theme theme = _themes.get(themeId);
575
576 if (theme == null) {
577 theme = ThemeFactoryUtil.getTheme(themeId);
578 }
579
580 theme.setTimestamp(timestamp);
581
582 PluginSetting pluginSetting =
583 pluginSettingLocalService.getDefaultPluginSetting();
584
585 theme.setPluginPackage(pluginPackage);
586 theme.setDefaultPluginSetting(pluginSetting);
587
588 theme.setThemeCompanyLimit(companyLimit);
589 theme.setThemeGroupLimit(groupLimit);
590
591 if (servletContextName != null) {
592 theme.setServletContextName(servletContextName);
593 }
594
595 theme.setLoadFromServletContext(loadFromServletContext);
596
597 String name = GetterUtil.getString(
598 themeElement.attributeValue("name"), theme.getName());
599
600 String rootPath = GetterUtil.getString(
601 themeElement.elementText("root-path"), theme.getRootPath());
602
603 rootPath = themeContextReplace.replace(rootPath);
604
605 themeContextReplace.addValue("root-path", rootPath);
606
607 String templatesPath = GetterUtil.getString(
608 themeElement.elementText("templates-path"),
609 theme.getTemplatesPath());
610
611 templatesPath = themeContextReplace.replace(templatesPath);
612 templatesPath = StringUtil.safePath(templatesPath);
613
614 themeContextReplace.addValue("templates-path", templatesPath);
615
616 String cssPath = GetterUtil.getString(
617 themeElement.elementText("css-path"), theme.getCssPath());
618
619 cssPath = themeContextReplace.replace(cssPath);
620 cssPath = StringUtil.safePath(cssPath);
621
622 themeContextReplace.addValue("css-path", cssPath);
623
624 String imagesPath = GetterUtil.getString(
625 themeElement.elementText("images-path"), theme.getImagesPath());
626
627 imagesPath = themeContextReplace.replace(imagesPath);
628 imagesPath = StringUtil.safePath(imagesPath);
629
630 themeContextReplace.addValue("images-path", imagesPath);
631
632 String javaScriptPath = GetterUtil.getString(
633 themeElement.elementText("javascript-path"),
634 theme.getJavaScriptPath());
635
636 javaScriptPath = themeContextReplace.replace(javaScriptPath);
637 javaScriptPath = StringUtil.safePath(javaScriptPath);
638
639 themeContextReplace.addValue("javascript-path", javaScriptPath);
640
641 String virtualPath = GetterUtil.getString(
642 themeElement.elementText("virtual-path"),
643 theme.getVirtualPath());
644
645 String templateExtension = GetterUtil.getString(
646 themeElement.elementText("template-extension"),
647 theme.getTemplateExtension());
648
649 theme.setName(name);
650 theme.setRootPath(rootPath);
651 theme.setTemplatesPath(templatesPath);
652 theme.setCssPath(cssPath);
653 theme.setImagesPath(imagesPath);
654 theme.setJavaScriptPath(javaScriptPath);
655 theme.setVirtualPath(virtualPath);
656 theme.setTemplateExtension(templateExtension);
657
658 Element settingsElement = themeElement.element("settings");
659
660 if (settingsElement != null) {
661 List<Element> settingElements = settingsElement.elements(
662 "setting");
663
664 for (Element settingElement : settingElements) {
665 boolean configurable = GetterUtil.getBoolean(
666 settingElement.attributeValue("configurable"));
667 String key = settingElement.attributeValue("key");
668 String[] options = StringUtil.split(
669 settingElement.attributeValue("options"));
670 String type = settingElement.attributeValue("type", "text");
671 String value = settingElement.attributeValue(
672 "value", StringPool.BLANK);
673 String script = settingElement.getTextTrim();
674
675 theme.addSetting(
676 key, value, configurable, type, options, script);
677 }
678 }
679
680 theme.setWapTheme(
681 GetterUtil.getBoolean(
682 themeElement.elementText("wap-theme"), theme.isWapTheme()));
683
684 Element rolesElement = themeElement.element("roles");
685
686 if (rolesElement != null) {
687 List<Element> roleNameElements = rolesElement.elements(
688 "role-name");
689
690 for (Element roleNameElement : roleNameElements) {
691 pluginSetting.addRole(roleNameElement.getText());
692 }
693 }
694
695 _readColorSchemes(
696 themeElement, theme.getColorSchemesMap(), themeContextReplace);
697 _readColorSchemes(
698 themeElement, theme.getColorSchemesMap(), themeContextReplace);
699
700 Element layoutTemplatesElement = themeElement.element(
701 "layout-templates");
702
703 if (layoutTemplatesElement != null) {
704 Element standardElement = layoutTemplatesElement.element(
705 "standard");
706
707 if (standardElement != null) {
708 layoutTemplateLocalService.readLayoutTemplate(
709 servletContextName, servletContext, null,
710 standardElement, true, themeId, pluginPackage);
711 }
712
713 Element customElement = layoutTemplatesElement.element(
714 "custom");
715
716 if (customElement != null) {
717 layoutTemplateLocalService.readLayoutTemplate(
718 servletContextName, servletContext, null, customElement,
719 false, themeId, pluginPackage);
720 }
721 }
722
723 if (!theme.isWapTheme()) {
724 _setSpriteImages(servletContext, theme, imagesPath);
725 }
726
727 if (!_themes.containsKey(themeId)) {
728 _themes.put(themeId, theme);
729 }
730
731 themes.add(theme);
732 }
733
734 return themes;
735 }
736
737 private void _setSpriteImages(
738 ServletContext servletContext, Theme theme, String resourcePath)
739 throws Exception {
740
741 if (!resourcePath.startsWith(StringPool.SLASH)) {
742 resourcePath = StringPool.SLASH.concat(resourcePath);
743 }
744
745 Set<String> resourcePaths = servletContext.getResourcePaths(
746 resourcePath);
747
748 if ((resourcePaths == null) || resourcePaths.isEmpty()) {
749 return;
750 }
751
752 List<URL> imageURLs = new ArrayList<URL>(resourcePaths.size());
753
754 for (String curResourcePath : resourcePaths) {
755 if (curResourcePath.endsWith(StringPool.SLASH)) {
756 _setSpriteImages(servletContext, theme, curResourcePath);
757 }
758 else if (curResourcePath.endsWith(".png")) {
759 URL imageURL = servletContext.getResource(curResourcePath);
760
761 if (imageURL != null) {
762 imageURLs.add(imageURL);
763 }
764 else {
765 _log.error(
766 "Resource URL for " + curResourcePath + " is null");
767 }
768 }
769 }
770
771 String spriteRootDirName = PropsValues.SPRITE_ROOT_DIR;
772 String spriteFileName = resourcePath.concat(
773 PropsValues.SPRITE_FILE_NAME);
774 String spritePropertiesFileName = resourcePath.concat(
775 PropsValues.SPRITE_PROPERTIES_FILE_NAME);
776 String rootPath = ServletContextUtil.getRootPath(servletContext);
777
778 Properties spriteProperties = SpriteProcessorUtil.generate(
779 servletContext, imageURLs, spriteRootDirName, spriteFileName,
780 spritePropertiesFileName, rootPath, 16, 16, 10240);
781
782 if (spriteProperties == null) {
783 return;
784 }
785
786 String contextPath = ContextPathUtil.getContextPath(servletContext);
787
788 spriteFileName = contextPath.concat(SpriteProcessor.PATH).concat(
789 spriteFileName);
790
791 theme.setSpriteImages(spriteFileName, spriteProperties);
792 }
793
794 private static Log _log = LogFactoryUtil.getLog(
795 ThemeLocalServiceImpl.class);
796
797 private static Map<String, Theme> _themes =
798 new ConcurrentHashMap<String, Theme>();
799 private static Map<Long, Map<String, Theme>> _themesPool =
800 new ConcurrentHashMap<Long, Map<String, Theme>>();
801
802 }