How to use Apache FOP with Velocity in Spring MVC web-app? - spring-mvc

I have simple configuration of Velocity in Spring context (according to an official Spring documentation) and works ok. How to configure/integrate this with Apache FOP and generate pdf documents ? I would be grateful for some examples.
<!-- velocity -->
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/velocity/"/>
</bean>
<bean id="velocityViewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".vm"/>
</bean>
Test controller:
#Controller
#RequestMapping("/doc")
public class DocumentController {
#RequestMapping("/test")
public ModelAndView velocityTest() {
List<String> xmens = new ArrayList<String>();
xmens.add("Professor X");
xmens.add("Cyclops");
xmens.add("Iceman");
xmens.add("Archangel");
xmens.add("Beast");
xmens.add("Phoenix");
Map<String, List<String>> model = new HashMap<String, List<String>>();
model.put("xmens", xmens);
return new ModelAndView("testdoc", model);
}
}
/WEB-INF/velocity/test.vm
<html>
<body>
<ul>
#foreach ($xmen in $xmens)
<li>$xmen</li>
#end
</ul>
</body>
</html>

I did it this way, but I think it is certainly a more elegant solution (testpdf.vm and testpdf.xsl are in /WEB-INF/velocity).
#Controller
#RequestMapping("/doc")
public class DocumentController {
#Autowired
private PdfReportService pdfReportService;
#RequestMapping("/pdf")
public void testPdf(HttpServletResponse response) throws IOException {
Map<String, Object> model = new HashMap<String,Object>();
model.put("message", "Hello World!");
pdfReportService.generatePdf("testpdf", model, response.getOutputStream());
}
}
PdfReportService:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
#Service("pdfReportService")
public class PdfReportService {
private final Logger log = Logger.getLogger(getClass());
#Autowired
private VelocityConfigurer velocityConfig;
#Autowired
private ServletContext servletContext;
public void generatePdf(String templateName, Map<String,Object>model, OutputStream out) {
// get an engine
final VelocityEngine engine = velocityConfig.getVelocityEngine();
// get the Template
Template template = engine.getTemplate(templateName+".vm");
// create a context and add data
VelocityContext context = new VelocityContext();
context.put("model", model);
// render the template into a StringWriter
StringWriter writer = new StringWriter();
template.merge(context, writer);
FopFactory fopFactory = FopFactory.newInstance();
try {
//Setup FOP
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);
//Setup Transformer
InputStream xstlIn = servletContext.getResourceAsStream("/WEB-INF/velocity/"+templateName+".xsl");
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(new StreamSource(xstlIn));
//Make sure the XSL transformation's result is piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
//Setup input
byte[] bytes = writer.toString().getBytes("UTF-8");
Source src = new StreamSource(new ByteArrayInputStream(bytes));
//Start XSLT transformation and FOP processing
transformer.transform(src, res);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

I did it exactly as marioosh. Later on we had issues (OutOfMemory ecxeptions) in a big web application when we had a lot of users doing PDF conversation at the same time since both the VelocityEngine and Apache FOP need some (a lot of) memory and when you have many concurrent users, this sums up.
We changed the approach to streaming. Velocity streams the XSL-FO to Apache FOP now. FOP streams the result to the client. We did this with PipedReader / PipedWriter. Please note that this solution needs an extra thread. I cannot share this code as we did it for a customer of us.
Meantime I found an existing solution for streaming on the web, see the archive. But note that this solution creates an extra thread via new Thread(worker).start();
In an application server, you should rather use a WorkManager instead. See also http://www.devx.com/java/Article/28815/1954

Related

Infinispan cluster with Karaf instances

we are very new to Infinispan and also quite new to Apache Karaf. Installing Infinispan in Karaf was easy, we did write two OSGi Bundles to form a cluster with two nodes that run on one host. We tried it with the tutorial for a distributed cache from the Infinispan website (tutorial). Unfortunately the cluster seems not to be build and we can't determine why. Any help or push in the right direction would be very appreciated.
The code of the bundle that writes something in the cache looks like that:
import org.infinispan.Cache;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.context.Flag;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheProducer implements BundleActivator{
private static Logger LOG = LoggerFactory.getLogger(CacheProducer.class );
private static DefaultCacheManager cacheManager;
#Override
public void start( BundleContext context ) throws Exception{
LOG.info( "Start Producer" );
GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
global.transport().clusterName("ClusterTest");
// Make the default cache a distributed synchronous one
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clustering().cacheMode(CacheMode.DIST_SYNC);
// Initialize the cache manager
cacheManager = new DefaultCacheManager(global.build(), builder.build());
// Obtain the default cache
Cache<String, String> cache = cacheManager.getCache();
cache.put( "message", "Hello World!" );
LOG.info( "Producer: whole cluster content!" );
cache.entrySet().forEach(entry -> LOG.info(entry.getKey()+ ": " + entry.getValue()));
LOG.info( "Producer: current cache content!" );
cache.getAdvancedCache().withFlags(Flag.SKIP_REMOTE_LOOKUP)
.entrySet().forEach(entry -> LOG.info(entry.getKey()+ ": " + entry.getValue()));
}
#Override
public void stop( BundleContext context ) throws Exception{
cacheManager.stop();
}
}
And the one that tries to print out what is in the cache like that:
package metdoc81.listener;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.infinispan.Cache;
import org.infinispan.manager.DefaultCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Activator implements BundleActivator{
private static Logger LOG = LoggerFactory.getLogger(Activator.class);
private static DefaultCacheManager cacheManager;
public void start( BundleContext bundleContext ) throws Exception{
LOG.info("start cluster listener");
GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
global.transport().clusterName("ClusterTest");
// Make the default cache a distributed synchronous one
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.clustering().cacheMode(CacheMode.DIST_SYNC);
// Initialize the cache manager
cacheManager = new DefaultCacheManager(global.build(), builder.build());
// Obtain the default cache
Cache<String, String> cache = cacheManager.getCache();
LOG.info("After configuration");
cache.entrySet().forEach(entry -> LOG.info(entry.getKey()+ ": " + entry.getValue()));
LOG.info("After logging");
}
public void stop( BundleContext bundleContext ) throws Exception{
}
}
The printing from the CacheProducer works, printing from the Listener does not.
We found the solution ourselves.
The problem just occurs when you try to run the code on MacOS, on Windows it's working. According to a discussion at JBossDeveloper there was a problem with the multicast routing on MacOS. Even though they added a workaround into the example code, you still have to add the -Djava.net.preferIPv4Stack=true Flag when running it or you have to add these two lines of code:
Properties properties = System.getProperties();
properties.setProperty( "java.net.preferIPv4Stack", "true" );

Off / Disable Inheritance Permission from Custom folder

I am trying to create a custom folder in Create menu of Document Library called 'Confidential Folder' in Alfresco. I've attempted the following procedure,
I have used the Alfresco Maven SDK to create a project that will package up my customizations in two AMPs (Alfresco Module Packages). One AMP is for the Alfresco web application (the "repo" tier) and the other is for the Alfresco Share web application (the "Share" tier).
I have added the following code in share-config-custom.xml:
XML:
<config evaluator="string-compare" condition="DocumentLibrary">
<create-content>
<content id="confidentialFolder" mimetype="text/plain"
label="Confidential Folder" itemid="cm:folder" icon="finalize">
<param name="action">confidential-folder</param>
</content>
</create-content>
</config>
In service-context.xml I registered the bean:
XML:
<bean id="confidential-folder" class="com.finalize.action.executer.ConfidentialFolder"
parent="action-executer">
<property name="nodeService">
<ref bean="NodeService" />
</property>
</bean>
And wrote the Java class for execute the action which will Off the Inheritance Permission.
Action Executer:
package com.finalize.action.executer;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.repo.security.permissions;
import org.alfresco.repo.policy.ClassPolicy;
//import org.alfresco.repo.events.EventsService;
public class ConfidentialFolder extends ActionExecuterAbstractBase {
public static final QName DISABLE = QName.createQName(NamespaceService.ALFRESCO_URI, "onInheritPermissionsDisabled");
// protected EventsService eventsService;
protected NodeService nodeService;
/* public void setEventsService(EventsService eventsService)
{
this.eventsService = eventsService;
}*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/*public void onInheritPermissionsDisabled(NodeRef nodeRef, boolean async)
{
inheritPermissionsDisabled(nodeRef, async);
}*/
#Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
onInheritPermissionsDisabled(actionedUponNodeRef,false);
// private static final QName POLICY_ON_INHERIT_PERMISSIONS_DISABLED = QName.createQName(NamespaceService.ALFRESCO_URI, "onInheritPermissionsDisabled");
// nodeService.addAspect(actionedUponNodeRef, QName.createQName(FinalizeModel.NAMESPACE_FINALIZE_CONTENT_MODEL, FinalizeModel.ASPECT_FIN_WEBABLE), properties);
}
#Override
protected void addParameterDefinitions(List<ParameterDefinition> paramList) {
paramList.add(
new ParameterDefinitionImpl( // Create a new parameter definition to add to the list
"active", // The name used to identify the parameter
DataTypeDefinition.BOOLEAN, // The parameter value type
false, // Indicates whether the parameter is mandatory
getParamDisplayLabel("active"))); // The parameters display label
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
I wanted to Off or Disable the Inheritance Permission from Confidential folder menu but I am not able to achieve.
This is cross-posted on the Alfresco forums here: https://community.alfresco.com/thread/238511-off-disable-inheritance-permission-from-custom-folder and is being discussed, so no need to duplicate it here.

What does the EndpointsServlet class do in Google's Endpoints?

First, I am a beginner in java servlets, maven projects and apis.
I am doing the following tutorial on getting started with google endpoints, which is a tutorial implementing the following maven project source code on github. On the web.xml, there is only one named Servlet, the EndpointsServlet like so:
<!-- wrap the backend with Endpoints Framework v2. -->
<servlet>
<servlet-name>EndpointsServlet</servlet-name>
<servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.example.echo.Echo</param-value>
</init-param>
</servlet>
What I dont understand is why are there no other servlets on the project? There are only 3 java classes in the main directory and none of them are servlet files. I am assuming that this project is a sample api with server side logic (such as routing and responding to requests) like any other servlet project which means there should be more than this servlet.
The comment on the web.xml is an obvious clue as to what it does but I dont really know what wrapping the backend with endpoints framework means. Also, I actually got the EndpointsServlet.java file and it says the servlet is a "handler for proxy-less API serving. This servlet understands and replies in JSON-REST. Again, I dont really understand this comment nor what the servlet does even reading it. Servlet code below:
package com.google.api.server.spi;
import com.google.api.server.spi.SystemService.EndpointNode;
import com.google.api.server.spi.config.ApiConfigException;
import com.google.api.server.spi.config.model.ApiClassConfig.MethodConfigMap;
import com.google.api.server.spi.config.model.ApiConfig;
import com.google.api.server.spi.config.model.ApiMethodConfig;
import com.google.api.server.spi.dispatcher.PathDispatcher;
import com.google.api.server.spi.handlers.ApiProxyHandler;
import com.google.api.server.spi.handlers.CorsHandler;
import com.google.api.server.spi.handlers.EndpointsMethodHandler;
import com.google.api.server.spi.handlers.ExplorerHandler;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* A handler for proxy-less API serving. This servlet understands and replies in JSON-REST.
*/
public class EndpointsServlet extends HttpServlet {
private static final String EXPLORER_PATH = "explorer";
private ServletInitializationParameters initParameters;
private SystemService systemService;
private PathDispatcher<EndpointsContext> dispatcher;
private CorsHandler corsHandler;
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ClassLoader classLoader = getClass().getClassLoader();
this.initParameters = ServletInitializationParameters.fromServletConfig(config, classLoader);
this.systemService = createSystemService(classLoader, initParameters);
this.dispatcher = createDispatcher();
this.corsHandler = new CorsHandler();
}
#Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
String method = getRequestMethod(request);
if ("OPTIONS".equals(method)) {
corsHandler.handle(request, response);
} else {
String path = Strings.stripSlash(
request.getRequestURI().substring(request.getServletPath().length()));
EndpointsContext context = new EndpointsContext(method, path, request, response,
initParameters.isPrettyPrintEnabled());
if (!dispatcher.dispatch(method, path, context)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().append("Not Found");
}
}
}
private String getRequestMethod(HttpServletRequest request) {
Enumeration headerNames = request.getHeaderNames();
String methodOverride = null;
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
if (headerName.toLowerCase().equals("x-http-method-override")) {
methodOverride = request.getHeader(headerName);
break;
}
}
return methodOverride != null ? methodOverride.toUpperCase() : request.getMethod();
}
private PathDispatcher<EndpointsContext> createDispatcher() {
PathDispatcher.Builder<EndpointsContext> builder = PathDispatcher.builder();
List<EndpointNode> endpoints = systemService.getEndpoints();
// We're building an ImmutableList here, because it will eventually be used for JSON-RPC.
ImmutableList.Builder<EndpointsMethodHandler> handlersBuilder = ImmutableList.builder();
for (EndpointNode endpoint : endpoints) {
ApiConfig apiConfig = endpoint.getConfig();
MethodConfigMap methods = apiConfig.getApiClassConfig().getMethods();
for (Entry<EndpointMethod, ApiMethodConfig> methodEntry : methods.entrySet()) {
if (!methodEntry.getValue().isIgnored()) {
handlersBuilder.add(
new EndpointsMethodHandler(initParameters, getServletContext(), methodEntry.getKey(),
apiConfig, methodEntry.getValue(), systemService));
}
}
}
ImmutableList<EndpointsMethodHandler> handlers = handlersBuilder.build();
for (EndpointsMethodHandler handler : handlers) {
builder.add(handler.getRestMethod(), Strings.stripTrailingSlash(handler.getRestPath()),
handler.getRestHandler());
}
ExplorerHandler explorerHandler = new ExplorerHandler();
builder.add("GET", EXPLORER_PATH, explorerHandler);
builder.add("GET", EXPLORER_PATH + "/", explorerHandler);
builder.add("GET", "static/proxy.html", new ApiProxyHandler());
return builder.build();
}
private SystemService createSystemService(ClassLoader classLoader,
ServletInitializationParameters initParameters) throws ServletException {
try {
SystemService.Builder builder = SystemService.builder()
.withDefaults(classLoader)
.setStandardConfigLoader(classLoader)
.setIllegalArgumentIsBackendError(initParameters.isIllegalArgumentBackendError())
.setDiscoveryServiceEnabled(true);
for (Class<?> serviceClass : initParameters.getServiceClasses()) {
builder.addService(serviceClass, createService(serviceClass));
}
return builder.build();
} catch (ApiConfigException | ClassNotFoundException e) {
throw new ServletException(e);
}
}
/**
* Creates a new instance of the specified service class.
*
* #param serviceClass the class of the service to create
*/
protected <T> T createService(Class<T> serviceClass) {
try {
return serviceClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(
String.format("Cannot instantiate service class: %s", serviceClass.getName()), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(
String.format("Cannot access service class: %s", serviceClass.getName()), e);
}
}
}
EndpointsServlet handles all API calls with a certain path prefix. It takes a RESTful API call and translates it into POJO(s) and dispatches it to a Java method you've written, and then serializes the return value of that method to JSON. It does this based on how you annotate your code.

How to configure Shiro with Spring Boot

I have a Spring MVC web application that uses Shiro authentication using Spring configuration rather than a shiro.ini.
I want to transition to a Spring Boot application.
I have been mainly successful. The application starts in Spring Boot and my Shiro environment gets setup. However I just cannot work out how to setup the Shiro Filter correctly. I need this to be working to make sure requests end up being handled by the correct thread.
In the original app I configured the Shiro Filter in the web.xml like this:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I have tried replicate this using a Java Config like this:
#Autowired
private WebSecurityManager webSecurityManager;
#Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new org.apache.shiro.spring.web.ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(webSecurityManager);
return shiroFilterFactoryBean;
}
#Bean
public org.apache.shiro.spring.LifecycleBeanPostProcessor lifecycleBeanPostProcessor()
{
return new org.apache.shiro.spring.LifecycleBeanPostProcessor();
}
#Bean
public Filter shiroFilter()
{
DelegatingFilterProxy filter = new DelegatingFilterProxy();
filter.setTargetBeanName("shiroFilterFactoryBean");
filter.setTargetFilterLifecycle(true);
return filter;
}
However I just cannot get everything to fit together and don't have enough knowledge to sort it out. I just can't see to connect the filter to the environment. I would guess it is something to do with the order things are setup.
Has anyone managed to use Spring Boot and Shiro together successfully?
Well, it seems that the lack of something, java config like this:
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.realm.text.PropertiesRealm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setLoginUrl("/login");
shiroFilter.setSuccessUrl("/index");
shiroFilter.setUnauthorizedUrl("/forbidden");
Map<String, String> filterChainDefinitionMapping = new HashMap<String, String>();
filterChainDefinitionMapping.put("/", "anon");
filterChainDefinitionMapping.put("/home", "authc,roles[guest]");
filterChainDefinitionMapping.put("/admin", "authc,roles[admin]");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
shiroFilter.setSecurityManager(securityManager());
Map<String, Filter> filters = new HashMap<String, Filter>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
filters.put("logout", new LogoutFilter());
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
System.out.println(shiroFilter.getFilters().size());
return shiroFilter;
}
#Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
#Bean(name = "realm")
#DependsOn("lifecycleBeanPostProcessor")
public PropertiesRealm realm() {
PropertiesRealm propertiesRealm = new PropertiesRealm();
propertiesRealm.init();
return propertiesRealm;
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
https://github.com/lenicliu/eg-spring/tree/master/eg-spring-boot/eg-spring-boot-shiro
lenicliu gave great information, since I can't comment on his answer because I don't have enough reputation. I would like to add all the imports I had to make for his code to actually compile (maybe useful for noobies on Shiro, like me).
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.realm.text.PropertiesRealm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;

CQ5 - Hiding a tab within a component dialog depending on user group?

Any ideas how I would hide or show a dialog tab panel depending on which user group the user belongs to?
I tried to do this through the CRX content explorers (ACL's). But I'm not getting much luck with it.
Cheers
As noted by anthonyh, the ACL approach is the way to go (if such a behavior is really necessary).
For example, to hide the "image" tab of the base page component:
edit acls for /libs/foundation/components/page/dialog/items/tabs/items/image
add deny jcr:read for author
login as author
go to http://localhost:4502/content/geometrixx/en.html and open the page properties
image tab should be gone
Note that in case of tabs included with xtype=cqinclude you have to set it on the include itself, not the included definition. Because at runtime it would complain over the missing target of the include and not render the dialog at all.
This can be accomplished with a custom servlet and a dialog event listener.
The listener function makes a request to the servlet, passing the current user ID and the desired group. The dialog tab can then be hidden based on the servlet response.
Here is an example dialog.xml for a CQ5 component:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Dialog"
xtype="dialog">
<listeners jcr:primaryType="nt:unstructured"
loadcontent="function(dialog) {
var url = '/bin/member.json';
// check if current user belongs to administrators group
url = CQ.HTTP.addParameter(url, 'userId', CQ.User.getUserID());
url = CQ.HTTP.addParameter(url, 'groupId', 'administrators');
var result = CQ.HTTP.eval(url);
if (!result.isMember) {
// hide "tab2" if user is not an administrator
dialog.findByType('tabpanel')[0].hideTabStripItem(1);
}
}" />
<items jcr:primaryType="cq:WidgetCollection">
<tab1 jcr:primaryType="cq:Widget" title="Text" xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
...
</items>
</tab1>
<tab2 jcr:primaryType="cq:Widget" title="Image" xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
...
</items>
</tab2>
</items>
</jcr:root>
And here is the corresponding servlet:
import java.io.IOException;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonGenerator.Feature;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.security.Group;
import com.day.cq.security.User;
import com.day.cq.security.UserManager;
import com.google.common.collect.ImmutableMap;
#Component(immediate = true)
#Service
#Properties({
#Property(name = "service.description", value = "Group Member servlet checks if a user is a member of a group."),
#Property(name = "sling.servlet.paths", value = "/bin/member")
})
public class GroupMemberServlet extends SlingAllMethodsServlet {
/** Required. */
private static final long serialVersionUID = 1L;
/** Logger */
private static final Logger LOG = LoggerFactory.getLogger(GroupMemberServlet.class);
private static final JsonFactory FACTORY = new JsonFactory().disable(Feature.AUTO_CLOSE_TARGET);
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response) {
final UserManager userManager = request.getResourceResolver().adaptTo(UserManager.class);
final String userId = request.getRequestParameter("userId").getString();
final String groupId = request.getRequestParameter("groupId").getString();
final Group group = (Group) userManager.get(groupId);
final User user = (User) userManager.get(userId);
writeJsonResponse(response, ImmutableMap.of("isMember", group.isMember(user)));
}
private void writeJsonResponse(final SlingHttpServletResponse response, final Object object) {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
try {
final JsonGenerator generator = FACTORY.createJsonGenerator(response.getWriter());
MAPPER.writeValue(generator, object);
} catch (final JsonGenerationException jge) {
LOG.error("error generating JSON response", jge);
} catch (final JsonMappingException jme) {
LOG.error("error mapping JSON response", jme);
} catch (final IOException ioe) {
LOG.error("error writing JSON response", ioe);
}
}
}
One question spring to mind...Why do you want to limit control of an authoring dialog and remove a tab?
There's no reason why ACLs wouldn't work for this. Did you set them to be restrictive enough for the tab? Were you testing with a non-admin user? I would be cautious using something so code-heavy to solve an access issue.
Personally if ACLs don't work as well as desired I'd explore creating an new widget based on the tabpanel xtype rather than a code solution which may end up as being specific to one version of CQ5.
My Answer: Use ACLs.
Please have a look at these vaguely related official documents - same principle but different objective:
http://dev.day.com/content/kb/home/cq5/CQ5SystemAdministration/CQ53HowToHideCQNavigationButtons.html
and
http://dev.day.com/docs/en/cq/current/administering/security.html

Resources