/*
* Copyright (C) 2003-2017 eXo Platform SAS.
*
* This 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 software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.management.calendar.operations;
import com.thoughtworks.xstream.XStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.exoplatform.calendar.service.Calendar;
import org.exoplatform.calendar.service.CalendarEvent;
import org.exoplatform.calendar.service.CalendarService;
import org.exoplatform.calendar.service.impl.CalendarServiceImpl;
import org.exoplatform.calendar.service.impl.JCRDataStorage;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.management.calendar.CalendarExtension;
import org.exoplatform.management.common.FileEntry;
import org.exoplatform.management.common.MockHttpServletRequest;
import org.exoplatform.management.common.MockHttpServletResponse;
import org.exoplatform.management.common.NavigationUtils;
import org.exoplatform.management.common.exportop.ActivitiesExportTask;
import org.exoplatform.management.common.exportop.SpaceMetadataExportTask;
import org.exoplatform.management.common.importop.AbstractImportOperationHandler;
import org.exoplatform.management.common.importop.ActivityImportOperationInterface;
import org.exoplatform.management.common.importop.FileImportOperationInterface;
import org.exoplatform.portal.application.PortalRequestContext;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.UserACL;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.mop.user.UserNavigation;
import org.exoplatform.portal.mop.user.UserPortal;
import org.exoplatform.portal.mop.user.UserPortalContext;
import org.exoplatform.portal.url.PortalURLContext;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.social.core.activity.model.ExoSocialActivity;
import org.exoplatform.social.core.manager.ActivityManager;
import org.exoplatform.social.core.space.SpaceUtils;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.core.storage.api.ActivityStorage;
import org.exoplatform.social.core.storage.api.IdentityStorage;
import org.exoplatform.web.ControllerContext;
import org.exoplatform.web.application.RequestContext;
import org.exoplatform.web.url.PortalURL;
import org.exoplatform.web.url.ResourceType;
import org.exoplatform.web.url.URLFactory;
import org.exoplatform.web.url.navigation.NodeURL;
import org.exoplatform.webui.application.WebuiApplication;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.management.api.exceptions.OperationException;
import org.gatein.management.api.operation.OperationAttributes;
import org.gatein.management.api.operation.OperationContext;
import org.gatein.management.api.operation.OperationNames;
import org.gatein.management.api.operation.ResultHandler;
import org.gatein.management.api.operation.model.NoResultModel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
/**
* The Class CalendarDataImportResource.
*
* @author <a href="mailto:bkhanfir@exoplatform.com">Boubaker Khanfir</a>
* @version $Revision$
*/
public class CalendarDataImportResource extends AbstractImportOperationHandler implements ActivityImportOperationInterface, FileImportOperationInterface {
/** The Constant log. */
final private static Logger log = LoggerFactory.getLogger(CalendarDataImportResource.class);
/** The Constant CALENDAR_PORTLET_NAME. */
public static final String CALENDAR_PORTLET_NAME = "CalendarPortlet";
/** The Constant INVITATION_DETAIL. */
public static final String INVITATION_DETAIL = "/invitation/detail/";
/** The calendar service. */
private CalendarService calendarService;
/** The calendar storage. */
private JCRDataStorage calendarStorage;
/** The portal config service. */
private UserPortalConfigService portalConfigService;
/** The page service. */
private PageService pageService;
/** The data storage. */
private DataStorage dataStorage;
/** The group calendar. */
private boolean groupCalendar;
/** The space calendar. */
private boolean spaceCalendar;
/** The type. */
private String type;
/**
* Instantiates a new calendar data import resource.
*
* @param groupCalendar the group calendar
* @param spaceCalendar the space calendar
*/
public CalendarDataImportResource(boolean groupCalendar, boolean spaceCalendar) {
this.groupCalendar = groupCalendar;
this.spaceCalendar = spaceCalendar;
type = groupCalendar ? spaceCalendar ? CalendarExtension.SPACE_CALENDAR_TYPE : CalendarExtension.GROUP_CALENDAR_TYPE : CalendarExtension.PERSONAL_CALENDAR_TYPE;
}
/**
* {@inheritDoc}
*/
@Override
public void execute(OperationContext operationContext, ResultHandler resultHandler) throws OperationException {
calendarService = operationContext.getRuntimeContext().getRuntimeComponent(CalendarService.class);
calendarStorage = ((CalendarServiceImpl) calendarService).getDataStorage();
spaceService = operationContext.getRuntimeContext().getRuntimeComponent(SpaceService.class);
activityManager = operationContext.getRuntimeContext().getRuntimeComponent(ActivityManager.class);
activityStorage = operationContext.getRuntimeContext().getRuntimeComponent(ActivityStorage.class);
identityStorage = operationContext.getRuntimeContext().getRuntimeComponent(IdentityStorage.class);
userACL = operationContext.getRuntimeContext().getRuntimeComponent(UserACL.class);
portalConfigService = operationContext.getRuntimeContext().getRuntimeComponent(UserPortalConfigService.class);
pageService = operationContext.getRuntimeContext().getRuntimeComponent(PageService.class);
dataStorage = operationContext.getRuntimeContext().getRuntimeComponent(DataStorage.class);
OperationAttributes attributes = operationContext.getAttributes();
List<String> filters = attributes.getValues("filter");
// "replace-existing" attribute. Defaults to false.
boolean replaceExisting = filters.contains("replace-existing:true");
// "create-space" attribute. Defaults to false.
boolean createSpace = filters.contains("create-space:true");
InputStream attachmentInputStream = getAttachementInputStream(operationContext);
RequestContext originalRequestContext = null;
try {
// extract data from zip
Map<String, List<FileEntry>> contentsByOwner = extractDataFromZip(attachmentInputStream);
// FIXME: INTEG-333. Add this to not have a null pointer exception while
// importing
if (!contentsByOwner.isEmpty()) {
originalRequestContext = fixPortalRequest();
}
for (String categoryId : contentsByOwner.keySet()) {
List<FileEntry> fileEntries = contentsByOwner.get(categoryId);
FileEntry spaceMetadataFile = getAndRemoveFileByPath(fileEntries, SpaceMetadataExportTask.FILENAME);
FileEntry activitiesFile = getAndRemoveFileByPath(fileEntries, ActivitiesExportTask.FILENAME);
for (FileEntry fileEntry : fileEntries) {
RequestLifeCycle.begin(PortalContainer.getInstance());
try {
importCalendar(fileEntry.getFile(), spaceMetadataFile == null ? null : spaceMetadataFile.getFile(), replaceExisting, createSpace);
} finally {
RequestLifeCycle.end();
}
}
if (activitiesFile != null) {
importActivities(activitiesFile.getFile(), null, true);
}
}
} catch (Exception e) {
throw new OperationException(OperationNames.IMPORT_RESOURCE, "Unable to import calendar contents", e);
} finally {
if (originalRequestContext != null) {
WebuiRequestContext.setCurrentInstance(originalRequestContext);
}
if (attachmentInputStream != null) {
try {
attachmentInputStream.close();
} catch (IOException e) {
// Nothing to do
}
}
}
resultHandler.completed(NoResultModel.INSTANCE);
}
/**
* {@inheritDoc}
*/
@Override
public void attachActivityToEntity(ExoSocialActivity activity, ExoSocialActivity comment) throws Exception {
if (comment != null) {
return;
}
String eventId = activity.getTemplateParams().get(CalendarExtension.EVENT_ID_KEY);
CalendarEvent event = calendarService.getEventById(eventId);
if (event == null) {
event = calendarService.getGroupEvent(eventId);
}
saveEvent(event, activity);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isActivityNotValid(ExoSocialActivity activity, ExoSocialActivity comment) throws Exception {
if (comment != null || activity.isComment()) {
return false;
}
String eventId = activity.getTemplateParams().get(CalendarExtension.EVENT_ID_KEY);
if (eventId == null) {
log.warn("An unkown Calendar activity was found: " + activity.getTitle());
return true;
}
CalendarEvent event = calendarService.getEventById(eventId);
if (event == null) {
event = calendarService.getGroupEvent(eventId);
if (event == null) {
log.warn("Calendar event not found. Cannot import activity '" + activity.getTitle() + "'.");
return true;
}
}
return false;
}
/**
* Import calendar.
*
* @param file the file
* @param spaceMetadataFile the space metadata file
* @param replaceExisting the replace existing
* @param createSpace the create space
* @throws Exception the exception
*/
private void importCalendar(File file, File spaceMetadataFile, boolean replaceExisting, boolean createSpace) throws Exception {
// Unmarshall calendar data file
XStream xStream = new XStream();
xStream.alias("Calendar", Calendar.class);
xStream.alias("Event", CalendarEvent.class);
@SuppressWarnings("unchecked")
List<Object> objects = (List<Object>) xStream.fromXML(FileUtils.readFileToString(file, "UTF-8"));
Calendar calendar = (Calendar) objects.get(0);
if (groupCalendar && (calendar.getCalendarOwner() == null || !calendar.getCalendarOwner().startsWith(SpaceUtils.SPACE_GROUP))) {
String[] groups = calendar.getGroups();
if (groups != null) {
for (String groupId : groups) {
if (groupId != null && groupId.startsWith(SpaceUtils.SPACE_GROUP)) {
calendar.setCalendarOwner(groups[0]);
break;
}
}
}
}
Calendar toReplaceCalendar = calendarService.getCalendarById(calendar.getId());
if (toReplaceCalendar != null) {
if (replaceExisting) {
log.info("Overwrite existing calendar: " + toReplaceCalendar.getName());
if (groupCalendar) {
// FIXME event activities aren't deleted
List<CalendarEvent> events = calendarService.getGroupEventByCalendar(Collections.singletonList(calendar.getId()));
deleteCalendarActivities(events);
// Delete Calendar
calendarService.removePublicCalendar(calendar.getId());
} else {
// FIXME event activities aren't deleted
List<CalendarEvent> events = calendarService.getUserEventByCalendar(calendar.getCalendarOwner(), Collections.singletonList(calendar.getId()));
deleteCalendarActivities(events);
// Delete Calendar
calendarService.removeUserCalendar(calendar.getCalendarOwner(), calendar.getId());
}
} else {
log.info("Ignore existing calendar: " + toReplaceCalendar.getName());
}
}
log.info("Create calendar: " + calendar.getName());
if (groupCalendar) {
if (spaceCalendar) {
if (spaceMetadataFile != null && spaceMetadataFile.exists()) {
boolean spaceCreatedOrAlreadyExists = createSpaceIfNotExists(spaceMetadataFile, createSpace);
if (!spaceCreatedOrAlreadyExists) {
log.warn("Import of Calendar of space '" + calendar.getName() + "' is ignored. Turn on 'create-space:true' option if you want to automatically create the space.");
return;
}
calendarService.removePublicCalendar(calendar.getId());
}
}
calendarStorage.savePublicCalendar(calendar, true, null);
} else {
calendarStorage.saveUserCalendar(calendar.getCalendarOwner(), calendar, true);
}
@SuppressWarnings("unchecked")
List<CalendarEvent> events = (List<CalendarEvent>) objects.get(1);
for (CalendarEvent event : events) {
log.info("Create calendar event: " + calendar.getName() + "/" + event.getSummary());
if (groupCalendar) {
calendarStorage.savePublicEvent(calendar.getId(), event, true);
} else {
calendarStorage.saveUserEvent(calendar.getCalendarOwner(), calendar.getId(), event, true);
}
}
deleteCalendarActivities(events);
}
/**
* Delete calendar activities.
*
* @param events the events
*/
private void deleteCalendarActivities(List<CalendarEvent> events) {
for (CalendarEvent event : events) {
if (event != null && event.getActivityId() != null) {
deleteActivity(event.getActivityId());
}
}
}
/**
* Save event.
*
* @param event the event
* @param exoSocialActivity the exo social activity
* @throws Exception the exception
*/
private void saveEvent(CalendarEvent event, ExoSocialActivity exoSocialActivity) throws Exception {
event.setActivityId(exoSocialActivity.getId());
Calendar calendar = calendarService.getCalendarById(event.getCalendarId());
if (calendar.getCalendarOwner().startsWith("/")) {
calendarStorage.savePublicEvent(event.getCalendarId(), event, false);
// the URL of Stream Activity will use staging URL, modify it
updateCalendarActivityURL(event, spaceCalendar ? calendar.getCalendarOwner() : null);
} else {
calendarStorage.saveUserEvent(userACL.getSuperUser(), event.getCalendarId(), event, false);
// the URL of Stream Activity will use staging URL, modify it
updateCalendarActivityURL(event, null);
}
}
/**
* Update calendar activity URL.
*
* @param event the event
* @param groupId the group id
* @throws Exception the exception
*/
private void updateCalendarActivityURL(CalendarEvent event, String groupId) throws Exception {
ExoSocialActivity activity = activityManager.getActivity(event.getActivityId());
if (activity != null) {
Map<String, String> templateParams = activity.getTemplateParams();
if (templateParams.containsKey(CalendarExtension.EVENT_LINK_KEY)) {
templateParams.put(CalendarExtension.EVENT_LINK_KEY, getLink(event, activity, groupId));
activityManager.updateActivity(activity);
}
}
}
/**
* Gets the link.
*
* @param event the event
* @param activity the activity
* @param spaceGroupId the space group id
* @return the link
* @throws Exception the exception
*/
private String getLink(CalendarEvent event, ExoSocialActivity activity, String spaceGroupId) throws Exception {
SiteKey siteKey = null;
if (spaceGroupId == null) {
siteKey = SiteKey.portal(portalConfigService.getDefaultPortal());
} else {
siteKey = SiteKey.group(spaceGroupId);
}
PortalRequestContext prc = Util.getPortalRequestContext();
UserPortal userPortal = null;
if (prc == null) {
// Get User navigation nodes defined for {siteName}
userPortal = portalConfigService.getUserPortalConfig(portalConfigService.getDefaultPortal(), "root", NULL_CONTEXT).getUserPortal();
} else {
userPortal = prc.getUserPortal();
}
String uri = NavigationUtils.getNavURIWithApplication(pageService, dataStorage, userPortal, siteKey, CALENDAR_PORTLET_NAME);
if (uri != null) {
String username = ConversationState.getCurrent().getIdentity().getUserId();
String url = uri + INVITATION_DETAIL + username + "/" + event.getId() + "/" + event.getCalType();
return url;
}
return StringUtils.EMPTY;
}
/** The Constant NULL_CONTEXT. */
private static final UserPortalContext NULL_CONTEXT = new UserPortalContext() {
public ResourceBundle getBundle(UserNavigation navigation) {
return null;
}
public Locale getUserLocale() {
return Locale.ENGLISH;
}
};
/**
* Fix portal request.
*
* @return the request context
*/
private RequestContext fixPortalRequest() {
RequestContext originalRequestContext;
originalRequestContext = WebuiRequestContext.getCurrentInstance();
if (originalRequestContext == null) {
final ControllerContext controllerContext = new ControllerContext(null, null, new MockHttpServletRequest(), new MockHttpServletResponse(), null);
PortalRequestContext portalRequestContext = new PortalRequestContext((WebuiApplication) null, controllerContext, groupCalendar ? SiteType.GROUP.getName() : SiteType.PORTAL.getName(), portalConfigService.getDefaultPortal(), "/portal/"
+ portalConfigService.getDefaultPortal() + "/calendar", (Locale) null) {
@Override
public <R, U extends PortalURL<R, U>> U newURL(ResourceType<R, U> resourceType, URLFactory urlFactory) {
if (resourceType.equals(NodeURL.TYPE)) {
@SuppressWarnings("unchecked")
U u = (U) new NodeURL(new PortalURLContext(controllerContext, null) {
public <S extends Object, V extends org.exoplatform.web.url.PortalURL<S, V>> String render(V url) {
return "";
};
});
return u;
}
return super.newURL(resourceType, urlFactory);
}
};
WebuiRequestContext.setCurrentInstance(portalRequestContext);
}
return originalRequestContext;
}
/**
* {@inheritDoc}
*/
@Override
public String getManagedFilesPrefix() {
return "calendar/" + type + "/";
}
/**
* {@inheritDoc}
*/
@Override
public boolean isUnKnownFileFormat(String filePath) {
return !filePath.contains(CalendarExportTask.CALENDAR_SEPARATOR)
|| (!filePath.endsWith(".xml") && !filePath.endsWith(SpaceMetadataExportTask.FILENAME) && !filePath.endsWith(ActivitiesExportTask.FILENAME));
}
/**
* {@inheritDoc}
*/
@Override
public boolean addSpecialFile(List<FileEntry> fileEntries, String filePath, File file) {
if (filePath.endsWith(SpaceMetadataExportTask.FILENAME)) {
fileEntries.add(new FileEntry(SpaceMetadataExportTask.FILENAME, file));
return true;
} else if (filePath.endsWith(ActivitiesExportTask.FILENAME)) {
fileEntries.add(new FileEntry(ActivitiesExportTask.FILENAME, file));
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public String extractIdFromPath(String path) {
String[] paths = path.split(CalendarExportTask.CALENDAR_SEPARATOR);
path = paths[1];
if (path.contains(".metadata")) {
path = path.substring(0, path.indexOf(".metadata"));
}
String id = path.substring(0, path.contains(".") ? path.indexOf(".") : path.contains("/") ? path.indexOf("/") : path.length());
return id;
}
/**
* {@inheritDoc}
*/
@Override
public String getNodePath(String filePath) {
String[] paths = filePath.split(CalendarExportTask.CALENDAR_SEPARATOR);
return paths[1];
}
}