Portlet Security
1. Portlet Architecture: Under the Hood
Section titled “1. Portlet Architecture: Under the Hood”OSGi Service Dynamics
Section titled “OSGi Service Dynamics”classDiagram
class PortletPipeline {
+doFilter()
+doDispatch()
}
class PortletContainer {
+render()
+processAction()
}
class DispatcherPortlet {
+handleRequest()
}
PortletPipeline --> PortletContainer
PortletContainer --> DispatcherPortlet
DispatcherPortlet --> Portlet
Key Insights:
- PortletPipeline: Handles request preprocessing (filters, security)
- PortletContainer: Manages state and lifecycle
- DispatcherPortlet: Bridges HTTP requests to portlet instances
Threading Model
Section titled “Threading Model”sequenceDiagram
participant Browser
participant Portal
participant Portlet
Browser->>Portal: HTTP Request
Portal->>Portlet: Thread Pool (size=configurable)
Portlet-->>Portal: Render Response
Portal-->>Browser: HTML
Configuration (portal-ext.properties):
portlet.executor.core.pool.size=20portlet.executor.max.pool.size=100portlet.executor.keep.alive.time=600002. Advanced Render Mechanics
Section titled “2. Advanced Render Mechanics”Dynamic Resource Serving
Section titled “Dynamic Resource Serving”@ServeResource( resourceID = "dynamicData", contentType = "application/json", characterEncoding = "UTF-8")public void serveDynamicData(ResourceRequest request, ResourceResponse response) throws IOException {
try (OutputStream out = response.getPortletOutputStream()) { JsonGenerator generator = Json.createGenerator(out); generator.writeStartObject() .write("timestamp", System.currentTimeMillis()) .write("data", fetchLiveData()) .writeEnd(); generator.flush(); }}Performance Optimization:
- Use
@Header(name="Cache-Control", value="max-age=300")for cacheable resources - Implement
ETagsupport for conditional requests
Fragment Caching
Section titled “Fragment Caching”<portlet> <fragment-cache>true</fragment-cache> <cache-scope>public</cache-scope> <cache-timeout>600</cache-timeout> <!-- 10 minutes --></portlet>Programmatic Control:
CachePortletResponseUtil.setCacheHeaders( resourceResponse, CacheControl.maxAge(10, TimeUnit.MINUTES));3. Portlet State Management
Section titled “3. Portlet State Management”Portlet Session Strategies
Section titled “Portlet Session Strategies”graph TD
A[Session Type] --> B[PORTLET_SCOPE]
A --> C[APPLICATION_SCOPE]
B --> D[Visible only to this portlet]
C --> E[Shared across all portlets]
Best Practices:
- Use
PortletSessionUtil.getPortletSession()for proper scoping - For clustered environments:
session.setAttribute("data", data, PortletSession.APPLICATION_SCOPE);
Distributed State with Redis
Section titled “Distributed State with Redis”@Component(service = PortletStateManager.class)public class RedisPortletStateManager {
@Reference private RedisConnection _redis;
public void saveState(String portletId, Serializable state) { _redis.execute(connection -> { connection.stringCommands().set( ("portlet:" + portletId).getBytes(), serialize(state) ); return null; }); }}4. Portlet Security Implementation Patterns
Section titled “4. Portlet Security Implementation Patterns”Fine-Grained Permission Checks
Section titled “Fine-Grained Permission Checks”public void render(RenderRequest request, RenderResponse response) { if (!PortletPermissionUtil.contains( PermissionThreadLocal.getPermissionChecker(), PortletKeys.PREFS_OWNER_TYPE_LAYOUT, PortletPermission.ACTION_CONFIGURATION)) { throw new PrincipalException(); } // Proceed with admin rendering}JWT Authentication Flow
Section titled “JWT Authentication Flow”sequenceDiagram
participant Client
participant Portlet
participant AuthService
Client->>Portlet: Request with JWT
Portlet->>AuthService: Validate Token
AuthService-->>Portlet: Claims
Portlet->>Client: Rendered Content
Implementation:
@Header( name = "Authorization", value = "Bearer ${request.headers['Authorization']}")public class SecurePortlet extends GenericPortlet { // ...}5. Performance Optimization
Section titled “5. Performance Optimization”Lazy Loading Pattern
Section titled “Lazy Loading Pattern”@Component(service = PortletDataLoader.class)public class AsyncDataLoader {
@Reference private ExecutorService _executor;
public <T> CompletableFuture<T> loadData(Supplier<T> supplier) { return CompletableFuture.supplyAsync(supplier, _executor); }}
// In Portlet:request.setAttribute( "asyncData", dataLoader.loadData(() -> fetchHeavyDataSet()));Connection Pooling
Section titled “Connection Pooling”@Reference(target = "(pool.name=portal)")private DataSource _dataSource;
public List<Data> fetchData() { try (Connection con = _dataSource.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT...")) { // Execute query }}6. Debugging Toolkit
Section titled “6. Debugging Toolkit”Diagnostic Endpoints
Section titled “Diagnostic Endpoints”@ServeResource(resourceID = "diagnostics")public void serveDiagnostics(ResourceRequest request, ResourceResponse response) { response.setContentType("text/plain"); PrintWriter writer = response.getWriter(); writer.println("Thread: " + Thread.currentThread().getName()); writer.println("Session: " + request.getPortletSession().getId()); writer.println("Preferences: " + request.getPreferences().getMap());}Performance Metrics
Section titled “Performance Metrics”@Metric(name = "render_time", description = "Portlet render duration")private Counter renderTime;
@Overrideprotected void doView(RenderRequest request, RenderResponse response) { long start = System.nanoTime(); try { // Rendering logic } finally { renderTime.record( System.nanoTime() - start, TimeUnit.NANOSECONDS ); }}7. Real-World Enterprise Pattern
Section titled “7. Real-World Enterprise Pattern”Multi-Tenant Portlet
Section titled “Multi-Tenant Portlet”@Component( property = { "com.liferay.portlet.tenantable=true", "javax.portlet.name=com_example_TenantAwarePortlet" }, service = Portlet.class)public class TenantAwarePortlet extends GenericPortlet {
@Reference private CompanyLocalService _companyService;
@Override protected void doView(RenderRequest request, RenderResponse response) { long companyId = PortalUtil.getCompanyId(request); Company company = _companyService.getCompany(companyId); request.setAttribute("company", company); }}Circuit Breaker Pattern
Section titled “Circuit Breaker Pattern”@Referenceprivate CircuitBreakerFactory _circuitBreakerFactory;
public String fetchExternalData() { return _circuitBreakerFactory.create("externalService").run( () -> externalService.call(), throwable -> "Fallback data" );}8. Testing Strategies
Section titled “8. Testing Strategies”Unit Test Example
Section titled “Unit Test Example”public class MyPortletTest {
@Test public void testRender() throws Exception { // Setup RenderRequest request = new MockRenderRequest(); RenderResponse response = new MockRenderResponse();
// Execute new MyPortlet().doView(request, response);
// Verify assertThat(response.getContent()).contains("Expected Content"); }}Integration Test
Section titled “Integration Test”@RunWith(PortletTestRunner.class)@PortletTestConfiguration( portletName = "myPortlet", portletClass = MyPortlet.class)public class MyPortletIT {
@Inject private PortletRequestDispatcher _dispatcher;
@Test public void testFullRender() { MockRenderRequest request = new MockRenderRequest(); MockRenderResponse response = new MockRenderResponse();
_dispatcher.render(request, response);
assertThat(response.getContentAsString()) .contains("Expected Markup"); }}9. Future-Forward Techniques
Section titled “9. Future-Forward Techniques”Portlet as Microservice
Section titled “Portlet as Microservice”@Component( property = { "service.exported.interfaces=*", "service.exported.configs=org.apache.cxf.rs", "javax.ws.rs=true" }, service = MyPortletService.class)@Path("/my-portlet-api")public class MyPortletServiceImpl implements MyPortletService {
@GET @Path("/data") @Produces("application/json") public String getData() { return "{ \"status\": \"OK\" }"; }}WASM Integration
Section titled “WASM Integration”// In portlet's JSWebAssembly.instantiateStreaming(fetch("module.wasm")).then((wasm) => { wasm.instance.exports.processData();});