How can I change the name of the node AEM creates when a component is first dropped on the page? - aem

I’m trying to figure out if it’s possible to customize the name of the node AEM creates when I first drop a component on the page.
The cq:Component node where my component is defined is named “knowledge-center-question” and when I drop it, AEM creates a node named “knowledge_center_que” in the page’s node tree using its default naming logic. I would prefer for the node name to be “question” when it is dropped (but I’d rather not rename the component itself).
It seems like this kind of thing must be possible given how customizable everything is in AEM, but I’m struggling to find an answer.

Take a look at :nameHints, which can be send as POST arguments to the SlingPostServlet: https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html#algorithm-for-node-name-creation

You need to write a custom Sling post processor. Sling post processor is called after a component dropped in the page. Example code :
#Component(service = SlingPostProcessor.class, immediate = true, name = "com.aem.CustomPostProcessor")
public class CustomPostProcessor implements SlingPostProcessor {
#Override
public void process(SlingHttpServletRequest request, List<Modification> modifications) throws Exception {
if (accepts(request)) {
final Resource resource = request.getResourceResolver().getResource(request.getResource().getPath());
// Your logic
modifications.add(Modification.onCreated(resource.getPath()));
}
}
protected boolean accepts(SlingHttpServletRequest request) {
return "/my/resource/type".equals(request.getResource().getResourceType());
}
}

Related

Umbraco - when editor create content send email notification to admin

Is it possible?
I am an admin. I want to be notified by email when editor (or writer or whom ever with the access) creates some content (e.g. enters some News in News document type).
And how?
I use Umbraco 7.5
You need to code into Umbraco ContentService events.
The following should get you started. It will be triggered whenever an item is published.
Be careful what you wish for though. You may get a barrage of useless emails if somebody publishes a parent node along with all of its child nodes.
There are other events that you can hook into so please refer to documentation at https://our.umbraco.com/Documentation/Reference/Events/ContentService-Events-v7.
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Publishing;
using Umbraco.Core.Services;
namespace My.Namespace
{
public class MyEventHandler : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ContentService.Published += ContentServicePublished;
}
private void ContentServicePublished(IPublishingStrategy sender, PublishEventArgs<IContent> args)
{
foreach (var node in args.PublishedEntities)
{
// Your code to send email here
}
}
}
}
You can either write your own custom code by creating some event handlers, which is what #wingyip has recommended, or you can use built-in Umbraco notification functionality.
For the second built-in option, please see all the steps here on this post.

I want a custom attribute to be fired automatically without writing that on the web api method

I want a custom attribute to be fired automatically without writing that on the web api method.
What is the way I can achieve that?
Let us say for example you have an action filter attribute
public class MyApiActionFilterAttribute : FilterAttribute {
//...
}
With that you can assign it to the class or action depending on how you want it to be used
public class MyApiController : ApiController {
[MyApiActionFilter]
public IHttpActionResult MyAction() {
return Ok();
}
}
This applies to this action only.
But, if you wanted it to be applied on all actions (globally) within the web API, you would need to add it to the configuration.
GlobalConfiguration.Configuration.Filters.Add(new MyApiActionFilterAttribute ());
This is normally done at startup web configuring the web API.
Note that in doing the above, the filter will be applied to all requests.

AEM- Activating page references in node properties along with the page

I am working on AEM 6.2 and have created a custom replication module. I have some properties in my page's child nodes under jcr:content,whose values are the path field to another pages in the same website.
when I am activating a page, I need to activate the pages referenced in the properties too.
For example, my page path is "/content/project/family/subfamily/TestPage"
I need to activate a page path in the node property "pathVal" under
"/content/project/family/subfamily/TestPage/abc123/jcr:content".
How do I do this?
I am not sure what you mean by
custom replication module
Writing a replication Preprocessor (see docs) may be a way to go. The replication process collects all implementations of that interface using the whiteboard pattern and then cycles through them invoking each one of them.
#Component
#Service
public class ReferencedPagePreprocessor implements Preprocessor {
#Reference
private Replicator replicator;
#Reference
private ResourceResolverFactory resolverFactory;
public void preprocess(ReplicationAction action, ReplicationOptions options) {
// some extra filtering to avoid the calculation if it's not the expected page type
String resourcePath = action.getPath();
ResourceResolver resolver = getResolver();
Resource resource = resolver.resolve(resourcePath);
String referencedResourcePath = resource.adaptTo(ValueMap.class).get("pathVal", String.class);
replicator.replicate(resolver.adaptTo(Session.class), ReplicationActionType.ACTIVATE, referencedResourcePath);
}
private ResourceResolver getResolver() {
...
}
}
Take also a look into a sample implementation in ACS AEM Commons
If I understand correctly either you have implemented your own workflow process that activates the page or you would have followed the approach of Preprocessor as outlined by Mateusz Chromiński.
In case you have written your own workflow process that invokes Replicator API you could effectively add logic to get referenced paths and call activate on them using Replicator API

How to produce rendered output from a Sling POST in AEM?

It seems like Sling expects every form POST to modify the JCR. So the expected/standard behavior would be a POST-redirect-GET, which is fine for most things. However, I need to be able to POST to AEM and then use the data in that POST to create a rendered result. Our use of AEM is stateless and so I don't want to carry the POST'd data in Session in order to utilize it in a subsequent GET.
Some have recommended putting the POST'd data in Browser sessionStorage, but that doesn't have broad enough support to be sufficient.
As far as I can tell there is no way for Sling in AEM to take a POST and produce a rendered result.
Here is a screenshot of what a POST produces in the page/resourceType component and in any Sling included jsp's that happen to be involved in the rendering.
I have tried things like using the "nop" operation.
<input type="hidden" name=":operation" value="nop" />
But either way all servlets think a POST is happening and don't render properly.
There is the option of creating a custom servlet, to handle the POST, but then how do you render the templated output and change the request so that all the components think they are serving a GET?
UPDATED:
Here is a screenshot of the "nop" POST.jsp result.
What you can do is create a POST.jsp file in the appropiate resourceType.
If your POST request go to /content/yourapp/something, which has a resourceType: your/app/example. Then you can create a file /apps/your/app/example/POST.jsp with whatever render you wish. You can even include your default rendering script in the POST.jsp file if you need it to be rendered the same as the GET requests.
The other option is to use a servlet registered for POST requests and internally use the SlingRequestProcessor service. That service allow you to programmatically process a request through Sling. You can use a SlingRequestWrapper to wrap your request and override getMethod() to return "GET". That should process the request as if it was a GET request.
This sounds like a somewhat funky use case, IIUC you are using a large request parameter P to drive the rendering?
Using a custom POST servlet should work, if you use something like
slingRequest.getRequestDispatcher(resource).forward(request, response) where request is a wrapper around the actual request, where request.getMethod() returns GET. You can then store your P data in request attributes.
The SlingHttpServletRequestWrapper class can be used to create such wrappers.
Creating a custom servlet to handle a post could be an idea. After successfull write you could redirect to the modified resource - simple 302.
The other solution that comes to my mind is a custom Filter that would do the same. However, since AEM expects to get 200 instead of 302, it would be good to tell by atrribute or parameter that this POST needs to be redirected. Otherwise some of the AEM UI functionalities could brake. This is a quick example of an idea. You would probably need to write something more sophisticated.
#Component(immediate = true)
#Service
#Properties({
#Property(name = Constants.SERVICE_DESCRIPTION, value = "Desc"),
#Property(name = Constants.SERVICE_VENDOR, value = "Company name"),
#Property(name = Constants.SERVICE_RANKING, intValue = RedirectFilter.RANKING),
#Property(name = "filter.scope", value = "request") })
public class RedirectFilter implements Filter {
public static final int RANKING = -1000; // low ranking
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
if (request.getParameter("redirect").equals("true")) {
((SlingHttpServletResponse) response).sendRedirect(((SlingHttpServletRequest)request).getRequestURI());
}
}
}

Performing Explicit Route Mapping based upon Web Api v2 Attributes

I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?