diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index cc2b32cd589c..eeb019a09084 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -17,14 +17,14 @@
1.12.488
3.10.1
2.2.0
- 1.14.12
+ 1.14.15
1.17
1.70
- 3.0.0
+ 4.0.0
4.33.0
0.14.1
- 2.16.1
- 2.22.1
+ 2.17.2
+ 2.27
22.3.3
@@ -1132,14 +1132,6 @@
-
-
-
- javax.ws.rs
- javax.ws.rs-api
- 2.0.1
-
-
io.swagger.core.v3
@@ -1382,7 +1374,11 @@
pom
import
-
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ ${jersey.version}
+
jakarta.inject
jakarta.inject-api
@@ -1466,6 +1462,13 @@
junit
junit
4.13.2
+
+
+ org.hamcrest
+ hamcrest-core
+
+
+
@@ -1488,29 +1491,23 @@
awaitility
${awaitility.version}
-
- org.awaitility
- awaitility-proxy
- ${awaitility.version}
-
+
org.mockito
mockito-core
- 5.11.0
+ 5.12.0
org.mockito
mockito-junit-jupiter
- 5.11.0
+ 5.12.0
test
-
org.hamcrest
- hamcrest-all
- 1.3
-
+ hamcrest
+ 2.1
+ test
diff --git a/dotCMS/pom.xml b/dotCMS/pom.xml
index 57c5de5155bd..ce61a614fa49 100644
--- a/dotCMS/pom.xml
+++ b/dotCMS/pom.xml
@@ -831,6 +831,11 @@
org.yaml
snakeyaml
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ compile
+
com.fasterxml.jackson.datatype
jackson-datatype-guava
@@ -975,13 +980,6 @@
-
-
-
- javax.ws.rs
- javax.ws.rs-api
- compile
-
@@ -1223,6 +1221,11 @@
org.glassfish.jersey.media
jersey-media-sse
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+
+
org.hamcrest
- hamcrest-all
+ hamcrest
compile
diff --git a/dotCMS/src/main/java/com/dotcms/annotations/AllowNulls.java b/dotCMS/src/main/java/com/dotcms/annotations/AllowNulls.java
new file mode 100644
index 000000000000..9fe3407f4c27
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/annotations/AllowNulls.java
@@ -0,0 +1,16 @@
+package com.dotcms.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used in Immutables to allow null values in the generated classes collections
+ * See https://immutables.github.io/immutable.html#nulls-in-collection
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AllowNulls {
+
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotcms/annotations/Nullable.java b/dotCMS/src/main/java/com/dotcms/annotations/Nullable.java
new file mode 100644
index 000000000000..4f7c34f32b37
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/annotations/Nullable.java
@@ -0,0 +1,16 @@
+package com.dotcms.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used in Immutables to allow null values in the generated classes collections
+ * See https://immutables.github.io/immutable.html#nulls-in-collection
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {
+
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotcms/annotations/SkipNulls.java b/dotCMS/src/main/java/com/dotcms/annotations/SkipNulls.java
new file mode 100644
index 000000000000..cb5382b78f5a
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotcms/annotations/SkipNulls.java
@@ -0,0 +1,15 @@
+package com.dotcms.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+/**
+ * This annotation is used in Immutables to skip null values in the generated classes collections
+ * See https://immutables.github.io/immutable.html#nulls-in-collection
+ */
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SkipNulls {
+
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/PortletResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/PortletResource.java
index 276123a1376b..af83c7fc7973 100644
--- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/PortletResource.java
+++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/portlet/PortletResource.java
@@ -110,16 +110,21 @@ public final Response saveNew(@Context final HttpServletRequest request,
final Portlet contentPortlet = portletApi.findPortlet("content");
- final Map initValues = new HashMap<>(contentPortlet.getInitParams());
- initValues.put("name", formData.portletName);
- initValues.put("baseTypes", formData.baseTypes);
- initValues.put("contentTypes", formData.contentTypes);
- initValues.put(DATA_VIEW_MODE_KEY, formData.dataViewMode);
+ final DotPortlet newPortlet = DotPortlet.builder()
+ .portletId(portletId)
+ .portletClass(contentPortlet.getPortletClass())
- final Portlet newPortlet = APILocator.getPortletAPI()
- .savePortlet(new DotPortlet(portletId, contentPortlet.getPortletClass(), initValues), initData.getUser());
+ .putInitParam("name", formData.portletName)
+ .putInitParam("baseTypes", formData.baseTypes)
+ .putInitParam("contentTypes", formData.contentTypes)
+ .putInitParam(DATA_VIEW_MODE_KEY, formData.dataViewMode)
+ .build();
- return Response.ok(new ResponseEntityView<>(Map.of(JSON_RESPONSE_PORTLET_ATTR, newPortlet.getPortletId()))).build();
+
+ final Portlet savedPortlet = APILocator.getPortletAPI()
+ .savePortlet(newPortlet.toPortlet(), initData.getUser());
+
+ return Response.ok(new ResponseEntityView<>(Map.of(JSON_RESPONSE_PORTLET_ATTR, savedPortlet.getPortletId()))).build();
} catch (final Exception e) {
Logger.error(this, String.format("An error occurred when saving new Portlet with ID " +
"'%s': %s", portletId, ExceptionUtil.getErrorMessage(e)), e);
@@ -158,18 +163,24 @@ public final Response updatePortlet(@Context final HttpServletRequest request, f
}
final Portlet contentPortlet = portletApi.findPortlet("content");
- final Map initValues = new HashMap<>(contentPortlet.getInitParams());
- initValues.put("name", formData.portletName);
- initValues.put("baseTypes", formData.baseTypes);
- initValues.put("contentTypes", formData.contentTypes);
- initValues.put(DATA_VIEW_MODE_KEY, formData.dataViewMode);
+ final DotPortlet updatedPortlet = DotPortlet.builder()
+ .portletId(portletId)
+ .portletClass(contentPortlet.getPortletClass())
+ .putInitParam("name", formData.portletName)
+ .putInitParam("baseTypes", formData.baseTypes)
+ .putInitParam("contentTypes", formData.contentTypes)
+ .putInitParam(DATA_VIEW_MODE_KEY, formData.dataViewMode)
+ .build();
+
final Portlet newPortlet = APILocator.getPortletAPI()
- .savePortlet(new DotPortlet(portletId, contentPortlet.getPortletClass(), initValues), initData.getUser());
+ .savePortlet(updatedPortlet.toPortlet(), initData.getUser());
return Response.ok(new ResponseEntityView<>(Map.of(JSON_RESPONSE_PORTLET_ATTR, newPortlet.getPortletId()))).build();
} catch (Exception e) {
+ Logger.error(this, String.format("An error occurred when updating Portlet with ID " +
+ "'%s': %s", formData.portletId, ExceptionUtil.getErrorMessage(e)), e);
response = ResponseUtil.mapExceptionResponse(e);
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/business/portal/DotPortlet.java b/dotCMS/src/main/java/com/dotmarketing/business/portal/DotPortlet.java
index dc6e76d0c250..02f6edd8c20e 100644
--- a/dotCMS/src/main/java/com/dotmarketing/business/portal/DotPortlet.java
+++ b/dotCMS/src/main/java/com/dotmarketing/business/portal/DotPortlet.java
@@ -1,193 +1,140 @@
package com.dotmarketing.business.portal;
-import com.dotmarketing.util.UtilMethods;
+import com.dotcms.annotations.SkipNulls;
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.annotation.*;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.liferay.portal.model.Portlet;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Style.*;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import java.util.ArrayList;
-import java.util.HashMap;
+import javax.xml.bind.annotation.*;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
- * This class implements the JAXB mapping for the portlet definitions in dotCMS.
- *
- * @author Jose Castro
- * @since Jun 17th, 2024
+ * This interface represents a DotPortlet, which is an immutable representation of a Portlet.
+ * It uses the Immutables framework for generating the implementation class and builder.
+ * Jackson annotations are used for JSON and XML serialization/deserialization.
*/
+@Value.Immutable
+@Value.Style(
+ overshadowImplementation = true,
+ typeBuilder = "*Builder",
+ depluralize = true,
+ visibility = ImplementationVisibility.PACKAGE,
+ builderVisibility = BuilderVisibility.PACKAGE,
+ implementationNestedInBuilder = true,
+ deepImmutablesDetection = true,
+ jdkOnly = true
+)
+@JsonSerialize(as = DotPortlet.class)
+@JsonDeserialize(builder = DotPortlet.Builder.class)
+@JsonIgnoreProperties(ignoreUnknown = false)
@XmlRootElement(name = "portlet")
-public class DotPortlet extends Portlet {
-
- @XmlElement(name = "init-param")
- private List initParams;
+@XmlAccessorType(XmlAccessType.FIELD)
+public interface DotPortlet extends XMLSerializable {
/**
- * Default class constructor, required by JAXB.
+ * Returns the portlet ID.
+ * @return The portlet ID as a String.
*/
- @SuppressWarnings("unused")
- public DotPortlet() {
- super(null, null, new HashMap<>());
- }
+ @XmlElement(name = "portlet-name")
+ @JsonProperty("portlet-name")
+ String getPortletId();
/**
- * Creates an instance of this class based on the original/legacy definition or a dotCMS
- * Portlet.
- *
- * @param portlet The original {@link Portlet} object.
+ * Returns the fully qualified class name of the portlet.
+ * @return The portlet class name as a String.
*/
- public DotPortlet(final Portlet portlet) {
- this(portlet.getPortletId(), portlet.getPortletClass(), portlet.getInitParams());
- }
+ @XmlElement(name = "portlet-class")
+ @JsonProperty("portlet-class")
+ String getPortletClass();
/**
- * Creates an instance of this class.
- *
- * @param portletId The ID of the portlet.
- * @param portletClass The base class that handles the existing legacy Liferay/Struts logic to
- * render the portlet.
- * @param initParams The initialization and/or configuration parameters for the portlet.
+ * Returns a list of initialization parameters for the portlet.
+ * This method converts the internal Map representation to a List of InitParam objects.
+ * @return A List of InitParam objects.
*/
- public DotPortlet(String portletId, String portletClass, Map initParams) {
- super(portletId, portletClass, initParams);
- this.initParams = new ArrayList<>();
- for (Map.Entry entry : initParams.entrySet()) {
- this.initParams.add(new InitParam(entry.getKey(), entry.getValue()));
- }
- }
-
- @Override
- @XmlElement(name = "portlet-name")
- public String getPortletId() {
- return portletId;
- }
-
- @Override
- public void setPortletId(final String portletId) {
- super.portletId = portletId;
- }
-
- @Override
- @XmlElement(name = "portlet-class")
- public String getPortletClass() {
- return portletClass;
- }
-
- @Override
- public void setPortletClass(final String portletClass) {
- super.portletClass = portletClass;
- }
-
- @SuppressWarnings("unused")
@XmlElement(name = "init-param")
- public List getInitParamList() {
- return this.initParams;
+ @JsonGetter("init-param")
+ @JacksonXmlElementWrapper(useWrapping = false, localName = "init-params")
+ default List getInitParams() {
+ return initParams().entrySet().stream()
+ .map(e -> InitParam.of(e.getKey(), e.getValue()))
+ .collect(Collectors.toList());
}
/**
- * Returns the initialization parameters of the portlet as a map. By default, the JAXB mapping
- * will read them as a list of attributes, but, for backward compatibility, we need to return
- * them as mapped values.
- *
- * @return A map with the initialization parameters of the portlet.
+ * Returns the internal Map representation of initialization parameters.
+ * This method is ignored in JSON/XML serialization.
+ * @return A Map of initialization parameter names to values.
*/
- @Override
- public Map getInitParams() {
- if (UtilMethods.isSet(this.initParams)) {
- for (final InitParam initParam : this.initParams) {
- super.initParams.put(initParam.getName(), initParam.getValue());
- }
- }
- return super.getInitParams();
- }
+ @JsonIgnore
+ @XmlTransient
+ @SkipNulls
+ Map initParams();
- @Override
- public String toString() {
- return "DotPortlet{" +
- " portletId='" + this.portletId + '\'' +
- ", portletClass='" + this.portletClass + '\'' +
- ", portletSource='" + this.portletSource + '\'' +
- ", initParamsAsList=" + this.initParams +
- '}';
+ /**
+ * Creates a DotPortlet instance from a Portlet object.
+ * @param portlet The Portlet object to convert.
+ * @return A new DotPortlet instance.
+ */
+ static DotPortlet from(Portlet portlet) {
+ return DotPortlet.builder()
+ .portletId(portlet.getPortletId())
+ .portletClass(portlet.getPortletClass())
+ .initParams(portlet.getInitParams())
+ .build();
}
/**
- * Represents the initialization parameter tags in a Portlet definition. It can contain any
- * value/pair required by the Portlet, there's no technical limitation.
+ * Converts this DotPortlet instance to a Portlet object.
+ * @return A new Portlet instance.
*/
- @XmlAccessorType(XmlAccessType.FIELD)
- public static class InitParam {
-
- @XmlElement(name = "name")
- private String name;
-
- @XmlElement(name = "value")
- private String value;
-
- /**
- * Default class constructor, required by JAXB.
- */
- @SuppressWarnings("unused")
- public InitParam() {
- }
-
- /**
- * Creates an instance of an initialization parameter.
- *
- * @param name The name of the parameter.
- * @param value The value of the parameter.
- */
- public InitParam(final String name, final String value) {
- this.name = name;
- this.value = value;
- }
-
- /**
- * Returns the name of the parameter.
- *
- * @return The name of the parameter.
- */
- public String getName() {
- return this.name;
- }
+ default Portlet toPortlet() {
+ return new Portlet(getPortletId(), getPortletClass(), initParams());
+ }
- /**
- * Sets the name of the parameter.
- *
- * @param name The name of the parameter.
- */
- public void setName(String name) {
- this.name = name;
- }
+ /**
+ * Builder class for DotPortlet.
+ * This class is generated by the Immutables framework and extended here for custom functionality.
+ */
+ class Builder extends DotPortletBuilder implements XMLEnabledBuilder {
/**
- * Returns the value of the parameter.
- *
- * @return The value of the parameter.
+ * Adds a list of InitParam objects to the builder.
+ * This method converts the List to the internal Map representation.
+ * @param initParams A List of InitParam objects.
+ * @return This Builder instance for method chaining.
*/
- public String getValue() {
- return this.value;
+ @XmlElement(name = "init-param")
+ @JsonSetter("init-param")
+ @JacksonXmlElementWrapper(useWrapping = false, localName = "init-params")
+ public Builder addAllInitParams(List initParams) {
+ putAllInitParams(initParams.stream()
+ .collect(Collectors.toMap(InitParam::getName, InitParam::getValue)));
+ return this;
}
/**
- * Sets the value of the parameter.
- *
- * @param value The value of the parameter.
+ * Creates a Builder instance from a Portlet object.
+ * @param portlet The Portlet object to convert.
+ * @return This Builder instance for method chaining.
*/
- public void setValue(String value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return "InitParam{" +
- "name='" + this.name + '\'' +
- ", value='" + this.value + '\'' +
- '}';
+ public Builder from(Portlet portlet) {
+ return portletId(portlet.getPortletId())
+ .portletClass(portlet.getPortletClass())
+ .initParams(portlet.getInitParams());
}
+ }
- }
-
-}
+ /**
+ * Creates a new Builder instance for DotPortlet.
+ * @return A new Builder instance.
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotmarketing/business/portal/InitParamTuple.java b/dotCMS/src/main/java/com/dotmarketing/business/portal/InitParamTuple.java
new file mode 100644
index 000000000000..dc583d288efb
--- /dev/null
+++ b/dotCMS/src/main/java/com/dotmarketing/business/portal/InitParamTuple.java
@@ -0,0 +1,36 @@
+package com.dotmarketing.business.portal;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Style.ImplementationVisibility;
+
+@Value.Immutable
+@Value.Style(
+ typeAbstract = "*Tuple",
+ typeImmutable = "*",
+ visibility = ImplementationVisibility.PUBLIC,
+ allParameters = true,
+ defaults = @Value.Immutable(builder = false, copy = false)
+)
+@JsonSerialize(as = InitParam.class)
+@JsonDeserialize(as = InitParam.class)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@XmlRootElement(name = "init-param")
+@XmlAccessorType(XmlAccessType.FIELD)
+public interface InitParamTuple {
+
+ @XmlElement(name = "name")
+ @JsonProperty("name")
+ String getName();
+
+ @XmlElement(name = "value")
+ @JsonProperty("value")
+ String getValue();
+}
\ No newline at end of file
diff --git a/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactory.java b/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactory.java
index 1aef58fb9155..77ab1dc7f56a 100644
--- a/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactory.java
+++ b/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactory.java
@@ -99,7 +99,7 @@ public interface PortletFactory {
Map xmlToPortlets(InputStream[] xmlFiles) throws SystemException;
/**
- * Transforms the specified XML string into a DotPortlet object. This allows the API to read
+ * Transforms the specified XML string into a Portlet object. This allows the API to read
* portlets that are defined in configuration files or the database.
*
* @param xml The XML string to transform.
@@ -110,6 +110,6 @@ public interface PortletFactory {
* @throws JAXBException An error occurred when mapping the XML String to a {@link DotPortlet}
* object.
*/
- Optional xmlToPortlet(final String xml) throws IOException, JAXBException;
+ Optional xmlToPortlet(final String xml) throws IOException, JAXBException;
}
diff --git a/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactoryImpl.java b/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactoryImpl.java
index c70dc4ea3ec2..aebf2ebfc0e6 100644
--- a/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactoryImpl.java
+++ b/dotCMS/src/main/java/com/dotmarketing/business/portal/PortletFactoryImpl.java
@@ -1,390 +1,301 @@
-package com.dotmarketing.business.portal;
-
-import com.dotcms.api.system.event.Payload;
-import com.dotcms.api.system.event.SystemEventType;
-import com.dotcms.business.CloseDBIfOpened;
-import com.dotcms.business.WrapInTransaction;
-import com.dotcms.exception.ExceptionUtil;
-import com.dotmarketing.business.APILocator;
-import com.dotmarketing.business.CacheLocator;
-import com.dotmarketing.common.db.DotConnect;
-import com.dotmarketing.exception.DotDataException;
-import com.dotmarketing.exception.DotRuntimeException;
-import com.dotmarketing.util.Logger;
-import com.dotmarketing.util.UtilMethods;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-import com.liferay.portal.SystemException;
-import com.liferay.portal.ejb.PrincipalBean;
-import com.liferay.portal.model.Portlet;
-import com.liferay.util.FileUtil;
-import io.vavr.control.Try;
-
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.UnmarshalException;
-import javax.xml.bind.Unmarshaller;
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Implementation class for the {@link PortletFactory} interface. This class uses JAXB to map XML
- * files into Java objects.
- * By default, portlet definitions come from three sources:
- *
- * - The {@code "/WEB-INF/portlet.xml"} file.
- * - The {@code "/WEB-INF/portlet-ext.xml"} file.
- * - The database, where custom Portlets are stored.
- *
- *
- *
- * @author Erick Gonzalez
- * @since Apr 24th, 2019
- */
-public class PortletFactoryImpl extends PrincipalBean implements PortletFactory {
-
- private final String[] systemXmlFiles;
-
- private static final Map, JaxbContext> jaxbContexts;
-
- // Creates the JAXB contexts for the {@link DotPortlet} and {@link PortletList} classes. They're
- // used by JAXB to map the contents of the configuration XML files into such classes.
- static {
- try {
- jaxbContexts = Map.of(
- DotPortlet.class, new JaxbContext(DotPortlet.class),
- PortletList.class, new JaxbContext(PortletList.class));
- } catch (final JAXBException e) {
- throw new DotRuntimeException(String.format("FATAL - Failed to create JAXB contexts: " +
- "%s", ExceptionUtil.getErrorMessage(e)), e);
- }
- }
-
- /**
- * Creates an instance of this Factory using the specified XML files as sources of Portlet
- * definitions.
- *
- * @param systemXmlFiles The paths to the XML files to read.
- */
- public PortletFactoryImpl(final String[] systemXmlFiles) {
- this.systemXmlFiles = systemXmlFiles;
- }
-
- /**
- * Default class constructor. It sets the default paths to the system XML files.
- */
- public PortletFactoryImpl() {
- this(new String[] {FileUtil.getRealPath("/WEB-INF/portlet.xml"), FileUtil.getRealPath("/WEB-INF/portlet-ext.xml")});
- }
-
- /**
- * Reads the contents of the specified XML file, and maps the Portlet definitions from it into
- * Portlet objects.
- *
- * @param pathToXmlFile The path to the XML file to read.
- *
- * @return A map with the {@link Portlet} definitions.
- *
- * @throws IOException An error occurred while reading the XML file.
- * @throws JAXBException An error occurred while mapping the XML file into Java objects.
- */
- private Map xmlToPortlets(final String pathToXmlFile) throws IOException, JAXBException {
- if (UtilMethods.isNotSet(pathToXmlFile)) {
- return new HashMap<>();
- }
- Logger.debug(this, "Loading Portlets from file: " + pathToXmlFile);
- final InputStream stream = new FileInputStream(pathToXmlFile);
- return xmlToPortlets(stream);
- }
-
- /**
- * Takes the Input Stream of an XML file and extracts the Portlet definitions from it into
- * Portlet objects.
- *
- * @param fileStream The {@link InputStream} of the XML file to read.
- *
- * @return A map with the {@link Portlet} definitions.
- *
- * @throws JAXBException An error occurred while mapping the XML file into Java objects.
- */
- private Map xmlToPortlets(final InputStream fileStream) throws JAXBException {
- if (null == fileStream) {
- return new HashMap<>();
- }
- final Map portlets = new HashMap<>();
- final PortletList portletList = (PortletList) jaxbContexts.get(PortletList.class).unmarshall(fileStream);
- int counter = 1;
- if (UtilMethods.isSet(portletList) && UtilMethods.isSet(portletList.getPortlets())) {
- for (final DotPortlet portlet : portletList.getPortlets()) {
- portlets.put(portlet.getPortletId(), portlet);
- Logger.debug(this, String.format("%d. Loading portlet ID '%s'", counter, portlet.getPortletId()));
- counter++;
- }
- }
- return portlets;
- }
-
- @Override
- @VisibleForTesting
- public Optional xmlToPortlet(final String xml) throws JAXBException {
- final InputStream stream = new ByteArrayInputStream(xml.getBytes(UTF_8));
- final DotPortlet portlet = (DotPortlet) jaxbContexts.get(DotPortlet.class).unmarshall(stream);
- if (null == portlet.getPortletId() || null == portlet.getPortletClass()) {
- return Optional.empty();
- }
- return Optional.of(portlet);
- }
-
- @Override
- public Map xmlToPortlets(final String[] xmlFiles) throws SystemException {
- final Map portlets = new HashMap<>();
- for (final String xmlFile : xmlFiles) {
- try {
- portlets.putAll(xmlToPortlets(xmlFile));
- } catch (final Exception e) {
- throw new SystemException(String.format("An error occurred when loading portlets from XML file " +
- "'%s': %s", xmlFile, ExceptionUtil.getErrorMessage(e)), e);
- }
- }
- return portlets;
- }
-
- @Override
- public Map xmlToPortlets(final InputStream[] xmlFiles) throws SystemException {
- final Map portlets = new HashMap<>();
- for (final InputStream xmlFile : xmlFiles) {
- try {
- portlets.putAll(xmlToPortlets(xmlFile));
- } catch (final Exception e) {
- throw new SystemException(String.format("An error occurred when loading portlets from XML stream: " +
- "%s", ExceptionUtil.getErrorMessage(e)), e);
- }
- }
- return portlets;
- }
-
- @WrapInTransaction
- @Override
- public void deletePortlet(final String portletId) throws DotDataException {
- final DotConnect db = new DotConnect();
- db.setSQL("delete from portletpreferences where portletid=?").addParam(portletId).loadResult();
- db.setSQL("delete from portlet where portletid=?").addParam(portletId).loadResult();
- db.setSQL("delete from cms_layouts_portlets where portlet_id=?" ).addParam(portletId).loadResult();
- CacheLocator.getPortletCache().clearCache();
- CacheLocator.getLayoutCache().clearCache();
- APILocator.getSystemEventsAPI().pushAsync(SystemEventType.UPDATE_PORTLET_LAYOUTS, new Payload());
- }
-
- @Override
- public Portlet findById(final String portletId) {
- return getPortletMap().get(portletId);
- }
-
- /**
- * Loads the dotCMS portlets from both the system XML files and the database.
- *
- * @return A map with the portlets loaded from the system XML files and the database.
- */
- private Map loadSystemPortlets() {
- final Map portlets = new HashMap<>();
-
- try {
- portlets.putAll(xmlToPortlets(this.systemXmlFiles));
- } catch (final Exception e) {
- Logger.error(this, e.getMessage(), e);
- }
-
- final List portletList = Try.of(this::findAllDb).getOrElse(Lists.newArrayList());
- for (final Portlet portlet : portletList) {
- portlets.put(portlet.getPortletId(), portlet);
- }
- return portlets;
- }
-
- /**
- * Returns the available Portlets in the current dotCMS instance. If the Portlets are not loaded
- * in cache yet, it takes care of doing so.
- *
- * @return A map with the available Portlets.
- */
- private Map getPortletMap() {
- final PortletCache cache = new PortletCache();
- Map portletsMap = cache.getAllPortlets();
- if (portletsMap.isEmpty()) {
- synchronized (PortletCache.class) {
- portletsMap = cache.getAllPortlets();
- if (portletsMap.isEmpty()) {
- final Map portletMap = this.loadSystemPortlets();
- portletsMap.putAll(portletMap);
- cache.putAllPortlets(portletsMap);
- }
- }
- }
- return portletsMap;
- }
-
- @Override
- public Collection getPortlets() throws SystemException {
- return getPortletMap().values();
- }
-
- /**
- * Loads all Portlets directly from the database. Keep in mind that Users can create their own
- * portlets for displaying different contents of a given type; e.g., Videos, PDFs, etc.
- *
- * @return A list with all the Portlets stored in the database.
- *
- * @throws DotDataException An error occurred while loading the Portlets from the database.
- */
- @CloseDBIfOpened
- public List findAllDb() throws DotDataException {
- final DotConnect db = new DotConnect();
- db.setSQL("select * from portlet where companyid=?").addParam("dotcms.org");
- final List portlets = new ArrayList<>();
- final List