/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.portlet.dao.jpa;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Transient;
import javax.persistence.Version;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.portlet.marketplace.IMarketplaceRating;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionId;
import org.apereo.portal.portlet.om.IPortletDefinitionParameter;
import org.apereo.portal.portlet.om.IPortletDescriptorKey;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletPreference;
import org.apereo.portal.portlet.om.IPortletType;
import org.apereo.portal.portlet.om.PortletLifecycleState;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.annotations.Type;
import org.springframework.util.StringUtils;
/**
*/
@Entity
@Table(name = "UP_PORTLET_DEF")
@SequenceGenerator(
name = "UP_PORTLET_DEF_GEN",
sequenceName = "UP_PORTLET_DEF_SEQ",
allocationSize = 5
)
@TableGenerator(name = "UP_PORTLET_DEF_GEN", pkColumnValue = "UP_PORTLET_DEF", allocationSize = 5)
@NaturalIdCache(region = "org.apereo.portal.portlet.dao.jpa.PortletDefinitionImpl-NaturalId")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class PortletDefinitionImpl implements IPortletDefinition {
//Properties are final to stop changes in code, hibernate overrides the final via reflection to set their values
@Id
@GeneratedValue(generator = "UP_PORTLET_DEF_GEN")
@Column(name = "PORTLET_DEF_ID")
private final long internalPortletDefinitionId;
@Transient private IPortletDefinitionId portletDefinitionId = null;
@SuppressWarnings("unused")
@Version
@Column(name = "ENTITY_VERSION")
private final long entityVersion;
//Hidden reference to the child portlet entities, used to allow cascading deletes where when a portlet definition is deleted all associated entities are also deleted
//MUST BE LAZY FETCH, this set should never actually be populated at runtime or performance will be TERRIBLE
@SuppressWarnings("unused")
@OneToMany(
mappedBy = "portletDefinition",
targetEntity = PortletEntityImpl.class,
cascade = {CascadeType.ALL},
fetch = FetchType.LAZY,
orphanRemoval = true
)
private Set<IPortletEntity> portletEntities = null;
@OneToOne(
targetEntity = PortletPreferencesImpl.class,
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER,
orphanRemoval = true
)
@JoinColumn(name = "PORTLET_PREFS_ID", nullable = false)
@Fetch(FetchMode.JOIN)
private final PortletPreferencesImpl portletPreferences;
/**
* Name is used for admin tools, but will typically not be presented to end users. It allows for
* situations where you need to define multiple similar portlets, all sharing a title. but still
* provides a way to distinguish between the portlets in admin tools.
*/
@Column(name = "PORTLET_NAME", length = 128, nullable = false, unique = true)
private String name;
@NaturalId(mutable = true)
@Column(name = "PORTLET_FNAME", length = 255, nullable = false)
@Type(type = "fname")
private String fname;
@Column(name = "PORTLET_TITLE", length = 128, nullable = false)
@Index(name = "IDX_PORTLET_DEF__TITLE")
private String title;
@ManyToOne(targetEntity = PortletTypeImpl.class, optional = false)
@JoinColumn(name = "PORTLET_TYPE_ID", nullable = false)
private IPortletType portletType;
@Column(name = "PORTLET_DESC", length = 255)
private String description;
@Column(name = "AVG_RATING")
private Double rating;
@Column(name = "AVG_RATING_USER_COUNT")
private Long usersRated;
@Column(name = "PORTLET_TIMEOUT", nullable = false)
private int timeout = 20000; //Default to a reasonable value
@Column(name = "ACTION_TIMEOUT")
private Integer actionTimeout = null;
@Column(name = "EVENT_TIMEOUT")
private Integer eventTimeout = null;
@Column(name = "RENDER_TIMEOUT")
private Integer renderTimeout = null;
@Column(name = "RESOURCE_TIMEOUT")
private Integer resourceTimeout = null;
//TODO link to User object once it is JPA managed
@Column(name = "PORTLET_PUBL_ID")
private int publisherId = -1;
//TODO link to User object once it is JPA managed
@Column(name = "PORTLET_APVL_ID")
private int approverId = -1;
//TODO link to User object once it is JPA managed
@Column(name = "PORTLET_EXP_ID")
private int expirerId = -1;
@Column(name = "PORTLET_PUBL_DT")
private Date publishDate = null;
@Column(name = "PORTLET_APVL_DT")
private Date approvalDate = null;
@Column(name = "PORTLET_EXP_DT")
private Date expirationDate = null;
@OneToMany(
targetEntity = PortletDefinitionParameterImpl.class,
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
@JoinColumn(name = "PORLTET_DEF_ID", nullable = false)
@MapKey(name = "name")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Fetch(FetchMode.JOIN)
private Map<String, IPortletDefinitionParameter> parameters =
new LinkedHashMap<String, IPortletDefinitionParameter>();
@ElementCollection(fetch = FetchType.EAGER, targetClass = PortletLocalizationData.class)
@JoinTable(name = "UP_PORTLET_DEF_MDATA", joinColumns = @JoinColumn(name = "PORTLET_ID"))
@MapKeyColumn(name = "LOCALE", length = 64, nullable = false)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Fetch(FetchMode.JOIN)
private final Map<String, PortletLocalizationData> localizations =
new LinkedHashMap<String, PortletLocalizationData>();
@Embedded private PortletDescriptorKeyImpl portletDescriptorKey;
/** Used to initialize fields after persistence actions. */
@PostLoad
@PostPersist
@PostUpdate
@PostRemove
private void init() {
if (this.internalPortletDefinitionId != -1
&& (this.portletDefinitionId == null
|| this.portletDefinitionId.getLongId()
!= this.internalPortletDefinitionId)) {
this.portletDefinitionId =
PortletDefinitionIdImpl.create(this.internalPortletDefinitionId);
}
}
/** Used by the ORM layer to create instances of the object. */
@SuppressWarnings("unused")
private PortletDefinitionImpl() {
this.internalPortletDefinitionId = -1;
this.entityVersion = -1;
this.portletPreferences = null;
}
public PortletDefinitionImpl(
IPortletType portletType,
String fname,
String name,
String title,
String applicationId,
String portletName,
boolean isFramework) {
Validate.notNull(portletType);
Validate.notNull(name);
Validate.notNull(fname);
Validate.notNull(title);
if (!isFramework) {
Validate.notNull(applicationId);
}
Validate.notNull(portletName);
this.internalPortletDefinitionId = -1;
this.entityVersion = -1;
this.portletPreferences = new PortletPreferencesImpl();
this.portletType = portletType;
this.name = name;
this.fname = fname;
this.title = title;
this.portletDescriptorKey = new PortletDescriptorKeyImpl();
this.portletDescriptorKey.setWebAppName(applicationId);
this.portletDescriptorKey.setPortletName(portletName);
this.portletDescriptorKey.setFrameworkPortlet(isFramework);
}
public PortletDefinitionImpl(
IPortletType portletType,
String fname,
String name,
String title,
String applicationId,
String portletName,
boolean isFramework,
IPortletDefinitionId Id) {
Validate.notNull(portletType);
Validate.notNull(name);
Validate.notNull(fname);
Validate.notNull(title);
if (!isFramework) {
Validate.notNull(applicationId);
}
Validate.notNull(portletName);
this.internalPortletDefinitionId = Id.getLongId();
this.entityVersion = -1;
this.portletPreferences = new PortletPreferencesImpl();
this.portletType = portletType;
this.name = name;
this.fname = fname;
this.title = title;
this.portletDescriptorKey = new PortletDescriptorKeyImpl();
this.portletDescriptorKey.setWebAppName(applicationId);
this.portletDescriptorKey.setPortletName(portletName);
this.portletDescriptorKey.setFrameworkPortlet(isFramework);
}
//** APIs for import/export support **//
@Override
public String getDataId() {
return this.getFName();
}
@Override
public String getDataTitle() {
return this.getName();
}
@Override
public String getDataDescription() {
return this.getDescription();
}
//** APIs for portlet definitions **//
/** @return the rating */
@Override
public Double getRating() {
return rating;
}
/**
* @param rating the rating to set. Must be within marketplaceRating range (inclusive). Can not
* be null.
* @throws IllegalArgumentException
*/
@Override
public void setRating(Double rating) {
Validate.notNull(rating, "Rating cannot be null. Maybe you meant 0?");
if (rating > IMarketplaceRating.MAX_RATING || rating < IMarketplaceRating.MIN_RATING) {
throw new IllegalArgumentException();
}
this.rating = rating;
}
/** @return the count of users that rated this portlet. Will not return null */
@Override
public Long getUsersRated() {
return usersRated == null ? 0 : usersRated;
}
/** @param usersRated - Number of users that rated this portlet. */
@Override
public void setUsersRated(Long usersRated) {
Validate.isTrue(usersRated > -1L, "Number of Users that rated shouldn't be under zero");
this.usersRated = usersRated;
}
/* (non-Javadoc)
* @see org.apereo.portal.om.portlet.IPortletDefinition#getPortletDefinitionId()
*/
@Override
public IPortletDefinitionId getPortletDefinitionId() {
init();
return this.portletDefinitionId;
}
/* (non-Javadoc)
* @see org.apereo.portal.portlet.om.IPortletEntity#getPortletPreferences()
*/
@Override
public List<IPortletPreference> getPortletPreferences() {
return portletPreferences.getPortletPreferences();
}
/* (non-Javadoc)
* @see org.apereo.portal.portlet.om.IPortletEntity#setPortletPreferences(java.util.List)
*/
@Override
public boolean setPortletPreferences(List<IPortletPreference> portletPreferences) {
return this.portletPreferences.setPortletPreferences(portletPreferences);
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getFName() {
return fname;
}
@Override
public void setFName(String fname) {
this.fname = fname;
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
@Override
public String getDescription() {
return description;
}
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public int getTimeout() {
return timeout;
}
@Override
public void setTimeout(int timeout) {
this.timeout = timeout;
}
@Override
public Integer getActionTimeout() {
return actionTimeout;
}
@Override
public void setActionTimeout(Integer actionTimeout) {
this.actionTimeout = actionTimeout;
}
@Override
public Integer getEventTimeout() {
return eventTimeout;
}
@Override
public void setEventTimeout(Integer eventTimeout) {
this.eventTimeout = eventTimeout;
}
@Override
public Integer getRenderTimeout() {
return renderTimeout;
}
@Override
public void setRenderTimeout(Integer renderTimeout) {
this.renderTimeout = renderTimeout;
}
@Override
public Integer getResourceTimeout() {
return resourceTimeout;
}
@Override
public void setResourceTimeout(Integer resourceTimeout) {
this.resourceTimeout = resourceTimeout;
}
@Override
public int getPublisherId() {
return publisherId;
}
@Override
public void setPublisherId(int publisherId) {
this.publisherId = publisherId;
}
@Override
public int getApproverId() {
return approverId;
}
@Override
public void setApproverId(int approverId) {
this.approverId = approverId;
}
@Override
public int getExpirerId() {
return expirerId;
}
@Override
public void setExpirerId(int expirerId) {
this.expirerId = expirerId;
}
@Override
public Date getPublishDate() {
return publishDate;
}
@Override
public void setPublishDate(Date publishDate) {
this.publishDate = publishDate;
}
@Override
public Date getApprovalDate() {
return approvalDate;
}
@Override
public void setApprovalDate(Date approvalDate) {
this.approvalDate = approvalDate;
}
@Override
public Date getExpirationDate() {
return expirationDate;
}
@Override
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
@Override
public IPortletType getType() {
return this.portletType;
}
@Override
public String getName(String locale) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData != null && localeData.getName() != null) {
return localeData.getName();
}
return name;
}
@Override
public String getDescription(String locale) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData != null && localeData.getDescription() != null) {
return localeData.getDescription();
}
return description;
}
@Override
public String getTitle(String locale) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData != null && localeData.getTitle() != null) {
return localeData.getTitle();
}
return title;
}
@Override
public String getAlternativeMaximizedLink() {
final IPortletDefinitionParameter alternativeMaximizedLinkParameter =
getParameter(ALT_MAX_LINK_PARAM);
if (null != alternativeMaximizedLinkParameter) {
final String alternativeMaximizedLink = alternativeMaximizedLinkParameter.getValue();
if (StringUtils.hasText(alternativeMaximizedLink)) {
return alternativeMaximizedLink;
}
}
return null;
}
@Override
public String getTarget() {
final IPortletDefinitionParameter targetParameter = getParameter(TARGET_PARAM);
if (null != targetParameter) {
final String target = targetParameter.getValue();
if (StringUtils.hasText(target)) {
return target;
}
}
return null;
}
@Override
public void setType(IPortletType portletType) {
this.portletType = portletType;
}
@Override
public IPortletDescriptorKey getPortletDescriptorKey() {
return this.portletDescriptorKey;
}
@Override
public void addLocalizedDescription(String locale, String chanDesc) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData == null) {
localeData = new PortletLocalizationData();
}
localeData.setDescription(chanDesc);
localizations.put(locale, localeData);
}
@Override
public void addLocalizedName(String locale, String chanName) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData == null) {
localeData = new PortletLocalizationData();
}
localeData.setName(chanName);
localizations.put(locale, localeData);
}
@Override
public void addLocalizedTitle(String locale, String chanTitle) {
PortletLocalizationData localeData = localizations.get(locale);
if (localeData == null) {
localeData = new PortletLocalizationData();
}
localeData.setTitle(chanTitle);
localizations.put(locale, localeData);
}
@Override
public Set<IPortletDefinitionParameter> getParameters() {
return Collections.unmodifiableSet(
new LinkedHashSet<IPortletDefinitionParameter>(parameters.values()));
}
@Override
public void setParameters(Set<IPortletDefinitionParameter> newParameters) {
if (newParameters == null) {
this.parameters = new LinkedHashMap<String, IPortletDefinitionParameter>();
} else if (this.parameters == null) {
this.parameters = new LinkedHashMap<String, IPortletDefinitionParameter>();
for (final IPortletDefinitionParameter parameter : newParameters) {
this.parameters.put(parameter.getName(), parameter);
}
} else {
//Build map of existing parameters for tracking which parameters have been removed
final Map<String, IPortletDefinitionParameter> oldPreferences =
new LinkedHashMap<String, IPortletDefinitionParameter>(this.parameters);
for (final IPortletDefinitionParameter parameter : newParameters) {
final String name = parameter.getName();
//Remove the existing parameter from the map since it is supposed to be persisted
final IPortletDefinitionParameter existingParameter = oldPreferences.remove(name);
if (existingParameter == null) {
//New parameter, add it to the list
this.parameters.put(name, parameter);
} else {
//Existing parameter, update the fields
existingParameter.setDescription(parameter.getDescription());
existingParameter.setValue(parameter.getValue());
this.parameters.put(name, existingParameter);
}
}
//Remove old parameters
this.parameters.keySet().removeAll(oldPreferences.keySet());
}
}
@Override
public IPortletDefinitionParameter getParameter(String key) {
return this.parameters.get(key);
}
@Override
public Map<String, IPortletDefinitionParameter> getParametersAsUnmodifiableMap() {
return Collections.unmodifiableMap(this.parameters);
}
@Override
public void removeParameter(IPortletDefinitionParameter parameter) {
this.parameters.remove(parameter.getName());
}
@Override
public void removeParameter(String name) {
this.parameters.remove(name);
}
@Override
public void addParameter(IPortletDefinitionParameter parameter) {
final String name = parameter.getName();
final IPortletDefinitionParameter existingParameter = this.parameters.get(name);
if (existingParameter != null) {
existingParameter.setDescription(parameter.getDescription());
existingParameter.setValue(parameter.getValue());
} else {
this.parameters.put(name, parameter);
}
}
@Override
public void addParameter(String name, String value) {
final IPortletDefinitionParameter existingParameter = this.parameters.get(name);
if (existingParameter != null) {
existingParameter.setValue(value);
} else {
this.parameters.put(name, new PortletDefinitionParameterImpl(name, value));
}
}
@Override
public EntityIdentifier getEntityIdentifier() {
return new EntityIdentifier(
String.valueOf(this.portletDefinitionId.getStringId()), IPortletDefinition.class);
}
@Override
public PortletLifecycleState getLifecycleState() {
final Date now = new Date();
if (parameters.containsKey(PortletLifecycleState.MAINTENANCE_MODE_PARAMETER_NAME)) {
return PortletLifecycleState.MAINTENANCE;
} else if (expirationDate != null && expirationDate.before(now)) {
return PortletLifecycleState.EXPIRED;
} else if (publishDate != null && publishDate.before(now)) {
return PortletLifecycleState.PUBLISHED;
} else if (approvalDate != null && approvalDate.before(now)) {
return PortletLifecycleState.APPROVED;
} else {
return PortletLifecycleState.CREATED;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.fname == null) ? 0 : this.fname.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!IPortletDefinition.class.isAssignableFrom(obj.getClass())) return false;
final IPortletDefinition other = (IPortletDefinition) obj;
if (this.fname == null) {
if (other.getFName() != null) return false;
} else if (!this.fname.equals(other.getFName())) return false;
return true;
}
@Override
public String toString() {
ToStringBuilder toStringBuilder =
new ToStringBuilder(this)
.append("definitionId", portletDefinitionId)
.append("fname", fname)
.append("descriptorKey", portletDescriptorKey)
.append("type", portletType);
return toStringBuilder.toString();
}
}