Optimizations for the models processing content items.

pull/20/head
Jens Pelzetter 2022-01-29 13:16:39 +01:00
parent 65d53e9992
commit 661c70073f
8 changed files with 497 additions and 225 deletions

View File

@ -4,21 +4,33 @@
<div class="container"> <div class="container">
<div class="bg-light mb-4 rounded-3"> <div class="bg-light mb-4 rounded-3">
<div class="container-fluid py-5"> <div class="container-fluid py-5">
<h1 class="display-5 fw-bold"> <#if CmsPagesCategorizedItemModel.itemAvailable>
LibreCMS <h1 class="display-5 fw-bold">
</h1> ${CmsPagesCategorizedItemModel.title}
<p> </h1>
No index item has been defined. <p>
</p> ${CmsPagesCategorizedItemModel.description}
<a class="btn btn-primary btn-lg" </p>
href="https://www.libreccm.org" <a class="btn btn-primary btn-lg"
type="button"> href="#">
Find out more Find out more
</a> </a>
<#else>
<h1 class="display-5 fw-bold">
LibreCMS
</h1>
<p>
No index item has been defined.
</p>
<a class="btn btn-primary btn-lg"
href="https://www.libreccm.org">
Find out more
</a>
</#if>
</div> </div>
</div> </div>
<#if CmsPagesCategorizedItemModel.contentItem??> <#if CmsPagesCategorizedItemModel.itemAvailable>
Category has an index item Category has an index item
<#else> <#else>
Category has no index item Category has no index item
@ -34,6 +46,39 @@
<dt>view</dt> <dt>view</dt>
<dd>${view!""}</dd> <dd>${view!""}</dd>
</dl> </dl>
<h2>From <code>ArticleModel</code></h2>
<dl>
<dt>Title</dt>
<dd>${CmsPagesArticleModel.title}</dd>
<dt>Description</dt>
<dd>${CmsPagesArticleModel.description}</dd>
<dt>Text</dt>
<dd>${CmsPagesArticleModel.text}</dd>
</dl>
<h2>Item List</h2>
<p>Item List size: ${CmsPagesItemListModel.listSize}</p>
<ul>
<#list CmsPagesItemListModel.items as item>
<li>
<dl>
<dt>UUID</dt>
<dd>${item.uuid}</dd>
<dt>displayName</dt>
<dd>${item.displayName}</dd>
<dt>Name</dt>
<dd>${item.name}</dd>
<dt>Title</dt>
<dd>${item.title}</dd>
<dt>description</dt>
<dd>${item.description}</dd>
<dt>Type</dt>
<dd>${item.type}</dd>
</dl>
</li>
</#list>
</ul>
</div> </div>
</@main.librecms> </@main.librecms>

View File

@ -28,8 +28,6 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/** /**
* Model for getting the special properties of an {@link Article}. For general * Model for getting the special properties of an {@link Article}. For general
@ -39,7 +37,7 @@ import javax.ws.rs.core.Response;
*/ */
@RequestScoped @RequestScoped
@Named("CmsPagesArticleModel") @Named("CmsPagesArticleModel")
public class ArticleModel { public class ArticleModel implements ProcessesContentItem {
@Inject @Inject
private ContentItemModel contentItemModel; private ContentItemModel contentItemModel;
@ -47,41 +45,46 @@ public class ArticleModel {
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
private boolean initialized;
private String title; private String title;
private String description; private String description;
private String text; private String text;
public ArticleModel() {
initialized = false;
}
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getTitle() { public String getTitle() {
if (title == null) { contentItemModel.init();
init();
}
return title; return title;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getDescription() { public String getDescription() {
if (description == null) { contentItemModel.init();
init();
}
return description; return description;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getText() { public String getText() {
if (text == null) { contentItemModel.init();
init();
}
return text; return text;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void init() { @Override
final ContentItem contentItem = contentItemModel public void init(final ContentItem contentItem) {
.retrieveContentItem() if (initialized) {
.orElse(null); return;
}
if (contentItem instanceof Article) { if (contentItem instanceof Article) {
final Article article = (Article) contentItem; final Article article = (Article) contentItem;
@ -97,15 +100,8 @@ public class ArticleModel {
.ofNullable(article.getText()) .ofNullable(article.getText())
.map(globalizationHelper::getValueFromLocalizedString) .map(globalizationHelper::getValueFromLocalizedString)
.orElse(""); .orElse("");
} else { }
throw new WebApplicationException( initialized = true;
"Current content item is not an article.",
Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Current content item is not an article.")
.build()
);
}
} }
} }

View File

@ -18,24 +18,24 @@
*/ */
package org.librecms.pages.models; package org.librecms.pages.models;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.CategoryRepository; import org.libreccm.categorization.CategoryRepository;
import org.libreccm.core.CcmObject;
import org.libreccm.l10n.GlobalizationHelper; import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.contentsection.ContentItem; import org.librecms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItemVersion; import org.librecms.contentsection.ContentItemVersion;
import org.librecms.pages.PagesController;
import org.librecms.pages.PagesRouter; import org.librecms.pages.PagesRouter;
import org.librecms.pages.PagesService; import org.librecms.pages.PagesService;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.Optional; import java.util.Optional;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.transaction.Transactional; import javax.transaction.Transactional;
/** /**
@ -50,162 +50,348 @@ import javax.transaction.Transactional;
@Named("CmsPagesCategorizedItemModel") @Named("CmsPagesCategorizedItemModel")
public class ContentItemModel { public class ContentItemModel {
private static final Logger LOGGER = LogManager.getLogger( /**
ContentItemModel.class * Category repository used to retrieve the current category.
); */
@Inject
private CategoryManager categoryManager;
@Inject @Inject
private CategoryRepository categoryRepository; private CategoryRepository categoryRepository;
/**
* Category model used to get the curretn category.
*/
@Inject @Inject
private CategoryModel categoryModel; private CategoryModel categoryModel;
@Inject /**
private EntityManager entityManager; * Utility for globalization stuff.
*/
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
/**
* Provides some utility methods.
*/
@Inject @Inject
private PagesService pagesService; private PagesService pagesService;
/**
* All instances of implementations of the {@link ProcessesContentItem}
* interface.
*/
@Inject
private Instance<ProcessesContentItem> contentItemProcessingModels;
/**
* A {@link DateTimeFormatter} instance for converting dates and times to an
* ISO 8601 date/time string.
*/
private final DateTimeFormatter dateTimeFormatter private final DateTimeFormatter dateTimeFormatter
= DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault()); = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault());
/**
* The name of the item to be shown.
*/
private String itemName; private String itemName;
/**
* The version of the item to be shown.
*/
private ContentItemVersion itemVersion; private ContentItemVersion itemVersion;
// private Optional<ContentItem> contentItem; /**
* Data of the current content item, already prepared for retrieving the
* data from a MVC template. If no item with the {@link #itemName} provided
* by the {@link PagesController} is present in the category, the
* {@link Optional} will be empty.
*/
private Optional<ContentItemModelData> contentItem; private Optional<ContentItemModelData> contentItem;
/**
* Gets the item name provided by the {@link PagesController}.
*
* @return The item name provided by the {@link PagesController}.
*/
public String getItemName() { public String getItemName() {
return itemName; return itemName;
} }
/**
* Used by the {@link PagesController} to set the name of the requested
* content item. May be used by other controllers.
*
* @param itemName The name of the requested content item.
*/
public void setItemName(final String itemName) { public void setItemName(final String itemName) {
this.itemName = itemName; this.itemName = itemName;
} }
/**
* The requested version of the content item.
*
* @return The requested version of the content item.
*/
public ContentItemVersion getItemVersion() { public ContentItemVersion getItemVersion() {
return itemVersion; return itemVersion;
} }
/**
* Used by controllers, for example the {@link PagesController} to set the
* requested version of the content item.
*
* @param itemVersion The requested version of the content item.
*/
public void setItemVersion(final ContentItemVersion itemVersion) { public void setItemVersion(final ContentItemVersion itemVersion) {
this.itemVersion = itemVersion; this.itemVersion = itemVersion;
} }
/**
* A convient getter for checking if a content item is available from a
* template.
*
* @return {@code true} if an item is available, {@code false} if not.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public boolean isItemAvailable() { public boolean isItemAvailable() {
return getOrRetrieveContentItem().isPresent(); init();
} return contentItem.isPresent();
@Transactional(Transactional.TxType.REQUIRED)
public long getObjectId() {
return getOrRetrieveContentItem()
.map(ContentItemModelData::getObjectId)
.orElse(0L);
} }
/**
* Gets the {@code objectId)} (see {@link CcmObject#objectId} of the current
* item.
*
* @return The {@code objectId} of the current item if there is an item or
* {@code -1L} if there is no item.
*/
@Transactional(Transactional.TxType.REQUIRED)
public long getObjectId() {
init();
return contentItem
.map(ContentItemModelData::getObjectId)
.orElse(-1L);
}
/**
* Get the UUID of the current item (see {@link CcmObject#uuid}.
*
* @return The UUID of the current item if there is an item, an empty string
* if there is not item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getUuid() { public String getUuid() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getUuid) .map(ContentItemModelData::getUuid)
.orElse(""); .orElse("");
} }
/**
* Gets the display name of the current item (see
* {@link CcmObject#displayName}.
*
* @return The display name of the current item, or an empty string if there
* is not current item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getDisplayName() { public String getDisplayName() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getDisplayName) .map(ContentItemModelData::getDisplayName)
.orElse(""); .orElse("");
} }
/**
* Gets the item UUID of the current item (see {@link ContentItem#itemUuid}.
*
* @return The item UUID of the current item, or an empty string if the is
* no current item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getItemUuid() { public String getItemUuid() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getItemUuid) .map(ContentItemModelData::getItemUuid)
.orElse(""); .orElse("");
} }
/**
* Gets the name of the current item {@link ContentItem#name}) for the
* current language (see {@link GlobalizationHelper#getNegotiatedLocale()}).
*
* @return The name of the the current item for the current language, or an
* empty string if there is no item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getName() { public String getName() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getName) .map(ContentItemModelData::getName)
.orElse(""); .orElse("");
} }
/**
* Gets the title of the current item (see {@link ContentItem#title} for the
* current language (see {@link GlobalizationHelper#getNegotiatedLocale()}).
*
* @return The title of the current item for the current language, or an
* empty string if there is no item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getTitle() { public String getTitle() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getTitle) .map(ContentItemModelData::getTitle)
.orElse(""); .orElse("");
} }
/**
* Gets the description of the current item (see
* {@link ContentItem#description} for the current language (see
* {@link GlobalizationHelper#getNegotiatedLocale()}).
*
* @return The description of the current item for the current language, or
* an empty string if there is no item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getDescription() { public String getDescription() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getDescription) .map(ContentItemModelData::getDescription)
.orElse(""); .orElse("");
} }
/**
* Gets the version of the current item (see {@link ContentItem#version}.
*
* @return The version of the current version as a string, or an empty
* string if there is not current item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getVersion() { public String getVersion() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getVersion) .map(ContentItemModelData::getVersion)
.orElse(""); .orElse("");
} }
/**
* Gets the creation date/time of the current item (see
* {@link ContentItem#creationDate}.
*
* @return The creation date/time of the current item as ISO 8601 date/time
* string, or an empty string if there is no item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getCreationDate() { public String getCreationDate() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getCreationDate) .map(ContentItemModelData::getCreationDate)
.orElse(""); .orElse("");
} }
/**
* Gets the date/time of the last modification of the current item (see
* {@link ContentItem#lastModified}.
*
* @return The date/time of the modification of the current item as ISO 8601
* date/time string, or an empty string of there is not current
* item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getLastModified() { public String getLastModified() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getLastModified) .map(ContentItemModelData::getLastModified)
.orElse(""); .orElse("");
} }
/**
* Gets the user name of the user that created the item.
*
* @return The user name of the user that created the item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getCreationUser() { public String getCreationUser() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getCreationUser) .map(ContentItemModelData::getCreationUser)
.orElse(""); .orElse("");
} }
/**
* Gets the user name of the user that did the last modifications on the
* item.
*
* @return The user name of the user that did the last modifications on the
* item.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getLastModifyingUserName() { public String getLastModifyingUserName() {
return getOrRetrieveContentItem() init();
return contentItem
.map(ContentItemModelData::getLastModifyingUserName) .map(ContentItemModelData::getLastModifyingUserName)
.orElse(""); .orElse("");
} }
// public List<AttachmentList> getAttachments() { /**
// throw new UnsupportedOperationException("Not implemented yet."); * Initialize the this model and all models implementing
// } * {@link ProcessesContentItem#}.
// private Optional<ContentItem> getOrRetrieveContentItem() { *
// if (contentItem == null) { * If this model has not been initalizied already this method retrieves the
// retrieveContentItem(); * current content item using {@link #retrieveContentItem()}, and
// } * initializes {@link #contentItem} with an instance of
// return contentItem; * {@link ContentItemModelData} build from the item using
// } * {@link #buildModelData(org.librecms.contentsection.ContentItem)}.
*
* After that, the method will invoke the implementation of
* {@link ProcessesContentItem#init(org.librecms.contentsection.ContentItem)}
* for all available implemetentations of {@link ProcessesContentItem} (see
* {@link #contentItemProcessingModels}).
*
* Models implementing SHOULD call this method in their getters before
* returning a value. There is not need to wrap the invocation of this
* method in an {@code if} statement, the method will only run its logic if
* the item has not been initialized.
*
* If there is no current item, the method will do nothing.
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
private Optional<ContentItemModelData> getOrRetrieveContentItem() { public void init() {
if (contentItem == null) { if (contentItem != null) {
contentItem = retrieveContentItem().map(this::buildModelData); return;
}
final Optional<ContentItem> item = retrieveContentItem();
contentItem = item.map(this::buildModelData);
final Iterator<ProcessesContentItem> iterator
= contentItemProcessingModels.iterator();
while (iterator.hasNext()) {
final ProcessesContentItem model = iterator.next();
model.init(item.orElse(null));
} }
return contentItem;
} }
/**
* Helper method for retrieving the current content item. If
* {@link #itemName} is {@code null} or {@code index} the method will return
* if the index item of the current category. If the category has not index
* item, an empty {@link Optional} is returned.
*
* If {@link #itemName} is <b>not</b> {@code null} or {@code index}, the
* method will try to find a content item associated to the category where
* {@link ContentItem#name} matches {@link #itemName}.
*
* @return The current content item if any, or an empyty {@link Optional}.
*
* @see PagesService#findIndexItem(org.libreccm.categorization.Category,
* org.librecms.contentsection.ContentItemVersion)
* @see
* PagesService#findCategorizedItem(org.libreccm.categorization.Category,
* java.lang.String, org.librecms.contentsection.ContentItemVersion)
*/
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected Optional<ContentItem> retrieveContentItem() { private Optional<ContentItem> retrieveContentItem() {
final Optional<ContentItem> item; final Optional<ContentItem> item;
if (itemName == null || "index".equals(itemName)) { if (itemName == null || "index".equals(itemName)) {
item = pagesService.findIndexItem( item = pagesService.findIndexItem(
@ -245,6 +431,15 @@ public class ContentItemModel {
return item; return item;
} }
/**
* Helper method for building an instance {@link ContentItemModelData} for a
* {@link ContentItem}.
*
* @param item The source content item.
*
* @return An instance of {@link ContentItemModelData} for the provided
* content item.
*/
private ContentItemModelData buildModelData(final ContentItem item) { private ContentItemModelData buildModelData(final ContentItem item) {
final ContentItemModelData data = new ContentItemModelData(); final ContentItemModelData data = new ContentItemModelData();
data.setObjectId(item.getObjectId()); data.setObjectId(item.getObjectId());
@ -292,6 +487,12 @@ public class ContentItemModel {
return data; return data;
} }
/**
* Encapsulates the data of content item provided by this model. To avoid to
* have to many fields in this model class we use an internal class to
* encapsulate the data. The getters of the model class return the values
* from an instance of this class.
*/
private class ContentItemModelData { private class ContentItemModelData {
private long objectId; private long objectId;

View File

@ -43,7 +43,7 @@ import javax.transaction.Transactional;
*/ */
@RequestScoped @RequestScoped
@Named("CmsPagesContentItemTypeModel") @Named("CmsPagesContentItemTypeModel")
public class ContentItemTypeModel { public class ContentItemTypeModel implements ProcessesContentItem {
@Inject @Inject
private ContentItemModel contentItemModel; private ContentItemModel contentItemModel;
@ -54,48 +54,55 @@ public class ContentItemTypeModel {
private Optional<ContentItemTypeModelData> contentType; private Optional<ContentItemTypeModelData> contentType;
public long getContentTypeId() { public long getContentTypeId() {
return getOrRetrieveContentType() contentItemModel.init();
return contentType
.map(ContentItemTypeModelData::getTypeId) .map(ContentItemTypeModelData::getTypeId)
.orElse(0L); .orElse(0L);
} }
public String getUuid() { public String getUuid() {
return getOrRetrieveContentType() contentItemModel.init();
return contentType
.map(ContentItemTypeModelData::getUuid) .map(ContentItemTypeModelData::getUuid)
.orElse(""); .orElse("");
} }
public String getDisplayName() { public String getDisplayName() {
return getOrRetrieveContentType() contentItemModel.init();
return contentType
.map(ContentItemTypeModelData::getDisplayName) .map(ContentItemTypeModelData::getDisplayName)
.orElse(""); .orElse("");
} }
public String getLabel() { public String getLabel() {
return getOrRetrieveContentType() contentItemModel.init();
return contentType
.map(ContentItemTypeModelData::getLabel) .map(ContentItemTypeModelData::getLabel)
.orElse(""); .orElse("");
} }
public String getDescription() { public String getDescription() {
return getOrRetrieveContentType() contentItemModel.init();
return contentType
.map(ContentItemTypeModelData::getDescription) .map(ContentItemTypeModelData::getDescription)
.orElse(""); .orElse("");
} }
private Optional<ContentItemTypeModelData> getOrRetrieveContentType() {
if (contentType == null) {
retrieveContentType();
}
return contentType;
}
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
private void retrieveContentType() { public void init(final ContentItem contentItem) {
contentType = contentItemModel.retrieveContentItem() if (contentType != null) {
return;
}
contentType = Optional
.ofNullable(contentItem)
.map(ContentItem::getContentType) .map(ContentItem::getContentType)
.map(this::buildModelData); .map(this::buildModelData);
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)

View File

@ -31,8 +31,6 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/** /**
* *
@ -40,7 +38,7 @@ import javax.ws.rs.core.Response;
*/ */
@RequestScoped @RequestScoped
@Named("CmsPagesEventModel") @Named("CmsPagesEventModel")
public class EventModel { public class EventModel implements ProcessesContentItem {
@Inject @Inject
private ContentItemModel contentItemModel; private ContentItemModel contentItemModel;
@ -48,6 +46,8 @@ public class EventModel {
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
private boolean initialized;
private final DateTimeFormatter isoDateTimeFormatter; private final DateTimeFormatter isoDateTimeFormatter;
private String title; private String title;
@ -65,77 +65,67 @@ public class EventModel {
private String eventType; private String eventType;
public EventModel() { public EventModel() {
initialized = false;
isoDateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME isoDateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME
.withZone(ZoneId.systemDefault()); .withZone(ZoneId.systemDefault());
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getTitle() { public String getTitle() {
if (title == null) { contentItemModel.init();
init();
}
return title; return title;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getText() { public String getText() {
if (text == null) { contentItemModel.init();
init();
}
return text; return text;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getStartDateTime() { public String getStartDateTime() {
if (startDateTime == null) { contentItemModel.init();
init();
}
return startDateTime; return startDateTime;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getEndDateTime() { public String getEndDateTime() {
if (endDateTime == null) { contentItemModel.init();
init();
}
return endDateTime; return endDateTime;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getEventDate() { public String getEventDate() {
if (eventDate == null) { contentItemModel.init();
init();
}
return eventDate; return eventDate;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getLocation() { public String getLocation() {
if (location == null) { contentItemModel.init();
init();
}
return location; return location;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getEventType() { public String getEventType() {
if (eventType == null) { contentItemModel.init();
init();
}
return eventDate; return eventType;
} }
@Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void init() { public void init(final ContentItem contentItem) {
final ContentItem contentItem = contentItemModel if (initialized) {
.retrieveContentItem() return;
.orElse(null); }
if (contentItem instanceof Event) { if (contentItem instanceof Event) {
final Event event = (Event) contentItem; final Event event = (Event) contentItem;
@ -169,15 +159,9 @@ public class EventModel {
.ofNullable(event.getEventType()) .ofNullable(event.getEventType())
.map(globalizationHelper::getValueFromLocalizedString) .map(globalizationHelper::getValueFromLocalizedString)
.orElse(""); .orElse("");
} else {
throw new WebApplicationException(
"Current content item is not an event",
Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Current content item is not an event.")
.build()
);
} }
initialized = true;
} }
} }

View File

@ -41,7 +41,7 @@ import javax.ws.rs.core.Response;
*/ */
@RequestScoped @RequestScoped
@Named("CmsPagesMultiPartArticleModel") @Named("CmsPagesMultiPartArticleModel")
public class MultiPartArticleModel { public class MultiPartArticleModel implements ProcessesContentItem {
@Inject @Inject
private ContentItemModel contentItemModel; private ContentItemModel contentItemModel;
@ -49,6 +49,8 @@ public class MultiPartArticleModel {
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
private boolean initialized;
@Inject @Inject
private PageUrlModel pageUrlModel; private PageUrlModel pageUrlModel;
@ -61,69 +63,62 @@ public class MultiPartArticleModel {
private String currentSectionTitle; private String currentSectionTitle;
private String currentSectionText; private String currentSectionText;
private List<MultiPartArticleSectionModel> sections; private List<MultiPartArticleSectionModel> sections;
public MultiPartArticleModel() {
initialized = false;
}
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getTitle() { public String getTitle() {
if (title == null) { contentItemModel.init();
init();
}
return title; return title;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getSummary() { public String getSummary() {
if (summary == null) { contentItemModel.init();
return null;
}
return summary; return summary;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<String> getSectionTitles() { public List<String> getSectionTitles() {
if (sectionTitles == null) { contentItemModel.init();
init();
}
return Collections.unmodifiableList(sectionTitles); return Collections.unmodifiableList(sectionTitles);
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getCurrentSectionTitle() { public String getCurrentSectionTitle() {
if (currentSectionTitle == null) { contentItemModel.init();
init();
}
return currentSectionTitle; return currentSectionTitle;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getCurrentSectionText() { public String getCurrentSectionText() {
if (currentSectionText == null) { contentItemModel.init();
init();
}
return currentSectionText; return currentSectionText;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<MultiPartArticleSectionModel> getSections() { public List<MultiPartArticleSectionModel> getSections() {
if (sections == null) { contentItemModel.init();
init();
}
return Collections.unmodifiableList(sections); return Collections.unmodifiableList(sections);
} }
@Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void init() { public void init(final ContentItem contentItem) {
final ContentItem contentItem = contentItemModel if (initialized) {
.retrieveContentItem() return;
.orElse(null); }
if (contentItem instanceof MultiPartArticle) { if (contentItem instanceof MultiPartArticle) {
final MultiPartArticle mpa = (MultiPartArticle) contentItem; final MultiPartArticle mpa = (MultiPartArticle) contentItem;
@ -165,26 +160,20 @@ public class MultiPartArticleModel {
.ofNullable(mpa.getSections().get(currentSection).getTitle()) .ofNullable(mpa.getSections().get(currentSection).getTitle())
.map(globalizationHelper::getValueFromLocalizedString) .map(globalizationHelper::getValueFromLocalizedString)
.orElse(""); .orElse("");
currentSectionText = Optional currentSectionText = Optional
.ofNullable(mpa.getSections().get(currentSection).getText()) .ofNullable(mpa.getSections().get(currentSection).getText())
.map(globalizationHelper::getValueFromLocalizedString) .map(globalizationHelper::getValueFromLocalizedString)
.orElse(""); .orElse("");
sections = mpa sections = mpa
.getSections() .getSections()
.stream() .stream()
.map(this::buildSectionModel) .map(this::buildSectionModel)
.collect(Collectors.toList()); .collect(Collectors.toList());
} else {
throw new WebApplicationException(
"Current content item is not an MultiPartArticle",
Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Current content item is not an MultiPartArticle.")
.build()
);
} }
initialized = true;
} }
private int readCurrentSection() { private int readCurrentSection() {

View File

@ -22,7 +22,6 @@ import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.contentsection.ContentItem; import org.librecms.contentsection.ContentItem;
import org.librecms.contenttypes.News; import org.librecms.contenttypes.News;
import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
@ -32,8 +31,6 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/** /**
* *
@ -41,79 +38,79 @@ import javax.ws.rs.core.Response;
*/ */
@RequestScoped @RequestScoped
@Named("CmsPagesNewsModel") @Named("CmsPagesNewsModel")
public class NewsModel { public class NewsModel implements ProcessesContentItem {
@Inject @Inject
private ContentItemModel contentItemModel; private ContentItemModel contentItemModel;
@Inject @Inject
private GlobalizationHelper globalizationHelper; private GlobalizationHelper globalizationHelper;
private boolean initialized;
private final DateTimeFormatter isoDateTimeFormatter; private final DateTimeFormatter isoDateTimeFormatter;
private String title; private String title;
private String description; private String description;
private String text; private String text;
private String releaseDateTime; private String releaseDateTime;
private boolean homepage; private boolean homepage;
public NewsModel() { public NewsModel() {
initialized = false;
isoDateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME isoDateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME
.withZone(ZoneId.systemDefault()); .withZone(ZoneId.systemDefault());
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getTitle() { public String getTitle() {
if (title == null) { contentItemModel.init();
init();
}
return title; return title;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getDescription() { public String getDescription() {
if (description == null) { contentItemModel.init();
init();
}
return description; return description;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getText() { public String getText() {
if (text == null) { contentItemModel.init();
init();
}
return text; return text;
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public String getReleaseDateTime() { public String getReleaseDateTime() {
if (releaseDateTime == null) { contentItemModel.init();
init();
}
return releaseDateTime; return releaseDateTime;
} }
@Transactional(Transactional.TxType.REQUIRED)
public boolean getHomepage() {
return homepage;
}
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
protected void init() { public boolean getHomepage() {
final ContentItem contentItem = contentItemModel contentItemModel.init();
.retrieveContentItem()
.orElse(null); return homepage;
}
@Override
@Transactional(Transactional.TxType.REQUIRED)
public void init(final ContentItem contentItem) {
if (initialized) {
return;
}
if (contentItem instanceof News) { if (contentItem instanceof News) {
final News news = (News) contentItem; final News news = (News) contentItem;
title = Optional title = Optional
.ofNullable(news.getTitle()) .ofNullable(news.getTitle())
.map(globalizationHelper::getValueFromLocalizedString) .map(globalizationHelper::getValueFromLocalizedString)
@ -132,15 +129,9 @@ public class NewsModel {
.map(isoDateTimeFormatter::format) .map(isoDateTimeFormatter::format)
.orElse(""); .orElse("");
homepage = news.isHomepage(); homepage = news.isHomepage();
} else {
throw new WebApplicationException(
"Current content item is not a news item.",
Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Current content item is not a new item.")
.build()
);
} }
initialized = true;
} }
} }

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2022 LibreCCM Foundation.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.pages.models;
import org.hibernate.LazyInitializationException;
import org.librecms.contentsection.ContentItem;
import javax.transaction.Transactional;
/**
* Models that provide data for the current content item (either the index item
* of a category or a selected content item) SHOULD implement this interface.
*
* This {@link ContentItemModel} collects all instances of classes implementing
* this interface and calls their implemententation of the
* {@link #init(org.librecms.contentsection.ContentItem)} method.
*
* For an example of an implementation please look at
* {@link ContentItemTypeModel}, {@link ArticleModel} or
* at {@link MultiPartArticleModel} for a more complex model.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public interface ProcessesContentItem {
/**
* Initalizes a model providing the data of a content item for MVC
* templates.
*
* If the implementing model only provides data for specific sub classes of
* {@link ContentItem} the implementation MUST check the correct type using
* the {@code instanceof} operator. If the item is not a the correct type a
* implementation MUST gracefully ignore the item.
*
* To avoid a {@link LazyInitializationException} implementations SHOULD be
* annotated with {@link Transactional} and the
* {@link Transactional.TxType#REQUIRED}.
*
* @param item The item.
*/
void init(ContentItem item);
}