Adobe Experience Manager - How can we get currentPage url into Datasource - aem

Below is datasource.jsp for adding dynamic dropdown into my customComponent dialog, I'm using this datasource in my customComponent as below mentioned field.
Here my requirement is need to get url values to dropdown when my customComponent used in any page(this page having urls). So here i need that currentPage url where i have used my customComponent.
Please help me to get that page url where i have used my customComponent to this datasource.
<%
request.setAttribute(DataSource.class.getName(), EmptyDataSource.instance());
ResourceResolver resolver = resource.getResourceResolver();
//Create an ArrayList to hold data
List<Resource> fakeResourceList = new ArrayList<Resource>();
ValueMap vm = null;
Resource childResource = resourceResolver.getResource(currentPage.getPath()+"/jcr:content/node/path");
if(childResource!=null){
Node childNode = childResource.adaptTo(Node.class);
Node childLinks = childNode.getNode("childnode");
if(childLinks!=null){
NodeIterator childrenNodes = childLinks.getNodes();
while(childrenNodes.hasNext()) {
vm = new ValueMapDecorator(new HashMap<String, Object>());
Node next = childrenNodes.nextNode();
String label = next.getProperty("label").getValue().getString();
String path = next.getProperty("url").getValue().getString();
vm.put("text",label);
vm.put("value",path.substring(1));
fakeResourceList.add(new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm));
}
}
} else {
vm = new ValueMapDecorator(new HashMap<String, Object>());
vm.put("text","NoValue");
vm.put("value","");
fakeResourceList.add(new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm));
}
DataSource ds = new SimpleDataSource(fakeResourceList.iterator());
request.setAttribute(DataSource.class.getName(), ds);
%>
In my customComponent dialog content.xml which is using above datasource as it's sling:resourceType.
<dataSourceTest
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldDescription="Provide ID"
fieldLabel="Anchor"
name="./datasourceTest">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/mysite/components/page/datasource"/>
</dataSourceTest>

The SlingHttpServletRequest (in general) provides an instance of SlingBindings, which contains a reference to "currentPage" (I am using the static field WCMBindings.CURRENT_PAGE [dependency: groupId: com.adobe.cq.sightly, artifactId: cq-wcm-sightly-extension, version: 1.2.30] in my example).
The Optional I am using in my example is a Java 8 class which can be used to avoid too many checks for null references.
final Optional<Page> optional = Optional.ofNullable(request)
.map(req -> (SlingBindings) req.getAttribute(SlingBindings.class.getName()))
.map(b -> (Page) b.get(WCMBindings.CURRENT_PAGE));
A simplified/explicit example would be
Page getCurrentPageFromRequest(#Nonnull final SlingHTTPRequest request) {
final SlingBindings bindings = (SlingBindings) request.getAttribute(SlingBindings.class.getName())
if (bindings == null) {
return null;
}
return (Page) bindings.get(WCMBindings.CURRENT_PAGE);
}

In the code for the datasource's resourceType, you can get the URL of the current page by reading the CONTENTPATH attribute and using the resource resolver to get a resource which you can then adapt to a Node. The node will be the content item for the dialog. Confirmed to work in AEM 6.2.
// get list of child nodes of the current component node
Resource res = resourceResolver.getResource((String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE));
Node node = res.adaptTo(Node.class);
log.info(node.getPath());
source: https://rmengji.wordpress.com/2016/07/16/aem-6-2-touch-ui-dropdown-pulling-data-dynamically-using-sightly/

Related

Large http payloads aren't getting sent to the Serilog Http Sink endpoint in .NET Core 3.1

Summary
I'm having trouble posting from Serilog (Http Sink) to my custom .NET Core 3.1 WebAPI endpoint when the logging data is large. If I remove some log data when I do the logging, then Serilog sinks properly with my WebAPI endpoint.
My Configuration
new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Http(httpPath, httpClient: new CustomHttpClient(), batchPostingLimit: int.MaxValue, queueLimit: int.MaxValue)
.CreateLogger();
My Custom Http Client
public class CustomHttpClient : IHttpClient
{
private readonly HttpClient c_httpClient;
public CustomHttpClient()
{
c_httpClient = new HttpClient
{
MaxResponseContentBufferSize = 2147483647L
};
}
public void Configure(IConfiguration configuration)
{
}
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content) => c_httpClient.PostAsync(requestUri, content);
public void Dispose() => c_httpClient?.Dispose();
}
What actually does the logging
var exceptionModel = new AppMonModel
{
Application = "SerilogMvc Sample Application",
Message = ex.Message,
Source = "SerilogMvc.HomeController.Index",
StackTrace = ex.StackTrace,
InnerException = ex.InnerException?.StackTrace,
Details = "Sample details here",
InsertDate = DateTime.Now,
Severity = 100,
UserDescription = "Keyvan User",
ScreenshotBase64String = Convert.ToBase64String(System.IO.File.ReadAllBytes("C:/SamplePath/Untitled.png"))
};
c_logger.LogError(ex, "{exceptionModel}", exceptionModel);
My Endpoint
[HttpPost("log")]
[DisableRequestSizeLimit]
public void Log([FromBody] object logEvents) { ... }
Serilog Error
Event JSON representation exceeds the byte size limit of 262144 set for this sink and will be dropped;
Issue
When I remove ScreenshotBase64String = Convert.ToBase64String(System.IO.File.ReadAllBytes("C:/SamplePath/Untitled.png")) from my exceptionModel object, I see the error in my WebAPI endpoint. As soon as I add it back in, it doesn't even hit the endpoint.
Please let me know if you need additional details. I'd be more than glad to provide them.
The answer was quite simple after turning on Self logging. This is the change I needed to make to increase the batch formatter size:
var defaultBatchFormatter = new DefaultBatchFormatter(batchFormatterSize);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Error()
.Enrich.FromLogContext()
.WriteTo.Http(httpPath, batchFormatter: defaultBatchFormatter)
.CreateLogger();
The batch formatter size needed to be increased.
Need to add eventBodyLimitBytes
enter image description here
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl, eventBodyLimitBytes: 1048576)

How to create a client app for a RESTful service from wadl?

Given an application.wadl file, how do I generate Client app (Spring or any) and domain objects from a wadl file?
I tried :
wadl2java https://genologics.com/files/permanent/API/2.5/application.wadl
WADLToJava Error: java.lang.IllegalStateException: Single WADL resources element is expected
This is my findings by reviewing the source-code:
As SourceGenerator.java, wadltojava is trying to get the "resources" element from the "application" element and expects it to be one only.
private void generateResourceClasses(Application app, GrammarInfo gInfo,
Set<String> typeClassNames, File src) {
Element appElement = app.getAppElement();
List<Element> resourcesEls = getWadlElements(appElement, "resources");
if (resourcesEls.size() != 1) {
throw new IllegalStateException("Single WADL resources element is expected");
}
List<Element> resourceEls = getWadlElements(resourcesEls.get(0), "resource");
if (resourceEls.size() == 0) {
throw new IllegalStateException("WADL has no resource elements");
}
........
}
I checked the WADL you provided and seems like there is only one "resources" element.
On checking further in getWadlElements() method is using getWadlNamespace():
private List<Element> getWadlElements(Element parent, String name) {
List<Element> elements = parent != null
? DOMUtils.getChildrenWithName(parent, getWadlNamespace(), name)
: CastUtils.cast(Collections.emptyList(), Element.class);
if (!"resource".equals(name)) {
for (int i = 0; i < elements.size(); i++) {
Element el = elements.get(i);
Element realEl = getWadlElement(el);
if (el != realEl) {
elements.set(i, realEl);
}
}
}
return elements;
}
The namespace used here in WadlGenerator.java is
public static final String WADL_NS = "http://wadl.dev.java.net/2009/02";
But in your WADL the namespace seems to be different as below, and may be causing issue.
<wadl:application xmlns:wadl="http://research.sun.com/wadl/2006/10" xmlns:xs="http://www.w3.org/2001/XMLSchema">
It seems that you are using CXF so as per my understanding, I would suggest you to use the same framework which is used to generate the WADL.
Update:
Or, have the WADL and XSD's on your local and modify the namespace manually in WADL to the latest one and try again.

Implement Custom Authentication In Windows Azure Mobile Services

Windows Azure Mobile Services currently doesn't have an option for custom authentication and looking at the feature request
http://feedback.azure.com/forums/216254-mobile-services/suggestions/3313778-custom-user-auth
It isn't coming anytime soon.
With a .NET backend and a .NET application how do you implement custom authentication, so that you don't have to use Facebook, Google or any of their other current providers?
There are plenty of partially completed tutorials on how this this is done with a JS backend and iOS and Android but where are the .NET examples?
I finally worked through the solution, with some help of the articles listed below, some intellisense and some trial and error.
How WAMS Works
First I wanted to describe what WAMS is in a very simple form as this part confused me for a while until it finally clicked. WAMS is just a collection of pre-existing technologies packaged up for rapid deployment. What you need to know for this scenario is:
As you can see WAMS is really just a container for a WebAPI and other things, which I won't go into detail here. When you create a new Mobile Service in Azure you get to download a project that contains the WebAPI. The example they use is the TodoItem, so you will see code for this scenario through the project.
Below is where you download this example from (I was just doing a Windows Phone 8 app)
I could go on further about this but this tutorial will get you started:
http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/
Setup WAMS Project
You will need your MasterKey and ApplicationKey. You can get them from the Azure Portal, clicking on your Mobile Services App and pressing Manage Keys at the bottom
The project you just downloaded, in the Controllers folder I just created a new controller called AccountController.cs and inside I put
public HttpResponseMessage GetLogin(String username, String password)
{
String masterKey = "[enter your master key here]";
bool isValidated = true;
if (isValidated)
return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{ 'UserId' : 'F907F58C-09FE-4F25-A26B-3248CD30F835', 'token' : '" + GetSecurityToken(new TimeSpan(1,0, 0), String.Empty, "F907F58C-09FE-4F25-A26B-3248CD30F835", masterKey) + "' }") };
else
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Username and password are incorrect");
}
private static string GetSecurityToken(TimeSpan periodBeforeExpires, string aud, string userId, string masterKey)
{
var now = DateTime.UtcNow;
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var payload = new
{
exp = (int)now.Add(periodBeforeExpires).Subtract(utc0).TotalSeconds,
iss = "urn:microsoft:windows-azure:zumo",
ver = 2,
aud = "urn:microsoft:windows-azure:zumo",
uid = userId
};
var keyBytes = Encoding.UTF8.GetBytes(masterKey + "JWTSig");
var segments = new List<string>();
//kid changed to a string
var header = new { alg = "HS256", typ = "JWT", kid = "0" };
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));
var stringToSign = string.Join(".", segments.ToArray());
var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
SHA256Managed hash = new SHA256Managed();
byte[] signingBytes = hash.ComputeHash(keyBytes);
var sha = new HMACSHA256(signingBytes);
byte[] signature = sha.ComputeHash(bytesToSign);
segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
// from JWT spec
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
You can replace what is in GetLogin, with your own validation code. Once validated, it will return a security token (JWT) that is needed.
If you are testing on you localhost, remember to go into your web.config file and fill in the following keys
<add key="MS_MasterKey" value="Overridden by portal settings" />
<add key="MS_ApplicationKey" value="Overridden by portal settings" />
You need to enter in your Master and Application Keys here. They will be overridden when you upload them but they need to be entered if you are running everything locally.
At the top of the TodoItemController add the AuthorizeLevel attribute as shown below
[AuthorizeLevel(AuthorizationLevel.User)]
public class TodoItemController : TableController<TodoItem>
You will need to modify most of the functions in your TodoItemController but here is an example of the Get All function.
public IQueryable<TodoItem> GetAllTodoItems()
{
var currentUser = User as ServiceUser;
Guid id = new Guid(currentUser.Id);
return Query().Where(todo => todo.UserId == id);
}
Just a side note I am using UserId as Guid (uniqueidentifier) and you need to add this to the todo model definition. You can make the UserId as any type you want, e.g. Int32
Windows Phone/Store App
Please note that this is just an example and you should clean the code up in your main application once you have it working.
On your Client App
Install NuGet Package: Windows Azure Mobile Services
Go into App.xaml.cs and add this to the top
public static MobileServiceClient MobileService = new MobileServiceClient(
"http://localhost:50527/",
"[enter application key here]"
);
In the MainPage.xaml.cs I created
public class Token
{
public Guid UserId { get; set; }
public String token { get; set; }
}
In the main class add an Authenticate function
private bool Authenticate(String username, String password)
{
HttpClient client = new HttpClient();
// Enter your own localhost settings here
client.BaseAddress = new Uri("http://localhost:50527/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync(String.Format("api/Account/Login?username={0}&password={1}", username, password)).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(response.Content.ReadAsStringAsync().Result);
App.MobileService.CurrentUser = new MobileServiceUser(token.UserId.ToString());
App.MobileService.CurrentUser.MobileServiceAuthenticationToken = token.token;
return true;
}
else
{
//Something has gone wrong, handle it here
return false;
}
}
Then in the Main_Loaded function
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Authenticate("test", "test");
RefreshTodoItems();
}
If you have break points in the WebAPI, you will see it come in, get the token, then come back to the ToDoItemController and the currentUser will be filled with the UserId and token.
You will need to create your own login page as with this method you can't use the automatically created one with the other identity providers. However I much prefer creating my own login screen anyway.
Any other questions let me know in the comments and I will help if I can.
Security Note
Remember to use SSL.
References
[] http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx
[] http://www.contentmaster.com/azure/creating-a-jwt-token-to-access-windows-azure-mobile-services/
[] http://chrisrisner.com/Custom-Authentication-with-Azure-Mobile-Services-and-LensRocket
This is exactly how you do it. This man needs 10 stars and a 5 crates of beer!
One thing, I used the mobile Service LoginResult for login like:
var token = Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
Hope to get this into Android now!

How to get currentPagePath in Slingservlet?

From some javascript I call the following Slingservlet with ("/bin/fooServlet?"+params);
#SlingServlet(paths = "/bin/fooServlet", methods = "GET", metatype = true)
public class FooServlet extends SlingAllMethodsServlet {
..
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
Session session = resourceResolver.adaptTo(Session.class);
Page currentPage = pageManager.getPage(request.getPathInfo());
String currentPagePath = currentPage.getPath();
...
}
My Question is: How to get the currentPagePath of the current Page in FooServlet? currentPagePath in the code is null.
As Thomas mentioned, if you define a servlet with fixed paths property, you wouldn't have reference to a Resource.
One way of achieving this is by passing your page path along with the request to the servlet. Also CQ.WCM.getPagePath() returns only /libs/wcm/core/content/siteadmin, as the current page is siteadmin and you may need to tweak your script a bit in order to access the selected page within siteadmin.
To get your page path either from siteadmin or from the page itself, you can use the following script and then pass the value to your servlet for further processing.
var currentPagePath = null;
/* if accessed via siteadmin */
if(CQ.wcm.SiteAdmin.hasListSelection()) {
var grid = CQ.wcm.SiteAdmin.getActiveGrid();
var selections = grid.getSelectionModel().getSelections();
/*Assuming that you are selecting only one page at a time. */
currentPagePath = selections[0].id;
} else { /* accessed via page */
currentPagePath = CQ.WCM.getPagePath();
}
And then you can call the servlet with the currentPagePath as one of the parameters.
GET /bin/fooServlet?currentPagePath=' + currentPagePath + '&foo=bar';
UPDATE
The above code works fine for CQ 5.5 + , for older versions you can use this.
var currentPagePath = null;
/* if accessed via siteadmin */
if(CQ.wcm.SiteAdmin.hasListSelection()) {
var grid = CQ.Ext.getCmp(window.CQ_SiteAdmin_id + "-grid");
if (grid) {
var selections = grid.getSelectionModel().getSelections();
currentPagePath = selections[0].id;
}
} else { /* accessed via page */
currentPagePath = CQ.WCM.getPagePath();
}
If you define the servlet with a fixed paths property you don't have any reference to a Resource or Page
You either need to define resourceTypes that matches to a page component or use cq:Page, but this will then be active for every request to a page and is not recommended without at least some selectors
Then you can get the Resource with request.getResource(). To get a Page you'll need to adapt the ResourceResolver to a PageManager and use getContainingPage(Resource resource).
Have a look at the documentation:
http://sling.apache.org/documentation/the-sling-engine/servlets.html
request.getPathInfo() in this case presumably is /bin/fooServlet?[parameterString], which is why PageManager is returning null for its path — from the PageManager's point of view, no resource exists at this location.
One simple option would be to send an additional callingPage parameter when hitting the Servlet. This way you could just read it from the parameter map:
GET /bin/fooServlet?foo=bar&callingPage=/en/home.html
void doGet() {
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
String callingPage = request.getParameter("callingPage");
String callingPagePath = pageManager.getPage(callingPage).getPath();
}
I don't know if it's a good pratice, but perhaps you could use the referer.
import java.net.URI;
import java.net.URISyntaxException;
try {
String currentPagePath = new URI(request.getHeader("referer")).getPath();
} catch (java.net.URISyntaxException e) {
}

Adobe AEM (CQ) 5.6 Remove renditions workflow

I'm looking for a way (preferably a workflow) that removes/cleans renditions!
My problem is that over time I have loads of images with renditions that are no longer used.
Is there a good way to clean this and "reclaim" my disk space? :)
Though i would like to suggest you sling servlet route to remove as you will have more control over what should be deleted and from which path.
You can reuse some of the code from below as well.
I created a sample program a few weeks back to remove renditions except the original one whenever a new image was added and i was using workflows:
The code below was a component. A workflow was created and this class was then added as a process step to a workflow and the same workflow was set in any launcher and event type was created.
Basically, i used Query builder api and workflow api and was able to achieve the same. If you use servlet way as suggested you can take path as a parameter and then use query builder api to locate the renditions folder and then iterate over the same and remove the nodes.
Sample values that will be extracted via query builder:
http://localhost:4502/bin/querybuilder.json?path=%2fcontent%2fdam%2fgeometrixx%2ficons&property=jcr%3aprimaryType&property.1_value=nt%3afolder
public void execute(WorkItem item, WorkflowSession wfsession, MetaDataMap args)
throws WorkflowException {
try {
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
WorkflowData workflowData = item.getWorkflowData();
String path = workflowData.getPayload().toString();
path = path.replace("/jcr:content/renditions", "");
session = resourceResolver.adaptTo(Session.class);
Map<String, String> map = new HashMap<String, String>();
map.put("path", path);
map.put("property", "jcr:primaryType");
map.put("property.1_value", "nt:folder");
Query query = builder.createQuery(PredicateGroup.create(map), session);
SearchResult result = query.getResult();
List<Hit> hits = result.getHits();
Resource renditionResource = resourceResolver.resolve(hits.get(0).getPath());
Iterator<Resource> reneditionIterator = renditionResource.listChildren();
while(reneditionIterator.hasNext()){
Resource specificResource= reneditionIterator.next();
Node renditionNode = specificResource.adaptTo(Node.class);
if(!renditionNode.getName().equals("original")){
renditionNode.remove();
}
}
} catch (LoginException e) {
e.printStackTrace();
}
Servlet
ResourceResolver resourceResolver = slingHTTPrequest.getResourceResolver();
String path = slingHTTPrequest.getParameter("path");
session = resourceResolver.adaptTo(Session.class);
Map<String, String> map = new HashMap<String, String>();
map.put("path", path);
map.put("property", "jcr:primaryType");
map.put("property.1_value", "nt:folder");
Query query = builder.createQuery(PredicateGroup.create(map), session);
SearchResult result = query.getResult();
List<Hit> hits = result.getHits();
for(Hit hit: hits){
Resource renditionResource = resourceResolver.resolve(hit.getPath());
Iterator<Resource> reneditionIterator = renditionResource.listChildren();
while(reneditionIterator.hasNext()){
Resource specificResource= reneditionIterator.next();
Node renditionNode = specificResource.adaptTo(Node.class);
LoggerUtil.debugLog(this.getClass(),"Node name will be {}",renditionNode.getName());
if(!renditionNode.getName().equals("original")){
LoggerUtil.debugLog(this.getClass(), "removing rendition, parent node name is{}",renditionNode.getParent().getParent().getParent().getName());
renditionNode.remove();
}
}
}