Hierarchical SQL Queries in Adobe CQ5

December 13th, 2013
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.globalbin.web.servlets;

import java.io.IOException;
import java.io.Writer;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
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.Reference;
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.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
@Service(Servlet.class)
@Properties({
    @Property(name="service.description", value="MultiLevel Servlet"),
    @Property(name="service.vendor", value="The Global Bin"),
    @Property(name="sling.servlet.extensions", value="json"),
    @Property(name="sling.servlet.paths", value="/bin/multi")
})

public class MultiLevelQueryServlet extends SlingAllMethodsServlet {
   
    private static final long serialVersionUID = 1L;
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private Session session;
   
    @Reference
    private SlingRepository repository;
    protected void bindRepository(SlingRepository repository) {
        this.repository = repository;
    }
   
    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {

        String term = request.getParameter("q");
        String sqlQuery = "SELECT * FROM [cq:Page] AS parent INNER JOIN [nt:base] AS child "
         + "ON ISCHILDNODE(child,parent) WHERE ISDESCENDANTNODE(parent, '/content/geometrixx/en/events') AND "
         + "CONTAINS(child.*, '"+ term +"')";
               
        JSONArray found = sqlQueryJcr(sqlQuery);
        log.info(">> SQL QUERY: " + sqlQuery);
        response.setContentType("application/json");
        Writer w = response.getWriter();
        w.write(found.toString());
    }
   
    private JSONArray sqlQueryJcr(String queryString) {
        JSONArray results = new JSONArray();
        try {
            session = this.repository.loginAdministrative(null);
            QueryManager qm = session.getWorkspace().getQueryManager();
            Query query = qm.createQuery(queryString, Query.JCR_SQL2);
            RowIterator ri = query.execute().getRows();        
            while (ri.hasNext()) {
                  JSONObject dbObj = new JSONObject();
                  Row row = ri.nextRow();
                  Node parent = row.getNode("parent");
                  dbObj.put("result-"+parent.getPath(), nodeToJsonObject(parent));
                  results.put(dbObj);
            }
        } catch (RepositoryException re) {
            log.error(">>> ERROR:" + re.getMessage());
        } catch (Exception e) {
            log.error("> ERROR:" + e.getMessage());
        } finally {
            session.logout();
        }
        return results;
    }
   
    private JSONArray nodesToJsonArray(NodeIterator ni) {
        JSONArray jsonArray = new JSONArray();
        while (ni.hasNext()) {
            Node node = ni.nextNode();
            JSONObject obj = new JSONObject();
            try {
                obj.put( node.getName(), nodeToJsonObject(node));
            } catch (JSONException e) {
                log.error(e.getMessage());
            } catch (RepositoryException e) {
                log.error(e.getMessage());
            }
            jsonArray.put(obj);
        }
        return jsonArray;
    }
   
    private JSONObject nodeToJsonObject(Node node) {
        JSONObject obj = new JSONObject();
        try {
            PropertyIterator pi = node.getProperties();
            while (pi.hasNext()) {
                javax.jcr.Property prop = pi.nextProperty();
                if (prop.isMultiple()) {
                    obj.put(prop.getName(), "[multi]");
                } else {
                    obj.put(prop.getName(), prop.getString());                 
                }
            }
            NodeIterator children = node.getNodes();
            obj.put("nodes-of-" + node.getName(), nodesToJsonArray(children));
        } catch (JSONException e) {
            log.error(e.getMessage());
        } catch (RepositoryException e) {
            log.error(e.getMessage());
        }      
        return obj;
    }
}

Create A Template App with Secha Touch and PhoneGap

May 13th, 2013

Install the following packages:

  • PhoneGap SDK
  • Sencha Touch SDK
  • Sencha Cmd
  • Adroid Development Toolkit

Create this Bash script for running the Sencha Cmd and PhoneGap generators. You can add it your ~/bin directory or anywhere where it is visible from your file path. Edit the paths to match your specific versions of Sencha Touch and PhoneGap.

#!/bin/bash

PROJECT_DIR="anywhere"
PROJECT_NAME="Anywhere"
PACKAGE_NAME="com.globalbin.mobile"
DEV_DIR="~/Development"
SENCHA_SDK="~/touch-2.2.0"
SENCHA_CMD="~/bin/Sencha/Cmd/3.1.1.274"
CORDOVA_VER="2.7.0"
PHONEGAP="~/phonegap-2.7.0/lib"
APP_DIR=$DEV_DIR"/"$PROJECT_DIR

echo "Creating Sench Touch + PhoneGap App for Android:"
echo "SENCHA_SDK="$SENCHA_SDK
echo "SENCHA_CMD="$SENCHA_CMD
echo "PHONEGAP="$PHONEGAP
echo "-------------------"
echo "Project Name: " $PROJECT_NAME
echo "Package Name: " $PACKAGE_NAME
echo "Project Directory: " $APP_DIR

echo "Sencha Touch App..."
cd $SENCHA_SDK
pwd
$SENCHA_CMD/sencha generate app $PROJECT_NAME $APP_DIR
cp $PHONEGAP"/android/cordova-"$CORDOVA_VER".js" $APP_DIR
ls -al $APP_DIR

echo "PhoneGap App ..."
$PHONEGAP/android/bin/create $APP_DIR/build/$PROJECT_NAME/android $PACKAGE_NAME $PROJECT_NAME

Add these Ant build targets to the build.xml file:

   <target name="-after-build">
      <delete dir="${build.dir}/android/assets/www"/>
      <copy todir="${build.dir}/android/assets/www">
        <fileset dir="${build.dir}/package"/>
      </copy>
    </target>

Notes:
Sencha 2.2 had issues with Ruby 2.0 and I was getting Ant build errors (Compass failing).
I had to install GCC 4.2 which was no longer available in OSX Lion.

$ brew tap --repair homebrew/dupes
$ brew install apple-gcc42
$ export CC=/usr/local/bin/gcc-4.2
$ rvm pkg install readline
$ rvm install 1.9.3
$ gem update system
$ gem install compass

Run the build again:

$ sencha app build package && build/MyApp/android/cordova/debug

A Simple Backbone.js App

April 28th, 2013

Testing the CodeColorer plugin for WordPress. Here’s a snippet I’ve written for a simple model Page() with a collection PageList() and their corresponding views PageContentView() and PageListView().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
var Page = Backbone.Model.extend({
    defaults: {
        name: '',
        title: '',
        description: '',
        keywords: '',
        content: []
    },
    validate: function(attrs, options) {
        var name = attrs.name;
        if (name.match(/\s+/g) != null) {
            return "invalid name attribute";
        }
    },
    idAttribute: '_id',
    initialize: function () {
        this.on("change", function (model) {});
    }
});

var PageList = Backbone.Collection.extend({
    model: Page,
    url: "/api/page",
    idAttribute: '_id',
    initialize: function () {
        _.bindAll(this, 'retrieve');
        this.retrieve();
    },
    retrieve: function () {
        var self = this;
        this.fetch({
            success: function (collection, response, options) {
                self.reset();
                self.add(response);
                self.trigger("change");
            },
            error: function (collection, xhr, options) {
            }
        });
    }
});

var PageContentView = Backbone.View.extend({
    tagName: "tr",
    initialize: function () {
        _.bindAll(this, 'render');
        this.model.bind('change', this.render);
    },
    render: function () {
        var pageObj = {
            id: this.model.get("_id"),
            name: this.model.get("name"),
            title: this.model.get("title"),
            description: this.model.get("description"),
            keywords: this.model.get("keywords"),
            content: this.model.get("content")
        };
        this.$el.append(_.template($("#list-page-template").html(), pageObj));
        return this;
    }
});


var PageListView = Backbone.View.extend({
    el: '#page-list',
    initialize: function() {
         _.bindAll(this, 'render', 'renderPageRow', 'renderTemplate',
              'addPage','editPage','savePage','cancelEditPage','deletePage');
        this.collection = new PageList();
        this.collection.bind('change', this.render, this);
        this.collection.bind('remove', this.render, this);
    },


    events: {
        "click .add-page-button": "addPage",
        "click .edit-page-button": "editPage",
        "click .save-page-edit": "savePage",
        "click .cancel-page-edit": "cancelEditPage",
        "click .delete-page-button": "deletePage"
    },

    render: function () {
        var self = this;
        this.$el.html("");
        this.$el.append(this.renderTemplate("#add-page-template"));
        _(this.collection.models).each(function (item) {
            self.renderPageRow(item);
        }, this);
        return this;
    },

    renderTemplate: function (name, obj) {
        return _.template($(name).html(), obj);
    },

    renderPageRow: function (item) {
        var pageItem = new PageContentView({model: item});
        this.$el.append(pageItem.render().el);
    },

    addPage: function (e) {
        e.preventDefault();
        var newPage = new Page({
            name: $('#page-name').val(),
            title: $('#page-title').val()
        });
        this.collection.add(newPage, {merge: true, at: 0});
        newPage.save({}, {
            success: function () {
            },
            error: function () {
            }
        });
        return false;
    },

    editPage: function (e) {
        e.preventDefault();
        var model = this.collection.get(e.target.value);
        var el = $(e.target).closest('tr');
        el.empty();

        var content = model.get("content");
        var selectedIDs = [];

        console.debug("content-attribute:", typeof content);
        if (typeof content === "string") {
            selectedIDs = content.split("|");
            console.debug("selectedIDs split: ", selectedIDs);
        } else if (typeof content === "object") {
            selectedIDs = _.pluck(model.get("content"),"_id");
            console.debug("selectedIDs plucked: ", selectedIDs);
        }

        var textList = [];
        _.each(textListView.collection.models, function(item){
            var menuItem = {
                _id: item.get("_id"),
                name: item.get("name")
            };
            textList.push(menuItem);
        });
        textList.sort(function(a,b){
            if (a.name == b.name) return 0;
            if (a.name < b.name) return -1;
            else return 1;
        });

        var pageObj = {
            id: model.get("_id"),
            name: model.get("name"),
            title: model.get("title"),
            description: model.get("description"),
            keywords: model.get("keywords"),
            content: selectedIDs,
            textList: textList
        };

        var template = this.renderTemplate("#edit-page-template",pageObj);
        el.append($(template));
        return false;
    },

    savePage: function (e) {
        e.preventDefault();
        var model = this.collection.get(e.target.value);
        var id = model.get("_id");

        var pageObj = {
            title: $('#title-'+id).val(),
            description: $('#desc-'+id).val(),
            keywords: $('#keywords-'+id).val()
        };

        if (model) {
            model.set(pageObj);
        }

        var selectedIDs = [];
        $("#content-"+id+" option:selected")
            .each(function(){
                selectedIDs.push($(this).val());
            });

        if (selectedIDs.length)
            model.set("content", selectedIDs.join("|"));
        else
            model.set("content", null);

        model.save({},{
            success: function() {},
            error: function() {}
        });
        return false;
    },

    cancelEditPage: function (e) {
        e.preventDefault();
        this.render();
        return false;
    },

    deletePage: function (e) {
        e.preventDefault();
        var model = this.collection.get(e.target.value);
        model.destroy({
            success: function(model, response, options) {
                console.debug("deleted", model);
            },
            error: function(model, xhr, options) {
                console.debug("error deleting");
            }
        });
        return false;
    }
});

Recent Video Projects

September 22nd, 2011

Caitlin Mae’s 18th Birthday [Online Edit]

Special performance by the family

Other recent video projects

Photography Notes

February 9th, 2011

Photography is just like any other visual art form. It involves appreciation of art basics we learned back in high school: shape, balance, composition, tonal values (light vs. dark), color theory, texture, pattern, perspective, form. On another level, it could be about capturing your emotion at a particular instant, a report of a historic event as it unfolds, a memento of significant life events kept for posterity, or a visual means to sell an idea.

When a picture is taken, one must ask: what is the subject? Why is it interesting? What particular attribute of the subject do you want to capture? How could this quality be best presented visually? As the person taking this picture, you have a choice of keeping everything in sharp focus or lead the eye to a particular center of interest.

You also decide how fast or how long you want the camera to take this slice of time (shutter speed). This can be a tiny fraction like 1/250th of a second, several minutes, or even hours if your camera allows it. In the grand scheme of things, time really is relative.

Perhaps one of the most important aspect of photography is how you frame a shot (aka composition). You decide what goes inside that frame and more importantly, what gets excluded. A traditional photograph is a two dimensional representation of the three dimensional world we live in. That in itself is part of the challenge: a magical photograph can engulf the viewer in a total immersive experience.

Photography is about understanding light and its qualities. You need to be aware from which direction this light is coming from and what type it is: your on camera flash, the street lamp to the left of your subject, or the setting sun behind it. Different light sources emit a dominant color in the visual spectrum. A tungsten lamp tends to be warm or yellowish, while some daylight are more biased towards cooler or bluish hues. A light source has shape and its size matters.

You choose where to stand when you take a photograph. This vantage point influences how the viewer might interpret your relationship with your subject. For example you can make a person appear to be powerful by shooting from a low vantage point with a wide angle lens and you can do the opposite say by shooting from the top. Photojournalist Robert Capa once said: “If your photos are not good enough, you are not close enough”.

Be brutal with editing. Developing one’s personal preference on what is visually appealing demand critical assessment and the courage to admit when it is not. Editing a large pile of photographs down to the few good ones is your responsibility, not the viewer’s.

Take photos of those subjects that really matter to you. Explore the different genres. Study the works of famous photographers and what it is about their work that makes it important. They may or may not resonate with your own aesthetic and that is perfectly OK. Develop your personal vision by seeking inspiration and being open to constructive critique.

Learn the rules so you know how (and when) to break them.

Photography is storytelling. By choosing where you point your camera, you are sharing with the world those things that matter most to you. The camera reveals the photographer’s biases and there is no such thing as an unbiased photograph.

Ads for the latest cameras can sometimes be a little misleading in the sense that they want you to believe all you have to do is press the button and it will magically do the rest. I myself was once fooled that photography was the quickest way to produce instant art. Happy to report it was not so. Pretty much every special skill in life takes dedication of time and effort to learn.