I am new to Adobe cq5. Went through many online blogs and tutorials but could not get much. Can any one provide a Adobe cq5 application example with detailed explanation that can store and retrieve data in JCR.
Thanks in advance.
Here's a snippet for CQ 5.4 to get you started. It inserts a content page and text (as a parsys) at an arbitrary position in the content hierarchy. The position is supplied by a workflow payload, but you could write something that runs from the command line and use any valid CRX path instead. The advantage of making it a process step is that you get a session established for you, and the navigation to the insert point has been taken care of.
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import org.apache.sling.jcr.resource.JcrResourceConstants;
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.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.exec.WorkflowData;
import com.day.cq.workflow.exec.WorkflowProcess;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.cq.wcm.api.NameConstants;
#Component
#Service
#Properties({
#Property(name = Constants.SERVICE_DESCRIPTION,
value = "Makes a new tree of nodes, subordinate to the payload node, from the content of a file."),
#Property(name = Constants.SERVICE_VENDOR, value = "Acme Coders, LLC"),
#Property(name = "process.label", value = "Make new nodes from file")})
public class PageNodesFromFile implements WorkflowProcess {
private static final Logger log = LoggerFactory.getLogger(PageNodesFromFile.class);
private static final String TYPE_JCR_PATH = "JCR_PATH";
* * *
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap args)
throws WorkflowException {
//get the payload
WorkflowData workflowData = workItem.getWorkflowData();
if (!workflowData.getPayloadType().equals(TYPE_JCR_PATH)) {
log.warn("unusable workflow payload type: " + workflowData.getPayloadType());
workflowSession.terminateWorkflow(workItem.getWorkflow());
return;
}
String payloadString = workflowData.getPayload().toString();
//the text to be inserted
String lipsum = "Lorem ipsum...";
//set up some node info
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("d-MMM-yyyy-HH-mm-ss");
String newRootNodeName = "demo-page-" + simpleDateFormat.format(new Date());
SimpleDateFormat simpleDateFormatSpaces = new SimpleDateFormat("d MMM yyyy HH:mm:ss");
String newRootNodeTitle = "Demo page: " + simpleDateFormatSpaces.format(new Date());
//insert the nodes
try {
Node parentNode = (Node) workflowSession.getSession().getItem(payloadString);
Node pageNode = parentNode.addNode(newRootNodeName);
pageNode.setPrimaryType(NameConstants.NT_PAGE); //cq:Page
Node contentNode = pageNode.addNode(Node.JCR_CONTENT); //jcr:content
contentNode.setPrimaryType("cq:PageContent"); //or use MigrationConstants.TYPE_CQ_PAGE_CONTENT
//from com.day.cq.compat.migration
contentNode.setProperty(javax.jcr.Property.JCR_TITLE, newRootNodeTitle); //jcr:title
contentNode.setProperty(NameConstants.PN_TEMPLATE,
"/apps/geometrixx/templates/contentpage"); //cq:template
contentNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"geometrixx/components/contentpage"); //sling:resourceType
Node parsysNode = contentNode.addNode("par");
parsysNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"foundation/components/parsys");
Node textNode = parsysNode.addNode("text");
textNode.setProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
"foundation/components/text");
textNode.setProperty("text", lipsum);
textNode.setProperty("textIsRich", true);
workflowSession.getSession().save();
}
catch (RepositoryException e) {
log.error(e.toString(), e);
workflowSession.terminateWorkflow(workItem.getWorkflow());
return;
}
}
}
I have posted further details and discussion.
A few other points:
I incorporated a timestamp into the name and title of the content
page to be inserted. That way, you can run many code and test cycles
without cleaning up your repository, and you know which test was the
most recently run. Added bonus: no duplicate file names, no
ambiguity.
Adobe and Day have been inconsistent about providing constants for
property values, node types, and suchlike. I used the constants that
I could find, and used literal strings elsewhere.
I did not fill in properties like the last-modified date. In code for
production I would do so.
I found myself confused by Node.setPrimaryType() and
Node.getPrimaryNodeType(). The two methods are only rough
complements; the setter takes a string but the getter returns a
NodeType with various info inside it.
In my original version of this code, I read the text to be inserted from a file, rather than just using the static string "Lorem ipsum..."
Once you've worked through this example, you should be able to use the Abobe docs to write code that reads data back from the CRX.
If you want to learn how to write a CQ application that can store and query data from the CQ JRC, see this article:
http://scottsdigitalcommunity.blogspot.ca/2013/02/querying-adobe-experience-manager-data.html
This provides a step by step guide and walks you right through the entire processes - including building the OSGi bundle using Maven.
FRom the comments above - I see reference to BND file. You should stay away from CRXDE to create OSGi and use Maven.
Related
I'm trying to work with DynamoDb streams, I am using the example code shown in this article. I've modified it to work in a basic Spring Boot app (initializr), utilizing an existing DynamoDb table which has streams enabled. Everything appears to work, however; I'm not seeing any new updates.
This particular database has a bulk update once per day at a specific time, it may get some minor changes now and then during the day. I'm trying to monitor these minor updates. When I run the application I can see the records from the bulk update, however if my application is running and I use the AWS Console to modify, create or delete a record I don't seem to get any output.
I'm using:
Spring Boot:2.3.9.RELEASE
amazon-kinesis-client:1.14.2
Java 11
Running on Mac Catalina (though that shouldn't matter)
In my test application I did the following:
package com.test.dynamodb_streams_test_kcl.service;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBStreams;
import com.amazonaws.services.dynamodbv2.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
#Slf4j
#Service
#RequiredArgsConstructor
public class LowLevelKclProcessor {
private static final String dynamoDbTableName = "global-items";
private final AmazonDynamoDB dynamoDB;
private final AmazonDynamoDBStreams dynamoDBStreams;
private final ZonedDateTime startTime = ZonedDateTime.now();
#PostConstruct
public void initialize() {
log.info("Describing table={}", dynamoDbTableName);
DescribeTableResult itemTableDescription = dynamoDB.describeTable(dynamoDbTableName);
log.info("Got description");
String itemTableStreamArn = itemTableDescription.getTable().getLatestStreamArn();
log.info("Got stream arn ({}) for table={} tableArn={}", itemTableStreamArn,
itemTableDescription.getTable().getTableName(), itemTableDescription.getTable().getTableArn());
// Get all the shard IDs from the stream. Note that DescribeStream returns
// the shard IDs one page at a time.
String lastEvaluatedShardId = null;
do {
DescribeStreamResult describeStreamResult = dynamoDBStreams.describeStream(
new DescribeStreamRequest()
.withStreamArn(itemTableStreamArn)
.withExclusiveStartShardId(lastEvaluatedShardId));
List<Shard> shards = describeStreamResult.getStreamDescription().getShards();
// Process each shard on this page
for (Shard shard : shards) {
String shardId = shard.getShardId();
System.out.println("Shard: " + shard);
// Get an iterator for the current shard
GetShardIteratorRequest getShardIteratorRequest = new GetShardIteratorRequest()
.withStreamArn(itemTableStreamArn)
.withShardId(shardId)
.withShardIteratorType(ShardIteratorType.LATEST);
GetShardIteratorResult getShardIteratorResult =
dynamoDBStreams.getShardIterator(getShardIteratorRequest);
String currentShardIter = getShardIteratorResult.getShardIterator();
// Shard iterator is not null until the Shard is sealed (marked as READ_ONLY).
// To prevent running the loop until the Shard is sealed, which will be on average
// 4 hours, we process only the items that were written into DynamoDB and then exit.
int processedRecordCount = 0;
while (currentShardIter != null && processedRecordCount < 100) {
System.out.println(" Shard iterator: " + currentShardIter.substring(380));
// Use the shard iterator to read the stream records
GetRecordsResult getRecordsResult = dynamoDBStreams.getRecords(new GetRecordsRequest()
.withShardIterator(currentShardIter));
List<Record> records = getRecordsResult.getRecords();
for (Record record : records) {
// I set a breakpoint on the line below, but it was never hit after the bulk update info
if (startTime.isBefore(ZonedDateTime.ofInstant(record.getDynamodb()
.getApproximateCreationDateTime().toInstant(), ZoneId.systemDefault()))) {
System.out.println(" " + record.getDynamodb());
}
}
processedRecordCount += records.size();
currentShardIter = getRecordsResult.getNextShardIterator();
}
}
// If LastEvaluatedShardId is set, then there is
// at least one more page of shard IDs to retrieve
lastEvaluatedShardId = describeStreamResult.getStreamDescription().getLastEvaluatedShardId();
} while (lastEvaluatedShardId != null);
}
}
Note that your test is based on the low-level API, not on the Kenisis client library. So it's normal to have some tricky technical details to deal with.
Your test application has some similarities with the example given in the doc, but it has issues:
When I run the application I can see the records from the bulk update
ShardIteratorType.LATEST will not look for old records that happened before running the test (It starts reading just after the most recent stream records in the shard)
So, I will assume that the iterator type was different (ex: TRIM_HORIZON) and changed later to LATEST during your tests.
The main issue comes from the fact that your application will sequentially poll shards, and it will bloque in the first shard until it finds 100 new records in this shard (due to LATEST iterator type).
So, you may not see the new minor changes while the test is running if they belong to a different shard.
Solutions:
1- Poll shards in parallel using threads.
2- Filter returned shards using the sequence number of the last logged record, and try to guess the shard that may contain minor changes.
3- Dangerous & I'm not sure if it works :)
In a test table, and if your data model allows this: close the current stream, and enable a new one, then make sure that all your writes belong to one partition. In the majority of cases, table partitions have a one-to-one relationship with active shards. Theoretically, you have only one active shard to deal with.
I am trying to program a MODULE for the Ignition SDK but I am running into problems with the paths of the strings in the .properties file not working properly.
I have a file called
ProfileSettings.properties
and one called
ProfileSettings.java
In .properties file, I have the following strings:
Category.Settings=Connection
ConnectionString.Name=Connection String
ConnectionString.Desc=Connection String for the IoT Hub device
MaxTime.Name=Maximum time
MaxTime.Desc=The time spent
MaxMessages.Name=Maximum to collect
MaxMessages.Desc=will be collected
and in the .java file, I have reference to the strings by using
public static final StringField connectionString = new StringField(META, "ConnectionString");
public static final IntField maxTime = new IntField(META, "MaxTime");
public static final IntField maxMessages = new IntField(META, "MaxMessages");
Category CONNECTION_CATEGORY = new Category("ProfileSettings.Category.Connection", 1001)
.include(connectionString, maxTime, maxMessages);
but when I load the module into the gateway and look at the configuration page, I get ¿ProfileSettings.ConnectionString.Name? where it shows question marks
around the path and not the actual text needed for all the strings
Maybe try using the complete field names?
public static final StringField connectionString = new StringField(META, "ConnectionString.Name");
Or possibly
public static final StringField connectionStringName = new StringField(META, "ConnectionString.Name");
It would be helpful to have more information about what and where those files are from. Is the .properties file or properties.java something you wrote or is that something that comes as part of the SDK?
I'm a Flex newbie and I've searched both StackOverflow and Google'd but can't seem to figure out this (simple) problem with Flex/ActionScript 3 Asynchronous programming. I have a PHP service (Zend) that inserts a row into the table. What I would like to do is be able to call the service twice consecultively with different row values, and get back the new IDs (primary keys) returned by the service. Then I display the new IDs as 2 Alerts.
The problem is when I can the service twice in a row, I only get an Alert.show() for the last and most recent service call. I don't know how to access the result of the first.
I recognize I can reconfigure my Flex code and PHP service to accept an array of objects and just send a single PHP service call for both and receive back an array object with the results, but I'm also trying to get a better understanding in general how AsyncToken is working and how to access old results that I need. Is each use of {serviceResult}.token overwriting my previous result?
<s:NavigatorContent xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
// ...
Here is my related code where I want to set up 2 default folders for new users "Home" and "temp":
// imports
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexEvent;
import mx.rpc.AsyncToken;
import mx.rpc.IResponder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.rpc.Responder;
import spark.events.IndexChangeEvent;
import valueObjects.Folders;
// When the user clicks "Save" button, 2 new folders are created for a new user:
protected function pendingUserSaveButton_clickHandler(event:MouseEvent):void
{
// Create 2 initial user folders: Home and temp-taiwan
var t1Folders:Folders = new Folders();
t1Folders.users_email = tempUser.email;
t1Folders.name = t1Folders.description = "Home";
createFoldersFunction( t1Folders ); // assume returns folder ID = 100
var t2Folders:Folders = new Folders();
t2Folders.users_email = tempUser.email;
t2Folders.name = t2Folders.description = "temp";
createFoldersFunction( t2Folders ); // assume returns folder ID = 101
}
and here are my event handlers, and I want an Alert box for each new ID to pop up:
protected function createFoldersFunction(item:Folders):void
{
createFoldersResult.token = foldersService.createFolders(item);
}
protected function createFoldersResult_resultHandler(event:ResultEvent):void
{
Alert.show("Folder #" + ((event.result as int) as String) + " created");
// Currently, I only get Alert saying "Folder #101 created".
// I want to see 2 Alerts - one for #100 and another for #101
}
and here are my mx codes for callresponder and service:
<s:CallResponder id="createFoldersResult"
result="createFoldersResult_resultHandler(event)"/>
<foldersservice:FoldersService id="foldersService"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"
showBusyCursor="true"/>
Only '101' (the result of second service call) is triggering an Alert. Why is this?
Thank you!
Your code is overriding the token that createFoldersResult should respond to. A proper code would be:
protected function createFoldersFunction(item:Folders):void
{
var token:AsyncToken = foldersService.createFolders(item);
var responder:Responder = new Responder(createFoldersResult_resultHandler, someFaultHandler)
token.addResponder(responder);
}
another option would be set up the result handler for createFolders directly in the mxml, so your foldersservice:FoldersService would be:
<foldersservice:FoldersService id="foldersService"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"
showBusyCursor="true">
<mx:method name="createFolders" result="createFoldersResult_resultHandler(event)"/>
</foldersservice:FoldersService>
then you don't need createFoldersFunction, you can call foldersService.createFolders directly.
I've got a Scala program that downloads and parses html. I got the links to the image files form the html, Now I need to transfer those images to my hard drive. I'm wondering what the best Scala method I should use.
my connection code:
import java.net._
import java.io._
import _root_.java.io.Reader
import org.xml.sax.InputSource
import scala.xml._
def parse(sUrl:String) = {
var url = new URL(sUrl)
var connect = url.openConnection
var sorce:InputSource = new InputSource
var neo = new TagSoupFactoryAdapter //load sUrl
var input = connect.getInputStream
sorce.setByteStream(input)
xml = neo.loadXML(sorce)
input.close
}
My blog
Then you may want to take a look at java2s. Although the solution is in plain Java but you can still modify to Scala syntax to "just use it"
An alternative option is to use the system commands which is much cleaner
import sys.process._
import java.net.URL
import java.io.File
object Downloader {
def start(location: String) : Unit = {
val url = new URL(location)
var path = url match {
case UrlyBurd(protocol, host, port, path) => (if (path == "") "/" else path)
}
path = path.substring(path.lastIndexOf("/") + 1)
url #> new File(path) !!
}
}
object UrlyBurd {
def unapply(in: java.net.URL) = Some((
in.getProtocol,
in.getHost,
in.getPort,
in.getPath
))
}
One way to achieve that is: collect the URLs of the images and ask for them to the server (open a new connection with the image url and store the bytestream in the hard drive)
Quick version:
How do I get an image that was generated on the users browser back to the server?
The current plan is this:
The Flash developer will convert the bitmap to JPEG
He will then POST the JPEG to a page on the site.
I'm thinking I can create a WebService which will use a StreamReader to read the post and save it as a file.
Would that work? Any existing code/samples for doing this?
I suppose we should be able to look at code for doing any file upload to ASP.NET.
In this example, I've created a Flash file with a button on the stage. When you click that button, the Flash sends the image of the button to an ASPX file which saves it out as a JPEG. As you'll see this is done by drawing the DisplayObject into a BitmapData object and as such, you can easily replace the reference to the button with anything that inherits from DisplayObject (including a movie clip that contains the canvas for a paint application etc).
I’ll walk you through the Flash element first and then the .NET backend.
Flash
To send a generated image like this from Flash to ASP.NET (or any other backend) you’re going to need a couple of 3rd party libraries. We’ll need a JPEG Encoder (which Flash doesn’t have, but recent versions of Flex do) which we can get from the AS3 Core Lib http://code.google.com/p/as3corelib/. We’ll also need a base64 encoder for sending the data over the wire. I’ll use the one from Dynamic Flash, available at http://dynamicflash.com/goodies/base64/.
Download these and extract them somewhere sensible on your hard disk (like a C:\lib folder).
I created a new AS3 Flash file and saved it as uploader.fla. I added a button component to the stage and named it btnUpload. Next I edited the ActionScript settings and added my c:\lib folder to the classpath. Then I gave the document a class name of Uploader and saved the file.
Next, I created an ActionScript file and added the following code to it:
package
{
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.utils.ByteArray;
import fl.controls.Button;
import com.adobe.images.JPGEncoder;
import com.dynamicflash.util.Base64;
public class Uploader extends MovieClip
{
// Reference to the button on the stage
public var btnUpload:Button;
// Encoder quality
private var _jpegQuality:int = 100;
// Path to the upload script
private var _uploadPath:String = "/upload.aspx";
public function Uploader()
{
btnUpload.addEventListener(MouseEvent.CLICK, buttonClick);
}
private function buttonClick(e:MouseEvent):void
{
// Create a new BitmapData object the size of the upload button.
// We're going to send the image of the button to the server.
var image:BitmapData = new BitmapData(btnUpload.width, btnUpload.height);
// Draw the button into the BitmapData
image.draw(btnUpload);
// Encode the BitmapData into a ByteArray
var enc:JPGEncoder = new JPGEncoder(_jpegQuality);
var bytes:ByteArray = enc.encode(image);
// and convert the ByteArray to a Base64 encoded string
var base64Bytes:String = Base64.encodeByteArray(bytes);
// Add the string to a URLVariables object
var vars:URLVariables = new URLVariables();
vars.imageData = base64Bytes;
// and send it over the wire via HTTP POST
var url:URLRequest = new URLRequest(_uploadPath);
url.data = vars;
url.method = URLRequestMethod.POST;
var loader:URLLoader = new URLLoader();
loader.load(url);
}
}
}
I saved this file next to the FLA with the name Uploader.as.
I published the SWF into the root of my Asp.NET website.
This code assumes you want to upload the jpeg with a quality of 100% and that the script which will receive the data is called upload.aspx and is located in the root of the site.
ASP.NET
In the root of my website I created a WebForm named upload.aspx. In the .aspx file, i removed all the content apart from the page directive. It’s content look like this:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="upload.aspx.cs" Inherits="upload" %>
Then in the CodeBehind, I added the following:
using System;
using System.IO;
public partial class upload : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Get the data from the POST array
string data = Request.Form["imageData"];
// Decode the bytes from the Base64 string
byte[] bytes = Convert.FromBase64String(data);
// Write the jpeg to disk
string path = Server.MapPath("~/save.jpg");
File.WriteAllBytes(path, bytes);
// Clear the response and send a Flash variable back to the URL Loader
Response.Clear();
Response.ContentType = "text/plain";
Response.Write("ok=ok");
}
}
There are obviously hard-coded values such as the save path but from this you should be able to create whatever system you require.
If you need to manipulate the image, as long as you can get a byte[] or a Stream of the POSTed file, you can create an image of it, e.g.
MemoryStream mstr = new MemoryStream(myByteArray);
Image myImage = Image.FromStream(mstr);
Have him post the files like a standard HTML form. You can access those files in the Page_Load event of the page he is posting to by using the following collection
Request.Files
This will return a collection of HttpPostedFiles just like what a FileUpload control does.