/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.layout.set.internal.exportimport.data.handler;
import com.liferay.exportimport.data.handler.base.BaseStagedModelDataHandler;
import com.liferay.exportimport.kernel.lar.ExportImportDateUtil;
import com.liferay.exportimport.kernel.lar.ExportImportPathUtil;
import com.liferay.exportimport.kernel.lar.ExportImportProcessCallbackRegistryUtil;
import com.liferay.exportimport.kernel.lar.ExportImportThreadLocal;
import com.liferay.exportimport.kernel.lar.PortletDataContext;
import com.liferay.exportimport.kernel.lar.PortletDataHandlerKeys;
import com.liferay.exportimport.kernel.lar.StagedModelDataHandler;
import com.liferay.exportimport.kernel.lar.StagedModelDataHandlerUtil;
import com.liferay.exportimport.kernel.lar.StagedModelType;
import com.liferay.exportimport.kernel.staging.LayoutStagingUtil;
import com.liferay.exportimport.lar.ThemeExporter;
import com.liferay.exportimport.lar.ThemeImporter;
import com.liferay.layout.set.internal.exportimport.staged.model.repository.StagedLayoutSetStagedModelRepository;
import com.liferay.layout.set.model.adapter.StagedLayoutSet;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.Group;
import com.liferay.portal.kernel.model.Image;
import com.liferay.portal.kernel.model.Layout;
import com.liferay.portal.kernel.model.LayoutSet;
import com.liferay.portal.kernel.model.LayoutSetBranch;
import com.liferay.portal.kernel.model.LayoutSetPrototype;
import com.liferay.portal.kernel.model.LayoutSetStagingHandler;
import com.liferay.portal.kernel.model.StagedModel;
import com.liferay.portal.kernel.model.adapter.ModelAdapterUtil;
import com.liferay.portal.kernel.service.GroupLocalService;
import com.liferay.portal.kernel.service.ImageLocalService;
import com.liferay.portal.kernel.service.LayoutLocalService;
import com.liferay.portal.kernel.service.LayoutSetBranchLocalService;
import com.liferay.portal.kernel.service.LayoutSetLocalService;
import com.liferay.portal.kernel.service.LayoutSetPrototypeLocalService;
import com.liferay.portal.kernel.service.ServiceContext;
import com.liferay.portal.kernel.service.ServiceContextThreadLocal;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.Constants;
import com.liferay.portal.kernel.util.DateRange;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.MapUtil;
import com.liferay.portal.kernel.util.UnicodeProperties;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.xml.Element;
import com.liferay.sites.kernel.util.Sites;
import com.liferay.sites.kernel.util.SitesUtil;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* @author Mate Thurzo
*/
@Component(immediate = true, service = StagedModelDataHandler.class)
public class StagedLayoutSetStagedModelDataHandler
extends BaseStagedModelDataHandler<StagedLayoutSet> {
public static final String[] CLASS_NAMES =
{StagedLayoutSet.class.getName()};
@Override
public String[] getClassNames() {
return CLASS_NAMES;
}
protected void checkLayoutSetPrototypeLayouts(
PortletDataContext portletDataContext, Set<Layout> modifiedLayouts)
throws PortalException {
boolean layoutSetPrototypeLinkEnabled = MapUtil.getBoolean(
portletDataContext.getParameterMap(),
PortletDataHandlerKeys.LAYOUT_SET_PROTOTYPE_LINK_ENABLED);
if (!layoutSetPrototypeLinkEnabled ||
Validator.isNull(portletDataContext.getLayoutSetPrototypeUuid())) {
return;
}
LayoutSetPrototype layoutSetPrototype =
_layoutSetPrototypeLocalService.
getLayoutSetPrototypeByUuidAndCompanyId(
portletDataContext.getLayoutSetPrototypeUuid(),
portletDataContext.getCompanyId());
List<Layout> layoutSetLayouts = _layoutLocalService.getLayouts(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout());
for (Layout layout : layoutSetLayouts) {
String sourcePrototypeLayoutUuid =
layout.getSourcePrototypeLayoutUuid();
if (Validator.isNull(sourcePrototypeLayoutUuid)) {
continue;
}
if (SitesUtil.isLayoutModifiedSinceLastMerge(layout)) {
modifiedLayouts.add(layout);
continue;
}
Layout sourcePrototypeLayout = _layoutLocalService.fetchLayout(
layout.getSourcePrototypeLayoutUuid(),
layoutSetPrototype.getGroupId(), true);
if (sourcePrototypeLayout == null) {
_layoutLocalService.deleteLayout(
layout, false,
ServiceContextThreadLocal.getServiceContext());
}
}
}
protected void deleteMissingLayouts(
PortletDataContext portletDataContext, List<Element> layoutElements) {
boolean deleteMissingLayouts = MapUtil.getBoolean(
portletDataContext.getParameterMap(),
PortletDataHandlerKeys.DELETE_MISSING_LAYOUTS,
Boolean.TRUE.booleanValue());
if (!deleteMissingLayouts) {
return;
}
List<Layout> previousLayouts = _layoutLocalService.getLayouts(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout());
Stream<Element> layoutElementsStream = layoutElements.stream();
List<String> sourceLayoutUuids = layoutElementsStream.map(
(layoutElement) -> layoutElement.attributeValue("uuid")
).collect(
Collectors.toList()
);
if (_log.isDebugEnabled() && !sourceLayoutUuids.isEmpty()) {
_log.debug("Delete missing layouts");
}
Map<Long, Long> layoutPlids =
(Map<Long, Long>)portletDataContext.getNewPrimaryKeysMap(
Layout.class);
ServiceContext serviceContext =
ServiceContextThreadLocal.getServiceContext();
for (Layout layout : previousLayouts) {
if (!sourceLayoutUuids.contains(layout.getUuid()) &&
!layoutPlids.containsValue(layout.getPlid())) {
try {
_layoutLocalService.deleteLayout(
layout, false, serviceContext);
}
catch (Exception e) {
}
}
}
}
protected void doExportStagedModel(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet)
throws Exception {
exportLayouts(portletDataContext, stagedLayoutSet);
exportLogo(portletDataContext, stagedLayoutSet);
exportTheme(portletDataContext, stagedLayoutSet);
// Serialization
Element stagedLayoutSetElement =
portletDataContext.getExportDataElement(stagedLayoutSet);
// Last publish date must not be exported
UnicodeProperties settingsProperties =
stagedLayoutSet.getSettingsProperties();
settingsProperties.remove("last-publish-date");
// Page versioning
stagedLayoutSet = unwrapLayoutSetStagingHandler(stagedLayoutSet);
portletDataContext.addClassedModel(
stagedLayoutSetElement,
ExportImportPathUtil.getModelPath(stagedLayoutSet),
stagedLayoutSet);
// Last publish date
boolean updateLastPublishDate = MapUtil.getBoolean(
portletDataContext.getParameterMap(),
PortletDataHandlerKeys.UPDATE_LAST_PUBLISH_DATE);
if (ExportImportThreadLocal.isStagingInProcess() &&
updateLastPublishDate) {
ExportImportProcessCallbackRegistryUtil.registerCallback(
portletDataContext.getExportImportProcessId(),
new UpdateLayoutSetLastPublishDateCallable(
portletDataContext.getDateRange(),
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout()));
}
}
protected void doImportStagedModel(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet)
throws Exception {
Optional<StagedLayoutSet> existingLayoutSetOptional =
_stagedLayoutSetStagedModelRepository.fetchExistingLayoutSet(
portletDataContext.getScopeGroupId(),
stagedLayoutSet.isPrivateLayout());
StagedLayoutSet importedStagedLayoutSet =
(StagedLayoutSet)stagedLayoutSet.clone();
importedStagedLayoutSet.setGroupId(
portletDataContext.getScopeGroupId());
String layoutsImportMode = MapUtil.getString(
portletDataContext.getParameterMap(),
PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE,
PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE_MERGE_BY_LAYOUT_UUID);
if (existingLayoutSetOptional.isPresent() &&
!layoutsImportMode.equals(
PortletDataHandlerKeys.
LAYOUTS_IMPORT_MODE_CREATED_FROM_PROTOTYPE)) {
StagedLayoutSet existingLayoutSet = existingLayoutSetOptional.get();
importedStagedLayoutSet.setLayoutSetId(
existingLayoutSet.getLayoutSetId());
importedStagedLayoutSet =
_stagedLayoutSetStagedModelRepository.updateStagedModel(
portletDataContext, importedStagedLayoutSet);
}
importLogo(portletDataContext);
importTheme(portletDataContext, stagedLayoutSet);
portletDataContext.importClassedModel(
stagedLayoutSet, importedStagedLayoutSet);
Element layoutsElement = portletDataContext.getImportDataGroupElement(
Layout.class);
List<Element> layoutElements = layoutsElement.elements();
// Delete missing pages
deleteMissingLayouts(portletDataContext, layoutElements);
// Remove layouts that were deleted from the layout set prototype
Set<Layout> modifiedLayouts = new HashSet<>();
checkLayoutSetPrototypeLayouts(portletDataContext, modifiedLayouts);
// Last merge time
updateLastMergeTime(portletDataContext, modifiedLayouts);
// Page priorities
updateLayoutPriorities(
portletDataContext, layoutElements,
portletDataContext.isPrivateLayout());
// Page count
_layoutSetLocalService.updatePageCount(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout());
}
protected void exportLayouts(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet) {
// Force to always export layout deletions
portletDataContext.addDeletionSystemEventStagedModelTypes(
new StagedModelType(Layout.class));
// Force to always have a layout group element
portletDataContext.getExportDataGroupElement(Layout.class);
long[] layoutIds = portletDataContext.getLayoutIds();
List<StagedModel> stagedModels =
_stagedLayoutSetStagedModelRepository.fetchChildrenStagedModels(
portletDataContext, stagedLayoutSet);
for (StagedModel stagedModel : stagedModels) {
Layout layout = (Layout)stagedModel;
if (!ArrayUtil.contains(layoutIds, layout.getLayoutId())) {
Element layoutElement = portletDataContext.getExportDataElement(
layout);
layoutElement.addAttribute(Constants.ACTION, Constants.SKIP);
continue;
}
try {
if (!LayoutStagingUtil.prepareLayoutStagingHandler(
portletDataContext, layout)) {
continue;
}
StagedModelDataHandlerUtil.exportReferenceStagedModel(
portletDataContext, stagedLayoutSet, layout,
PortletDataContext.REFERENCE_TYPE_CHILD);
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to export layout " + layout.getName(), e);
}
}
}
}
protected void exportLogo(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet) {
boolean logo = MapUtil.getBoolean(
portletDataContext.getParameterMap(), PortletDataHandlerKeys.LOGO);
if (!logo) {
return;
}
long layoutSetBranchId = MapUtil.getLong(
portletDataContext.getParameterMap(), "layoutSetBranchId");
LayoutSetBranch layoutSetBranch =
_layoutSetBranchLocalService.fetchLayoutSetBranch(
layoutSetBranchId);
Image image = null;
if (layoutSetBranch != null) {
try {
image = _imageLocalService.getImage(
layoutSetBranch.getLogoId());
}
catch (PortalException pe) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to get logo for layout set branch " +
layoutSetBranch.getLayoutSetBranchId(),
pe);
}
}
}
else {
try {
image = _imageLocalService.getImage(
stagedLayoutSet.getLogoId());
}
catch (PortalException pe) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to get logo for layout set " +
stagedLayoutSet.getLayoutSetId(),
pe);
}
}
}
if ((image != null) && (image.getTextObj() != null)) {
String logoPath = ExportImportPathUtil.getRootPath(
portletDataContext);
logoPath += "/logo";
Element rootElement = portletDataContext.getExportDataRootElement();
Element headerElement = rootElement.element("header");
headerElement.addAttribute("logo-path", logoPath);
portletDataContext.addZipEntry(logoPath, image.getTextObj());
}
}
protected void exportTheme(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet) {
long layoutSetBranchId = MapUtil.getLong(
portletDataContext.getParameterMap(), "layoutSetBranchId");
LayoutSetBranch layoutSetBranch =
_layoutSetBranchLocalService.fetchLayoutSetBranch(
layoutSetBranchId);
ThemeExporter themeExporter = ThemeExporter.getInstance();
if (layoutSetBranch != null) {
try {
themeExporter.exportTheme(portletDataContext, layoutSetBranch);
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to export theme reference for layout set " +
"branch " + layoutSetBranch.getLayoutSetBranchId(),
e);
}
}
}
else {
try {
themeExporter.exportTheme(portletDataContext, stagedLayoutSet);
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to export theme reference for layout set " +
stagedLayoutSet.getLayoutSetId(),
e);
}
}
}
}
protected void importLogo(PortletDataContext portletDataContext) {
boolean logo = MapUtil.getBoolean(
portletDataContext.getParameterMap(), PortletDataHandlerKeys.LOGO);
if (!logo) {
return;
}
Element rootElement = portletDataContext.getImportDataRootElement();
Element headerElement = rootElement.element("header");
String logoPath = headerElement.attributeValue("logo-path");
if (Validator.isNull(logoPath)) {
return;
}
byte[] iconBytes = portletDataContext.getZipEntryAsByteArray(logoPath);
try {
if (ArrayUtil.isNotEmpty(iconBytes)) {
_layoutSetLocalService.updateLogo(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout(), true, iconBytes);
}
else {
_layoutSetLocalService.updateLogo(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout(), false, (File)null);
}
}
catch (PortalException pe) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to import logo", pe);
}
}
}
protected void importTheme(
PortletDataContext portletDataContext,
StagedLayoutSet stagedLayoutSet) {
ThemeImporter themeImporter = ThemeImporter.getInstance();
try {
themeImporter.importTheme(portletDataContext, stagedLayoutSet);
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn(
"Unable to import theme reference " +
stagedLayoutSet.getThemeId(),
e);
}
}
}
protected StagedLayoutSet unwrapLayoutSetStagingHandler(
StagedLayoutSet stagedLayoutSet) {
LayoutSet layoutSet = ModelAdapterUtil.adapt(
stagedLayoutSet, StagedLayoutSet.class, LayoutSet.class);
LayoutSetStagingHandler layoutSetStagingHandler =
LayoutStagingUtil.getLayoutSetStagingHandler(layoutSet);
if (layoutSetStagingHandler == null) {
return stagedLayoutSet;
}
return ModelAdapterUtil.adapt(
layoutSetStagingHandler.getLayoutSet(), LayoutSet.class,
StagedLayoutSet.class);
}
protected void updateLastMergeTime(
PortletDataContext portletDataContext, Set<Layout> modifiedLayouts)
throws PortalException {
String layoutsImportMode = MapUtil.getString(
portletDataContext.getParameterMap(),
PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE,
PortletDataHandlerKeys.LAYOUTS_IMPORT_MODE_MERGE_BY_LAYOUT_UUID);
if (!layoutsImportMode.equals(
PortletDataHandlerKeys.
LAYOUTS_IMPORT_MODE_CREATED_FROM_PROTOTYPE)) {
return;
}
// Last merge time is updated only if there aren not any modified
// layouts
Map<Long, Layout> layouts =
(Map<Long, Layout>)portletDataContext.getNewPrimaryKeysMap(
Layout.class + ".layout");
long lastMergeTime = System.currentTimeMillis();
for (Layout layout : layouts.values()) {
layout = _layoutLocalService.getLayout(layout.getPlid());
if (modifiedLayouts.contains(layout)) {
continue;
}
UnicodeProperties typeSettingsProperties =
layout.getTypeSettingsProperties();
typeSettingsProperties.setProperty(
Sites.LAST_MERGE_TIME, String.valueOf(lastMergeTime));
_layoutLocalService.updateLayout(layout);
}
// The layout set may be stale because LayoutUtil#update(layout)
// triggers LayoutSetPrototypeLayoutModelListener and that may have
// updated this layout set
LayoutSet layoutSet = _layoutSetLocalService.getLayoutSet(
portletDataContext.getGroupId(),
portletDataContext.isPrivateLayout());
UnicodeProperties settingsProperties =
layoutSet.getSettingsProperties();
String mergeFailFriendlyURLLayouts = settingsProperties.getProperty(
Sites.MERGE_FAIL_FRIENDLY_URL_LAYOUTS);
if (Validator.isNull(mergeFailFriendlyURLLayouts)) {
settingsProperties.setProperty(
Sites.LAST_MERGE_TIME, String.valueOf(lastMergeTime));
_layoutSetLocalService.updateLayoutSet(layoutSet);
}
}
protected void updateLayoutPriorities(
PortletDataContext portletDataContext, List<Element> layoutElements,
boolean privateLayout) {
Map<Long, Layout> layouts =
(Map<Long, Layout>)portletDataContext.getNewPrimaryKeysMap(
Layout.class + ".layout");
Map<Long, Integer> layoutPriorities = new HashMap<>();
int maxPriority = Integer.MIN_VALUE;
for (Element layoutElement : layoutElements) {
String action = layoutElement.attributeValue(Constants.ACTION);
if (action.equals(Constants.SKIP)) {
// We only want to update priorites if there are no elements
// with the SKIP action
return;
}
if (action.equals(Constants.ADD)) {
long layoutId = GetterUtil.getLong(
layoutElement.attributeValue("layout-id"));
Layout layout = layouts.get(layoutId);
// Layout might not have been imported due to a controlled
// error. See SitesImpl#addMergeFailFriendlyURLLayout.
if (layout == null) {
continue;
}
int layoutPriority = GetterUtil.getInteger(
layoutElement.attributeValue("layout-priority"));
layoutPriorities.put(layout.getPlid(), layoutPriority);
if (maxPriority < layoutPriority) {
maxPriority = layoutPriority;
}
}
}
List<Layout> layoutSetLayouts = _layoutLocalService.getLayouts(
portletDataContext.getGroupId(), privateLayout);
for (Layout layout : layoutSetLayouts) {
if (layoutPriorities.containsKey(layout.getPlid())) {
layout.setPriority(layoutPriorities.get(layout.getPlid()));
}
else {
layout.setPriority(++maxPriority);
}
_layoutLocalService.updateLayout(layout);
}
}
private static final Log _log = LogFactoryUtil.getLog(
StagedLayoutSetStagedModelDataHandler.class);
@Reference
private GroupLocalService _groupLocalService;
@Reference
private ImageLocalService _imageLocalService;
@Reference
private LayoutLocalService _layoutLocalService;
@Reference
private LayoutSetBranchLocalService _layoutSetBranchLocalService;
@Reference
private LayoutSetLocalService _layoutSetLocalService;
@Reference
private LayoutSetPrototypeLocalService _layoutSetPrototypeLocalService;
@Reference
private StagedLayoutSetStagedModelRepository
_stagedLayoutSetStagedModelRepository;
private class UpdateLayoutSetLastPublishDateCallable
implements Callable<Void> {
public UpdateLayoutSetLastPublishDateCallable(
DateRange dateRange, long groupId, boolean privateLayout) {
_dateRange = dateRange;
_groupId = groupId;
_privateLayout = privateLayout;
}
@Override
public Void call() throws PortalException {
Group group = _groupLocalService.getGroup(_groupId);
Date endDate = null;
if (_dateRange != null) {
endDate = _dateRange.getEndDate();
}
if (group.hasStagingGroup()) {
Group stagingGroup = group.getStagingGroup();
ExportImportDateUtil.updateLastPublishDate(
stagingGroup.getGroupId(), _privateLayout, _dateRange,
endDate);
}
else {
ExportImportDateUtil.updateLastPublishDate(
_groupId, _privateLayout, _dateRange, endDate);
}
return null;
}
private final DateRange _dateRange;
private final long _groupId;
private final boolean _privateLayout;
}
}