QuickInfoSessionController.java
/*
* Copyright (C) 2000 - 2026 Silverpeas
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of the GPL, you may
* redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS")
* applications as described in Silverpeas's FLOSS exception. You should have received a copy of the
* text describing the FLOSS exception, and it is also available here:
* "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
package org.silverpeas.components.quickinfo.control;
import org.silverpeas.components.quickinfo.NewsByStatus;
import org.silverpeas.components.quickinfo.QuickInfoComponentSettings;
import org.silverpeas.components.quickinfo.model.News;
import org.silverpeas.components.quickinfo.model.NewsSelection;
import org.silverpeas.components.quickinfo.model.QuickInfoService;
import org.silverpeas.components.quickinfo.model.QuickInfoServiceProvider;
import org.silverpeas.core.ResourceReference;
import org.silverpeas.kernel.SilverpeasException;
import org.silverpeas.kernel.SilverpeasRuntimeException;
import org.silverpeas.kernel.exception.NotFoundException;
import org.silverpeas.core.admin.component.model.ComponentInstLight;
import org.silverpeas.core.admin.component.model.PasteDetail;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.clipboard.ClipboardException;
import org.silverpeas.core.exception.DecodingException;
import org.silverpeas.core.io.media.image.thumbnail.ThumbnailSettings;
import org.silverpeas.core.io.upload.UploadedFile;
import org.silverpeas.core.pdc.pdc.model.PdcPosition;
import org.silverpeas.core.silverstatistics.access.service.StatisticService;
import org.silverpeas.core.subscription.SubscriptionService;
import org.silverpeas.core.subscription.SubscriptionServiceProvider;
import org.silverpeas.core.subscription.service.ComponentSubscription;
import org.silverpeas.core.subscription.service.ComponentSubscriptionResource;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.kernel.logging.SilverLogger;
import org.silverpeas.core.web.mvc.controller.AbstractComponentSessionController;
import org.silverpeas.core.web.mvc.controller.ComponentContext;
import org.silverpeas.core.web.mvc.controller.MainSessionController;
import org.silverpeas.core.web.subscription.SubscriptionContext;
import org.silverpeas.core.web.util.ListIndex;
import org.silverpeas.core.webapi.pdc.PdcClassificationEntity;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static java.util.Optional.ofNullable;
import static org.silverpeas.core.cache.service.VolatileIdentifierProvider.newVolatileIntegerIdentifierOn;
/**
* @author squere
*/
public class QuickInfoSessionController extends AbstractComponentSessionController {
private static final long serialVersionUID = 2978765807229854572L;
private QuickInfoComponentSettings instanceSettings;
private List<News> mainList;
private List<News> draftList;
private List<News> notYetVisibleList;
private List<News> noMoreVisibleList;
private List<News> currentList;
private final ListIndex currentIndex = new ListIndex(0);
public QuickInfoSessionController(MainSessionController mainSessionCtrl,
ComponentContext componentContext) {
super(mainSessionCtrl, componentContext, QuickInfoComponentSettings.MESSAGES_PATH,
QuickInfoComponentSettings.ICONS_PATH, QuickInfoComponentSettings.SETTINGS_PATH);
}
public QuickInfoComponentSettings getInstanceSettings() {
if (instanceSettings == null) {
ComponentInstLight app = getOrganisationController().getComponentInstLight(getComponentId());
instanceSettings = new QuickInfoComponentSettings(app.getDescription(getLanguage()));
}
instanceSettings.setCommentsEnabled(StringUtil
.getBooleanValue(getComponentParameterValue(QuickInfoComponentSettings.PARAM_COMMENTS)));
instanceSettings.setTaxonomyEnabled(StringUtil
.getBooleanValue(getComponentParameterValue(QuickInfoComponentSettings.PARAM_TAXONOMY)));
instanceSettings.setNotificationAllowed(!getUserDetail().isAnonymous() && !getUserDetail().isAccessGuest());
instanceSettings
.setBroadcastModes(getComponentParameterValue(QuickInfoComponentSettings.PARAM_BROADCAST));
instanceSettings.setDelegatedNewsEnabled(StringUtil
.getBooleanValue(getComponentParameterValue(QuickInfoComponentSettings.PARAM_DELEGATED)));
String userView = getComponentParameterValue(QuickInfoComponentSettings.PARAM_USERVIEW);
if (QuickInfoComponentSettings.VALUE_USERVIEW_LISTING.equals(userView)) {
instanceSettings.setMosaicViewForUsers(false);
}
return instanceSettings;
}
public NewsByStatus getQuickInfos() {
NewsByStatus newsByStatus =
getQuickInfoService().getAllNewsByStatus(getComponentId(), getUserId());
setQuickInfos(newsByStatus);
return newsByStatus;
}
public List<News> getVisibleQuickInfos() {
mainList = getQuickInfoService().getVisibleNews(getComponentId());
return mainList;
}
private void setQuickInfos(NewsByStatus newsByStatus) {
mainList = newsByStatus.getVisibles();
draftList = newsByStatus.getDrafts();
notYetVisibleList = newsByStatus.getNotYetVisibles();
noMoreVisibleList = newsByStatus.getNoMoreVisibles();
}
/**
* Gets the specified news of this QuickInfo instance. The user is authorized on this instance
* only, so a news of another instance is never served.
* @param id the unique identifier of the news.
* @param statVisit true to count the access to the news as a visit.
* @return the news.
* @throws NotFoundException if this instance has no such news.
*/
public News getNews(String id, boolean statVisit) {
News news = getQuickInfoService().getNews(new ResourceReference(id, getComponentId()));
if (news == null) {
throw new NotFoundException("No such news " + id + " in " + getComponentId());
}
if (statVisit) {
addVisit(news);
}
processIndex(news);
return news;
}
/**
* Gets the news of this QuickInfo instance backed by the specified publication. The user is
* authorized on this instance only, so a news of another instance is never served.
* @param foreignId the unique identifier of the publication backing the news.
* @return the news.
* @throws NotFoundException if this instance has no such news.
*/
public News getNewsByForeignId(String foreignId) {
News news = getQuickInfoService().getNewsByForeignId(foreignId);
if (news == null || !getComponentId().equals(news.getComponentInstanceId())) {
throw new NotFoundException(
"No such news with publication " + foreignId + " in " + getComponentId());
}
addVisit(news);
processIndex(news);
return news;
}
/**
* Publish news represented by the given identifier.
* @param id the identifier of the news that must be published.
*/
public void publish(String id) {
getQuickInfoService().publish(new ResourceReference(id, getComponentId()), getUserId());
loadNewsLists(true);
}
/**
* Prepare an instance of news that will exist only in memory if no action persistence will be
* applied on.
* @return an in memory instance of news.
*/
public News prepareEmptyNews() {
News news = News.builder()
.setTitleAndDescription(getString("quickinfo.news.untitled"), null)
.build();
news.setDraft();
news.setComponentInstanceId(getComponentId());
// Dummy identifiers
news.setId("volatileId@" + getComponentId() + "@" + System.nanoTime());
news.setPublicationId(newVolatileIntegerIdentifierOn(getComponentId()));
news.getPublication().getPK().setId(news.getPublicationId());
return news;
}
/**
* Indicates if the given news identifier is one that indicates the news is in memory only.
* @param newsId the news identifier to verify.
* @return true if the news identifier is one for memory use, false otherwise (id is one of
* persisted news).
*/
public boolean isNewsIdentifierFromMemory(String newsId) {
return StringUtil.isNotDefined(newsId) || newsId.startsWith("volatileId@" + getComponentId());
}
/**
* Creates into persistence the news with minimal data.
* @param news the news to persist.
*/
public void create(News news) {
news.setDraft();
news.setComponentInstanceId(getComponentId());
news.setCreatorId(getUserId());
getQuickInfoService().create(news);
}
/**
* Updates all the data of news.
* @param id the identifier of the news (used to load previous data).
* @param updatedNews the data to save.
* @param pdcPositions the pdc positions.
* @param uploadedFiles the files uploaded in the aim to be attached to the news.
* @param forcePublish true to indicate a publishing action, false otherwise.
*/
public void update(String id, News updatedNews, String pdcPositions,
Collection<UploadedFile> uploadedFiles, boolean forcePublish) {
News news = getNews(id, false);
news.setTitle(updatedNews.getTitle());
news.setDescription(updatedNews.getDescription());
news.setKeywords(updatedNews.getKeywords());
news.setContentToStore(updatedNews.getContentToStore());
news.setVisibilityPeriod(updatedNews.getVisibility().getSpecificPeriod().orElse(null));
news.setUpdaterId(getUserId());
news.setImportant(updatedNews.isImportant());
news.setTicker(updatedNews.isTicker());
news.setMandatory(updatedNews.isMandatory());
news.markAsModified();
if (forcePublish) {
news.setPublished();
}
getQuickInfoService().update(news, getPositionsFromJSON(pdcPositions), uploadedFiles,
forcePublish);
loadNewsLists(true);
}
public ThumbnailSettings getThumbnailSettings() {
int width = getSettings().getInteger("thumbnail.width", 200);
int height = getSettings().getInteger("thumbnail.height", 200);
return ThumbnailSettings.getInstance(getComponentId(), width, height);
}
public Boolean isSubscriberUser() {
Boolean subscriber = null;
if (!getUserDetail().isAccessGuest()) {
SubscriptionService subscriptionService = SubscriptionServiceProvider.getSubscribeService();
subscriber = subscriptionService
.existsSubscription(new ComponentSubscription(getUserId(), getComponentId()));
}
return subscriber;
}
public String manageSubscriptions() {
SubscriptionContext subscriptionContext = getSubscriptionContext();
subscriptionContext.initialize(ComponentSubscriptionResource.from(getComponentId()));
return subscriptionContext.getDestinationUrl();
}
public void submitNewsOnHomepage(String id) {
getQuickInfoService().submitNewsOnHomepage(new ResourceReference(id, getComponentId()),
getUserId());
}
public void addNewsToBeCopied(String newsId) throws ClipboardException {
final News news = getNews(newsId, false);
NewsSelection newsSelect = new NewsSelection(news);
addClipboardSelection(newsSelect);
}
public boolean isNewsToPaste() throws SilverpeasException {
return getClipboardSelectedObjects().stream()
.filter(Objects::nonNull)
.anyMatch(o -> o.isDataFlavorSupported(NewsSelection.NewsFlavor));
}
public void paste() throws ClipboardException {
getClipboardSelectedObjects().stream()
.filter(Objects::nonNull)
.filter(o -> o.isDataFlavorSupported(NewsSelection.NewsFlavor))
.map(o -> {
try {
return o.getTransferData(NewsSelection.NewsFlavor);
} catch (Exception e) {
throw new SilverpeasRuntimeException(e);
}
})
.filter(News.class::isInstance)
.map(News.class::cast)
.forEach(n -> {
final PasteDetail pasteDetail = new PasteDetail(getUserId());
pasteDetail.setToComponentId(getComponentId());
getQuickInfoService().copyNews(n, pasteDetail);
});
clipboardPasteDone();
}
/**
* Classify the info letter publication on the PdC. If the position parameter is filled it
* means that the content must be unclassified.
* @param positions the string JSON positions.
*/
private List<PdcPosition> getPositionsFromJSON(String positions) {
return ofNullable(positions)
.filter(StringUtil::isDefined)
.map(p -> {
try {
return PdcClassificationEntity.fromJSON(positions);
} catch (DecodingException e) {
SilverLogger.getLogger(this).error(e);
}
return null;
})
.filter(Objects::nonNull)
.orElseGet(PdcClassificationEntity::undefinedClassification)
.getPdcPositions();
}
private void addVisit(News news) {
if (!news.isDraft()) {
getStatisticService().addStat(getUserId(), news);
}
}
private QuickInfoService getQuickInfoService() {
return QuickInfoServiceProvider.getQuickInfoService();
}
private StatisticService getStatisticService() {
return StatisticService.get();
}
public News getPrevious() {
return currentList.get(currentIndex.getPreviousIndex());
}
public News getNext() {
return currentList.get(currentIndex.getNextIndex());
}
public ListIndex getIndex() {
return currentIndex;
}
private void processIndex(News news) {
loadNewsLists(false);
if (!news.isDraft() && news.isVisible()) {
currentList = mainList;
} else if (news.isDraft()) {
currentList = draftList;
} else if (news.isNotYetVisible()) {
currentList = notYetVisibleList;
} else if (news.isNoMoreVisible()) {
currentList = noMoreVisibleList;
}
if (currentList != null) {
currentIndex.setCurrentIndex(currentList.indexOf(news));
currentIndex.setNbItems(currentList.size());
}
}
/**
* Loads the handled lists.
* @param reload if true forces to load again the lists, if false loading the lists if it has not
* been yet done.
*/
private void loadNewsLists(boolean reload) {
if (mainList == null || reload) {
if (getHighestSilverpeasUserRole().isGreaterThanOrEquals(SilverpeasRole.PUBLISHER)) {
getQuickInfos();
} else {
getVisibleQuickInfos();
}
}
}
}