14 October 2024
Kilobytes in a hurricane
26 February 2022
191
07 August 2020
9 and counting
02 January 2019
Encryption at rest
Another great thing about Postgres is how mature it is: every question we wanted to ask had in most cases already been answered on Stack Overflow.
24 April 2018
Links roundup: 6
- Sophia Ciocca trots out the New York Times's new text editor, Oak. Track Changes and Comments. Color me green with feature envy.
- The OLPC project isn't quite dead. Yet.
- John Cook lucidly explains posits, a possible improvement over the current floating-point standard.
- Linkrot? The Times is fighting Flashrot, trying to shrinkwrap 10-year old pages in its archive.
21 August 2016
Snappy
The Slack integrations with the various editing desks are a tasty idea.
21 November 2015
Save the tapes
26 January 2015
Big update
On the server side, we re-integrated podcast channels and episodes into the overall data model. Now, podcasts are first-class objects in the CMS, peers of blog posts, news stories, topic landing pages, bios, and other kinds of content. Podcasts now can be associated with multimedia assets like video—oh, and audio—just like everything else in our digital universe.
08 July 2014
Compare and contrast
- Scoop provides for multiple versions of draft/published stories: this blows the doors off CQ5's simple (but effective) author instance/publish instance model.
- Automatic smart cropping of images, given a master and a thumbnail. I've had clients that would love a feature like that.
- Locking body copy independently of assets like images and multimedia. Yep, that's also something that my guys demand.
- Tagging to an open standard: I haven't built any production code to support this, but it's something we have explored. Generally the stumbling block is the question of who owns the tags.
26 November 2013
No lockups
10 July 2013
Fun with CQ5: 4
/cf# wasn't there. How did this team disable the Content Finder by default?
Well, at least part of the answer is that the content pages have this property defined: cq:defaultView="html". The opposite setting is cq:defaultView="contentfinder". Here's the Adobe KB entry on the topic.
20 December 2012
Fun with CQ5: 3
12 September 2012
Fun with CQ5: 2
So to render a menu, it's a typical coding pattern to walk a subtree of tags, and for each one, find its corresponding web page (Node) and render its hyperlink. The only tricky bit is that com.day.cq.tagging.Tag.find() returns both Nodes that are explcitly marked with the specified tag as well as Nodes that are only subordinate to a Node that is assigned the tag. A lot of the time, you just want Nodes of the first kind.
We had been using a scheme that matched the Node's navigation title to the tag's title, but that pattern broke down when authors started designing menu items with duplicate names. (And there were other wrinkles.) So, after some desperate Friday afternoon whiteboard sketching, I realized that we could inquire of each candidate Node what tags were assigned to it explicitly, and test for an exact match of TagIDs. And I cooked up the following method, stripped of exception handling, debugging logic, and a little project-specific stuff. The cq:tags property is multi-valued, so there's a little more messy object navigation than you might expect.
import com.day.cq.tagging.Tag;
import java.util.Iterator;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.Value;
import org.apache.sling.api.resource.Resource;
* * *
/**
* Returns the node tagged with the specified tag; if there is more than one such node, returns
* one of them arbitrarily. The node must have the tag assigned directly to it as a property;
* nodes that are tagged by inheritance in the content tree are ignored.
* @param tag
* @return node: the node tagged with the specified tag, or null
*/
public static Node getMatchingNode(Tag tag) {
Node matchingNode = null;
Iterator<Resource> relatedNodes = tag.find();
outer:
while (relatedNodes.hasNext() && matchingNode == null) {
Node node = relatedNodes.next().adaptTo(Node.class);
PropertyIterator tagIds = node.getProperties("cq:tags");
while (tagIds.hasNext()) {
Property property = tagIds.nextProperty();
Value values[] = property.getValues();
for (Value value : values) {
String id = value.getString();
if (id.equals(tag.getTagID())) {
matchingNode = node;
break outer;
}
}
}
}
return matchingNode;
}
30 April 2012
Fun with CQ5: 1
I wrapped this functionality into a workflow process step, but it ought to work perfectly well as part of a command-line program, too. 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. (I haven't yet explored developing for CQ5 with standalone programs).
I created a workflow model that uses this process step, then I created an instance of the model and ran it, specifying the English > Products > Triangle page from the Geometrixx web site as the payload.
Here's the code, sanitized to remove client names and a little condensed. I'm using version 5.4.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
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 = "Siteworx"),
@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();
//get the file contents
String lipsum = null;
try {
BufferedReader is = new BufferedReader(new FileReader("e:\\Sandbox\\CQ5\\content.html"));
lipsum = readerToString(is);
}
catch (IOException e) {
log.error(e.toString(), e);
workflowSession.terminateWorkflow(workItem.getWorkflow());
return;
}
//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;
}
}
}
Here's the comp text in
e:\\Sandbox\\CQ5\\content.html:
Veggies sunt bona vobis, proinde vos postulo esse magis earthnut pea catsear cress sea lettuce quandong scallion rock melon seakale jÃcama komatsuna onion.
Bush tomato garbanzo beetroot caulie plantain sorrel swiss chard summer purslane celtuce salad seakale rutabaga radicchio lettuce spring onion groundnut soko peanut. Tigernut bitterleaf bush tomato celery corn garbanzo bamboo shoot cauliflower komatsuna cress sweet pepper mustard squash. Celtuce parsley kakadu plum coriander peanut garlic radish water chestnut tomatillo yarrow parsnip.
Squash endive collard greens tigernut bamboo shoot okra melon turnip. Rock melon amaranth ricebean pea chickpea nori bitterleaf spring onion bush tomato aubergine beetroot lotus root earthnut pea artichoke eggplant collard greens chard water spinach. Prairie turnip napa cabbage lettuce bush tomato garlic chickweed wattle seed potato lotus root pea sprouts leek kakadu plum. Radish leek green bean epazote water chestnut bamboo shoot celtuce taro tomatillo horseradish lettuce spring onion. Mustard taro prairie turnip horseradish wattle seed kohlrabi rock melon yarrow broccoli rabe fennel spinach celery collard greens gourd turnip.
Here's what the Websites window looks like after execution:
Here's what the corresponding node looks like in CRXDE Lite:
And here's what the page looks like in the content finder:
Some notes about the demonstrator code:
- I like to use lorem ipsum-style text (I got this text from Veggie Ipsum) when I'm testing. It's always clear that you're using test data rather than a copy of something live. And in the unlikely event that your test data leaks into production, it's a lot less embarrassing to see "lorem ipsum" than "asdf jkl; asdf jkl;" or "yo mama" in 16-point type, in my opinion.
- 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()andNode.getPrimaryNodeType(). The two methods are only rough complements; the setter takes a string but the getter returns a NodeType with various info inside it.
24 January 2012
No vanilla, just chocolate
11 August 2011
Not many happy campers
“When you try to build a product that works for everybody, it works for nobody,” a former AOL employee says.
(Link via The Morning News.)
15 June 2011
Content management ecosystems
We’ve finally begun to accept that no single CMS can handle all of a digital news organization’s content functions. A good content management system today is designed to interact with lots of other software. There’s now a genuine expectation that a CMS will play nicely with videos stored on YouTube, or comments managed by Disqus, or live chats embedded from CoverItLive. Other environments such as Facebook, Twitter and Tumblr come with their own suites of tools. And increasingly, what we call a “content management system” is actually a combo of multiple tightly-integrated systems.