Category: Alfresco Tutorials

Creating custom Alfresco admin console panels

A common requirement for an Alfresco installation is the need to update configuration on customizations at runtime. One way to handle this is to add your own custom admin console panels to the out-of-the-box Alfresco admin console.

For example, suppose that at our company there is a system that manages data about our projects. Maybe it has a REST API that returns a list of projects. And, maybe we want to do something with that data from Alfresco.

It would be a good idea to centralize the logic that invokes the project API in a service class. The service class will need to know the URL of the project API. We could hardcode the project API URL, but then we’d have to build and deploy if it ever needs to be changed. We could put it in the alfresco-global.properties file and read it from there, which is a little better, but not great.

Alfresco already has an admin console that it uses to display and, in the case of Enterprise Edition, manage settings at runtime. This post shows how to add your own admin console panel to update settings of your custom service classes at runtime whether you use Enterprise or Community Edition.

This example uses a basic API built using Node and Express that returns a list of project names and IDs. In the real world your API won’t likely be part of the project–we use this “fake” API here as a convenient stand-in for demonstration purposes.

On the Alfresco side, we’ll create a UI action that gets the project list from the API and displays the projects in a dialog. It doesn’t really matter what we do with the data because the point of this example is to show how to make the API endpoint configurable via the admin console.

When you’re done, you’ll be able to invoke the API from Share and then change the API endpoint via the Alfresco admin console, and see that change reflected immediately on subsequent invocations from Share.

Ready? Let’s do it.

Steps

This code is available in GitHub. Each of the steps below is tagged. After you clone the repository you can switch between steps by doing git checkout step1 or git checkout step2 etc.

If you are going to follow along, clone the project:

git clone https://github.com/jpotts/alfresco-custom-admin.git

Then, checkout step0:

git checkout step0

The code now reflects an empty project created from the Alfresco SDK. At this point it is configured to use Community Edition. We’ll optionally switch over to Enterprise Edition in a while when the implementation steps become edition-specific.

The first three steps are about setting up the fake API as a Docker container, implementing a repo tier web script to fetch project data from the API, and then implementing a simple UI action that displays the project data in a Share dialog.

Once those steps are complete the path diverges: one step shows how to implement the custom admin console in Enterprise Edition and the other shows how to do it in Community Edition.

Step 1: Add a simple API to return project data

If you are following along:

git checkout step1

We want an API that returns a list of projects as JSON. A really easy way to do that is with Node.js and Express. So, we’ll add a new container to our Docker setup so that when we run Alfresco locally it will launch the fake API app.

Now we have a new Maven module called fake-projects-api that will build a Docker image that has the Node.js app. All we need to do is add it to the docker-compose.yml file that was bootstrapped when we created the project so that Docker Compose will create and run a container for the API alongside the containers for ACS, Share, and the others.

  • docker/docker-compose.yml. This is the original docker-compose.yml file provided by the Alfresco SDK, updated to include the new projects API.

If you want to test the API, run ./run.sh build_start then invoke this URL in your browser:

http://localhost:3000/projects

The API returns a list of project names and IDs as JSON.

Step 2: Add a service class and web script

If you are following along:

git checkout step2

At this point Alfresco is running and a Node.js app is running. The Node.js app represents our other enterprise API that we need to integrate with. In this step we’ll wire the two together by creating a repository tier web script that invokes the API and returns the list of projects. The web script won’t actually have the logic to call the API. Instead, we’ll put that in a service class to centralize the logic and make it easier to call from other places if needed.

I’ll create two model classes to make it easier to work with the JSON API response:

The service has the logic that actually invokes the API, and the web script calls that:

I’ll put the default value for the project service API URL in the alfresco-global.properties file. The web script Spring context can read it from there and inject it into the Project Service class.

Now you can run ./run.sh build_start from the root of the project source and after everything starts up, invoke the web script:

http://localhost:8080/alfresco/s/ecmarchitect/projects

To get a list of project names and IDs back as JSON.

[
    {
        "projectId": "A001",
        "projectName": "Some Project"
    },
    {
        "projectId": "A002",
        "projectName": "Some Other Project"
    },
    {
        "projectId": "A003",
        "projectName": "Yet Another Project"
    },
    {
        "projectId": "A004",
        "projectName": "Pilot Project"
    },
    {
        "projectId": "A005",
        "projectName": "Pet Project"
    }
]

Next, let’s add a small customization to Share that will invoke this API.

Step 3: Add a UI action that invokes the repo tier web script

If you are following along:

git checkout step3

The Share part of this example is not really important, but it is nice to have it just to verify that everything is working as we expect.

Let’s add a UI action that invokes client-side JavaScript. The client-side JavaScript will call the repo tier web script and display the results in a Share dialog.

If nothing is running, do a ./run.sh build_start to start everything up. If everything is still running from the previous step, run ./run.sh reload_share.

You should now be able to log in to Share, navigate to a document, and from either the document library or document details page click “Get Projects” to show the dialog with the list of project information retrieved from the API.

UI action showing API results

Enterprise Edition or Community Edition?

You now have a fake API, a repo web script that invokes it, and a Share UI action that calls the repo web script. Now it is time to add an admin console panel that can be used to configure the projects API URL at runtime.

Exactly how you do this differs between Enterprise Edition and Community Edition. It’s a bit easier with Enterprise Edition because the Enterprise Edition admin console supports JMX and it has a framework of sorts for displaying and modifying JMX-managed beans. In that case, we just add some JMX annotations to our service class and it becomes very easy to configure an admin panel that can get and set attributes on the JMX bean.

To see how that works, continue reading the next section, Step 4a.

If you are using Community Edition, it’s a bit more work because the admin console framework in Community Edition isn’t the same as the one in Enterprise Edition that relies heavily on JMX. But we can still add an admin console panel that will function just like the one in Enterprise Edition, from an end-user’s perspective.

To see how that works, skip the next section, Step 4a, and continue reading Step 4b.

Step 4a: Add the custom admin console panel to Enterprise Edition

If you are following along:

git checkout step4a

Up to this point, our project has been running Community Edition. If you are reading this section you’ve decided to move forward with Enterprise Edition, so the first change is to update the root pom.xml to change editions:

Of course you’ll need credentials for the private artifacts directory as well as quay.io for the Enterprise Docker image. I am assuming you already know where to put the credentials in your Maven and Docker authentication setup.

Next, the project service we created earlier needs some JMX annotations and the service-context.xml file needs a few additional JMX-related beans:

At this point, you could change the projects API URL at runtime using a JMX tool like JConsole or JMXTerm. But our goal is to create a custom admin panel. Admin console panels are just web scripts, so we’ll add a GET web script that renders the panel:

Be careful here. The path to the web script is important. It dictates where the panel shows up in the admin console. In this case, the important part of the path is “admin/ecmarchitect-tools”. That establishes a new section (“ecmarchitect-tools”) that we could use to group multiple panels. If we were to just use “admin” this new panel would show up in the “root” list of admin console panels, as a sibling to “System Summary”. It’s probably better to group them as I’ve done here.

Enterprise Edition comes with JMX support, and that makes it very easy to add our JMX managed beans to the admin console. Look at the web script controller. It’s just:

Admin.initModel(
    "ecmarchitect:name=Projects",
    ["ProjectsApiUrl"],
    "ecmarchitect-projects"
);

The initModel method of Admin takes care of everything for us. We just pass in three arguments:

  • The name of the bean. This value matches up with what’s in the “ecmarchitect-projects_MBeanExporter” Spring bean in service-context.xml.
  • An array of managed attributes. In this simple example there’s just one, ProjectsApiUrl, which is derived from the getter/setter method in the project service class.
  • The doc in admin-common.lib.js says this is the “ID of the current console tool” but it did not seem to matter what I put here, so I used values from my JMX bean to make a unique string, “ecmarchitect-projects”.

The freemaker view for this web script is also pretty succinct because the Enterprise Edition admin console framework does a lot of the work for us:

<#include "/org/alfresco/enterprise/repository/admin/admin-template.ftl" />
<@page title=msg("project-api-settings-console.title") readonly=false>
    <div class="column-full">
        <@section label=msg("project-api-settings-console.column") />
        <#-- Example - Retrieve keys - which are attribute names - use to index into attribute hash -->
        <#-- You can index directly by attribute name e.g. <@control attribute=attributes["Subject"] /> -->
        <#list attributes?keys as a>
            <@control attribute=attributes[a] />
        </#list>
    </div>
</@page>

This is all boilerplate with the exception of the message properties keys.

Since the last step, you’ve changed the Alfresco edition, so now you need to start fresh:

  1. Run ./run.sh purge to destroy and remove the existing containers.
  2. Run ./run.sh build_start to do a clean build and start up fresh containers.

Once everything comes up you can test your work:

  1. Log in to the admin console and click TBD to see the new admin console panel. The projects API URL is already set because the default value
    from the alfresco-global.properties file was injected into the Project Service class.
  2. Go ahead and change the URL to a bad value, then click Save.
  3. Now go into Share and try out the UI action. It should fail.
  4. Go back into the admin console and change the URL to the correct value, which is http://fake-projects-api:3000/projects, then click Save.
  5. Now go back into Share and try the UI action again. It should display the project data successfully.

Custom admin console panel in Enterprise Edition

If you only care about Enterprise Edition, you’re done. You can make changes to the projects API URL at runtime via the admin console.

Note that if you deploy what we’ve built so far to a multi-node cluster, each node will have its own copy of the projects API URL and when you make a change to the URL on one node, it will not propagate to the other nodes. In a future blog post I’ll show to address that with a replicating cache.

If you want to see how much work Enterprise Edition saved you, or you just realized you read the wrong section, read on to see how to implement the same admin console panel in Community Edition.

Step 4b: Add the custom admin console panel to Community Edition

If you are following along:

git checkout step4b

Our Enterprise Edition friends turned the Project Service class into a JMX-managed bean, then took advantage of the pre-existing framework for admin console panels to create a panel that can get and set managed attributes. We aren’t so lucky, but it isn’t the end of the world. We can still have the exact same functionality, we just have to do a bit more work. That’s how it goes with Community Edition, right?

We’ll need a web script that establishes the admin console panel and renders a form with the current value of the projects API URL:

Assuming you skipped the Enterprise Edition section (Step 4a) I’ll reiterate what I said there about paths. The path to the web script is important. It dictates where the panel shows up in the admin console. In this case, the important part of the path is “admin/ecmarchitect-tools”. That establishes a new section (“ecmarchitect-tools”) that we could use to group multiple panels. If we were to just use “admin” this new panel would show up in the “root” list of admin console panels, as a sibling to “System Summary”.

These are the same file paths and names as the Enterprise Edition example, but the web script controller and the freemarker view are different.

The web script controller has to grab the project service bean from the Spring context so it can get the current value of the projects API URL and add it to the model:

ctxt = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext();
projectService = ctxt.getBean('project-service', Packages.com.ecmarchitect.alfresco.examples.projects.services.ProjectService);
model.projectsApiUrl = projectService.getProjectsApiUrl();

The remaining calls take advantage of the out-of-the-box Admin component:

model.tools = Admin.getConsoleTools("api-settings");
model.metadata = Admin.getServerMetaData();

The freemarker view is a bit more involved. Rather than relying on Alfresco to build the form, we’ve got to do it ourselves:

<#include "/org/alfresco/repository/admin/admin-template.ftl" />
<@page title=msg("project-api-settings-console.title") readonly=true>
    <link rel="stylesheet" type="text/css" href="${url.context}/alfresco-custom-admin/css/api-settings.css" />
    <script type="text/javascript" src="${url.context}/alfresco-custom-admin/js/api-settings.js"></script>
    <div id="message" class="message hidden">Successfully saved values.
        <a href="#" onclick="this.parentElement.classList.add('hidden');" title="Close">[X]</a>
    </div>
    <form id="api-settings-form">
        <div class="column-full">
            <h2>${msg("project-api-settings-console.column")}</h2>
            <div class="section">
            </div>
            <div class="control text">
                <span class="label">ProjectsApiUrl:</span>
                <span class="value"><input id="projectsApiUrl" name="ecmarchitect:name=Projects|ProjectsApiUrl" value="${projectsApiUrl}" maxlength="255" tabindex="0"/></span>
            </div>
        </div>
        <div class="submission buttons">
            <input type="button" value="Save" onclick="ECMArchAdmin.saveSettings()"/>
        </div>
    </form>
</@page>

In the call to the page macro, readonly is set to true because I don’t want Alfresco trying to create its own form–I’m going to handle it.

Next there are some references to stylesheets and a small JavaScript file which I’ll cover shortly.

Then there is a DIV that starts out as hidden. This will show itself after successfully saving the settings.

After that is a simple form. The form has an input for the projects API URL and a save button that invokes a function in the client-side JavaScript file to do the save. The client-side JavaScript file is:

We could have posted the form to a web script using the form’s action, but I thought it would be nicer to use AJAX instead. That gave me a chance to display a success message using the same style that’s used in the Enterprise Edition case, so the look-and-feel is a lot closer.

Note that in Community Edition, the out-of-the-box Admin object actually has a request method, but it does not set the content-type header properly when it makes the request, which breaks the automatic JSON handling in the POST web script. To fix this, I copied the request function into api-settings.js and set the header properly.

As mentioned earlier, the save button invokes a client-side function called ECMArchAdmin.saveSettings. The function POSTs JSON to a web script, which is responsible for actually saving the settings. Here’s the web script:

Similar to the GET web script, the POST web script controller gets the project service from the Spring context, then grabs the projects API URL from the JSON that was POSTed, and invokes the project service setter.

var obj = JSON.parse(json.toString());
var projectsApiUrl = obj.projectsApiUrl;

ctxt = Packages.org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext();
projectService = ctxt.getBean('project-service', Packages.com.ecmarchitect.alfresco.examples.projects.services.ProjectService);
projectService.setProjectsApiUrl(projectsApiUrl);

model.projectsApiUrl = projectsApiUrl;

If you came to this step from Step 3 and everything is still running, you can do ./run.sh reload_acs to rebuild and restart the ACS container. Otherwise, run ./run.sh build_start to start everything up.

Once everything is up-and-running, you can test your work:

  1. log in to the admin console and click TBD to see the new admin console panel. The projects API URL is already set because the default value
    from the alfresco-global.properties file was injected into the Project Service class.
  2. Go ahead and change the URL to a bad value, then click Save.
  3. Now go into Share and try out the UI action. It should fail.
  4. Go back into the admin console and change the URL to the correct value, which is http://fake-projects-api:3000/projects, then click Save.
  5. Now go back into Share and try the UI action again. It should display the project data successfully.

Custom admin console panel in community edition

There you have it. You now know how to create your own custom admin console panels so that you can change properties on your Alfresco customizations at runtime. You saw that if you are using Enterprise Edition, it’s a little bit easier, because you can take advantage of JMX, whereas with Community Edition you have to render the form yourself and then write your own POST web script to call setters on your service class with updated data.

If you need to do more elaborate work on the admin console, take a look at the Order of the Bee Support Tools Add-On for further examples. It was really helpful to me when working on the Community Edition version of this example. And, if you are running Community Edition, you really ought to install that Add-On and maybe even get involved in helping maintain that project.

Photo credit: Data Center, Cushing Memorial Library, CC BY-NC-ND 2.0

Display a chart in Alfresco Share: Part Two

In Part One, you saw how to create an Alfresco Share dashlet that uses out-of-the-box Aikau widgets to chart data. In that example, the data came from a repository tier web script.

In this blog post, Part Two, you’ll see how to fetch the data from an external API called from the Share tier. The external API could be anything, but for this example we’ll implement it with Spring Boot. We’ll add a Docker module to the project so that the Spring Boot application will run in a container alongside the Alfresco and Share containers that are part of all Alfresco SDK projects. Finally, we’ll secure the external API so that only authenticated Alfresco users can invoke it.

Steps

This code is available in GitHub. Just like Part One, each of the steps below is tagged. After you clone the repository you can switch between steps by doing git checkout step4 or git checkout step5 etc. I’m starting with “Step 4” because Part One covered steps 1 – 3.

If you are going to follow along, and you haven’t already, clone the project:

git clone https://github.com/jpotts/alfresco-share-chart-example.git

Step 4: Add an external REST API with Spring Boot

If you are following along:

git checkout step4

The existing code invokes a web script in the repository tier that returns sales data. A more likely case is that the sales data comes from a REST API running external to Alfresco. In this step we’ll build a small Spring Boot application that simulates the external sales data API. To make things simple, we’ll add the new application as an additional module to the alfresco-share-chart-example project.

Switch to the root of the alfresco-share-chart-example project and create a new directory named alfresco-chart-example-api.

Within that, add a new pom.xml file that pulls in the Spring Boot dependency:

There are many ways to organize a Spring Boot app. I like to create separate files for my application, model, controllers, and services:

We’ll set the application port to 8000 in the application.properties file:

Test by switching into alfresco-chart-example-api and running mvn clean install. Then, run java -jar target/alfresco-chart-example-api-1.0-SNAPSHOT.jar. Once the app comes up you should be able to invoke:

http://localhost:8000/api/salesData

And see the JSON response.

Use ctl+c to stop the application.

Step 5: Create a Docker module for the example API

If you are following along:

git checkout step5

The Alfresco SDK uses Docker to run Alfresco and Alfresco Share, so we’ll do the same. To do that, create a new module directory called alfresco-chart-example-api-docker.

Within that, add a new pom.xml file. This pom.xml pulls in the application created in the previous step as a dependency so that it can be easily copied to the Docker image.

Now add a Dockerfile that builds the image. There are a lot of ways you can deploy Spring Boot apps with Docker. In this example, we’ll just use an Alpine Linux image with the JDK, and launch the Spring Boot app directly from the JAR, just like you did when you tested the app in the previous step.

When we start Alfresco with the run.sh script that is included with the SDK it builds the Alfresco containers, starts them up, and networks them together using Docker Compose. Our container should be included in that, so we’ll update the Docker Compose file that already exists off the root of the project source code and add corresponding tasks to the run scripts:

Finally, make sure to add the two new module directories to the root pom.xml:

Test by changing to the alfresco-chart-example-api-docker directory and running mvn clean install. Then, switch to the root of the project source and run ./run.sh reload_api or run.bat reload_api. This should cause a Docker container to start up with the Spring Boot application running. Again, you should be able to invoke:

http://localhost:8000/api/salesData

And see the JSON response.

Run ./run.sh stop or run.bat stop to stop the container.

At this point you should be able to do ./run.sh build_start or run.bat build_start to start up Alfresco, Alfresco Share, and the Spring Boot Example API, all at once.

Step 6: Update Share to fetch data from the API

If you are following along:

git checkout step6

Now Alfresco, Share, and the API are running, but the Share chart dashlet is still pulling its data from the repository tier web script. It’s time to update the dashlet to instead pull its data from the Spring Boot example API.

First, create a web script that the chart dashlet will call to get its data. This is similar to the one we created in the repository tier in Part One, but this one is going into the Share tier. You could run this in the repository tier, but putting it in the Share tier saves us one hop.

The web script calls the external API and returns the data in the format that the dashlet is expecting. To make that call, we’re using a built-in root-scoped object called “remote” that is able to call external REST APIs:

var connector = remote.connect("example-api");
var dataString = connector.get("/api/salesData?ticket=" + ticket);

That connector ID, “example-api”, refers to Share configuration that includes the actual host that is being connected to:

In this example, the API is running as a Docker container, so the endpoint-url refers to the Docker Compose service name:

<endpoint-url>http://alfresco-chart-example-api:8000</endpoint-url>

Finally, the CustomReportService we created in Part One is still referring to the repository tier web script. It needs to be updated to instead call the new Share tier web script:

You’ve made changes to Alfresco Share, so do ./run.sh reload_share or run.bat reload_share to rebuild and run the Share container. When it comes back up, go to Share at http://localhost:8180/share, log in, and go to the test site that has your chart dashlet.

The chart dashlet should now display data coming from the Spring Boot API.

Step 7: Secure the API to only allow requests from authenticated Alfresco users

If you are following along:

git checkout step7

The Alfresco Share sales summary dashlet is now pulling data from an external REST API. If you don’t mind that anyone can fetch that data from the REST API at any time, there is no need to go any further. In this example, let’s assume that only authenticated Alfresco users should be allowed to retrieve data from the REST API. How can we modify the API and the web script that calls it to meet that requirement?

First, let’s add a new service to alfresco-chart-example-api that knows how to validate Alfresco tickets:

The service uses the Alfresco 5.2 public REST API to ask Alfresco if the ticket is valid.

With that in place, the SalesDataController can be updated to validate the ticket before returning a response:

If you do a run.sh reload_api or run.bat reload_api at this point and try to invoke http://localhost:8000/api/salesData you should see a 401 returned, which is exactly what we want–if you don’t have a valid ticket you cannot invoke the sales data API.

To test a successful call, use curl to grab a ticket:

jpotts@Metaversant alfresco-share-chart-example % curl -X POST -H 'content-type: application/json' http://localhost:8080/alfresco/s/api/login -d'{"username":"admin", "password":"admin"}'
{
    "data":
    {
        "ticket":"TICKET_01b44089022f98740c3eb27009f49e787f9fb213"
    }
}

And then pass the ticket to the sales data API and the response should be successful:

jpotts@Metaversant alfresco-share-chart-example % curl "http://localhost:8000/api/salesData?ticket=TICKET_01b44089022f98740c3eb27009f49e787f9fb213"
[{"region":"North","amount":125},{"region":"South","amount":125},{"region":"East","amount":125},{"region":"West","amount":125}]

So far, so good.

The last step is to modify the Share tier web script that fetches the sales data from the API to pass along the ticket. One gotcha here is that Share does not have direct access to the ticket, so we’ll first add a tiny repository tier web script that returns the ticket:

And then we can modify the Share tier web script to fetch the ticket from the repository tier and pass it along to the sales data API:

The controller uses the same “remote” root-scoped object it used previously, but it relies on the default connector, which is the Alfresco repository web script connector, to fetch the ticket:

// Need to get the user's ticket from the repo
var ticketString = remote.call("/custom/ticket");
var ticketResponse = jsonUtils.toObject(ticketString);
var ticket = ticketResponse.ticket;

// Get the sales data from the API
var connector = remote.connect("example-api");
var dataString = connector.get("/api/salesData?ticket=" + ticket);

You’ve now made changes to both the repository tier and the share tier. Either reload each one with ./run.sh reload_acs and ./run.sh reload_share or do a ./run.sh stop to stop all the running containers followed by a ./run.sh build_start to start everything back up.

That’s it. You’ve now got an external API that returns data only to authenticated Alfresco users and a Share dashlet that displays that data in a pie chart.

Display a chart in Alfresco Share: Part One

The other day a customer asked how much trouble it would be to chart some data in an Alfresco Share dashlet. The data comes from a non-Alfresco REST API.

The answer is that it is relatively easy to do. That’s because Alfresco Share ships with some basic out-of-the-box chart widgets called BarChart, DonutChart, and PieChart. You may have seen examples of dashlets that use PieChart if you’ve ever added the Site Content Breakdown or Site Contributor Breakdown dashlets to your Share dashboard.

In this blog post, I’ll show you how to make your own chart dashlets by shamelessly stealing Erik Winlöf’s code that implements the Site Content Breakdown dashlet and modifying it for our own purposes to show sales data, by region, in a dashlet. In part one, the data is fetched from a repository tier web script. In part two, the data comes from a Spring Boot app.

Steps

This code is available in GitHub. Each of the steps below is tagged. After you clone the repository you can switch between steps by doing git checkout step1 or git checkout step2 etc.

If you are going to follow along, clone the project:

git clone https://github.com/jpotts/alfresco-share-chart-example.git

Then, checkout step0:

git checkout step0

And startup the project using Docker and Docker Compose:

./run.sh build_start

This will start up Alfresco using Docker with no customizations made at this point.

Step 1: Add a simple API to return the data to chart

If you are following along:

git checkout step1

If Alfresco is already running:

./run.sh reload_acs

We want to build a dashlet that can chart data fetched from an API source. So, to start, we’ll add a repository tier web script that returns data as JSON. Once the chart is working we can swap that out to an external data source.

The files that make up the web script are:

With the web script in place, invoking http://localhost:8080/alfresco/s/custom/data will return a JSON response with sales broken down by region, like this:

     {
         "resultset": [
           [
                 "West", 125
             ],
             [
                 "North", 75
             ],
             [
                 "East", 250
             ],
             [
                 "South", 150
             ]
         ],
         "metadata": [
             {
                 "colIndex": 0,
                 "colType": "String",
                 "colName": "name"
             },
             {
                 "colIndex": 1,
                 "colType": "Numeric",
                 "colName": "sum"
             }
         ]
     }

The format of the JSON is what the chart component expects–it includes a resultset property, with a list of data values, and a metadata property, which describes the data types found in the result set.

If you need exact steps on how to create a web script, see my webscripts tutorial.

Step 2: Add a dashlet

If you are following along:

git checkout step2

If Alfresco is already running:

./run.sh reload_share

In this step, add a dashlet. Share supports the old “Surf” way to add dashlets as well as the newer “Aikau” way to add a dashlet. Because we’re going to use an Aikau widget for the chart, we’ll use the Aikau approach for the dashlet.

Dashlets are implemented using web scripts, so we’ll need a descriptor, controller, and view template:

This is an Aikau dashlet, so in addition to the dashlet web script, we need an Aikau widget, implemented in client-side JavaScript, that sets up the dashlet:

Now log in to Share at http://localhost:8180/share, create a test site, and customize the site’s dashboard to add the newly-created dashlet.

Customize dashlet page

It doesn’t have anything in it yet because we need to define a service to fetch the data and a widget to display it. That’s the next step.

An empty dashlet

Step 3: Add a widget and a service

If you are following along:

git checkout step3

If Alfresco is already running:

./run.sh reload_share

The last step is to add an Aikau service to fetch the data and an Aikau widget to display the data fetched by the service. The widget will use the out-of-the-box pie chart widget to display the chart.

First, we write an Aikau service to fetch the data from the API and publish it to a topic:

Then, we write an Aikau widget that subscribes to the topic and renders the chart using the PieChart widget:

The dashlet controller we implemented earlier needs to be updated to refer to the new service:

And the Aikau dashlet code needs to be updated to point to the new widget:

Now when the dashlet renders, it fetches the data from the repository tier web script and presents it in a pie chart.

Hovering over each slice in the pie chart produces and overlay with the detailed data.

Share dashlet with a pie chart that pulls data from a REST API

Part Two Preview

In Part Two, we’ll create a little Spring Boot application that the dashlet will call to fetch its data instead of fetching it from the repository tier. The Spring Boot app will authenticate the request using the authenticated Alfresco user’s ticket, so that only authenticated Alfresco users can fetch the data from the API.

Adding actions to the Alfresco Share Selected Items menu

In a typical Alfresco implementation it is quite common to develop one or more custom actions to encapsulate a set of operations that need to be performed against a folder or document. Custom actions can be called from folder rules, added to the user interface, or called programmatically from web scripts. To learn more about developing custom actions, check out my tutorial on custom actions.

When actions are added to the Share document library or document details page they operate against a single folder or document. But the Share document library also lets users select multiple objects and then run actions against them. Out-of-the-box examples are things like “Download as Zip”, “Copy”, “Move”, “Start Workflow”, and “Delete” as shown in the screenshot below.

Alfresco Share out-of-the-box selected items menu

This blog post explains how you can add your own custom actions to the “Selected Items” menu in Alfresco Share.

First, I’m going to assume that you already have a custom action that works when invoked from a rule or from the UI and you just need it to work from the Selected Items menu. I’ll call mine “Example Action”. It doesn’t do anything exciting except log the node reference of the object the action is run against. Here it is sitting in the list of document actions on the document details page:

Custom action displayed in the Alfresco Share document details actions list

Now the goal is to add Example Action to the “Selected Items” menu so that the action will run against each object the user has selected. If you want to skip right to the working project on GitHub that’s fine, otherwise, read on for a step-by-step…

Step 1: Extend the multi-select configuration

I used a Share extension to add some custom configuration to get Example Action to show up in the document library browse list and the document details page. I’ll add additional config to extend the out-of-the-box “multi-select” list of action links:

<config evaluator="string-compare" condition="DocumentLibrary">
    <multi-select>
        <action type="action-link" id="onExampleAction" label="actions.example-action"></action>
    </multi-select>
</config>

The action label is a property bundle key that has the text I want to appear in the menu. The action ID, onExampleAction, is the name of a client-side JavaScript function to be called when the user selects the menu item.

I’ll implement the onExampleAction function in a file named custom-actions.js. I need to get Share to include that file as a dependency, so I add an additional config block:

<config evaluator="string-compare" condition="DocLibCustom">
    <dependencies>
        <js src="resources/multi-select-demo-share/custom-actions.js"></js>
    </dependencies>
</config>

That’s all I need to change in my Share extension XML file. See the full file on GitHub.

Now let’s look at the client-side JavaScript.

Step 2: Implement the client-side JavaScript

In the previous step I configured Share to add an action to the “Selected Items” menu that invokes the onExampleAction function when clicked and I told it where to find the function. Now I need to implement the function. To do that, I use YUI to fire a “registerAction” event, passing in an object with two properties: one is the name of my action and the other is a function to execute:

(function() {
      YAHOO.Bubbling.fire("registerAction",
          {
              actionName: "onExampleAction",
              fn: function custom_DLTB_onExampleAction(assets) {
                  // Function logic goes here
              }
          });
})();

Note that “onExampleAction” matches whatever I specified as the action ID in the multi-select config. It has nothing to do with the name of the Java class that implements the back-end action executer.

Within that function body you could do whatever you want. Maybe you need to capture some additional data from the user, so you present a modal. Maybe you want to display a progress bar. It’s up to you.

In my case, I just need to send the selected nodes to the back-end. Remember that I already have a working action. Mine is a Java class named ExampleAction that writes a log message with the node reference of the node the action is running against.

It would be nice if I could just somehow call that action and hand it my list of selected node references, but the execute method in the ActionExecuter interface only accepts a single node. Plus, we’re currently sitting in client-side JavaScript delivered by Share that needs to invoke an action in the Alfresco back-end–a completely separate web application. I need a way to asynchronously make an HTTP call to the Alfresco back-end from here.

The solution is to call a custom repository-tier web script with the list of selected node references, and use the web script controller to invoke the action for each of the node references in the list.

Alfresco provides a utility for making asynchronous posts and a constant that has the Share proxy URI. (The Share proxy avoids making the call from the browser all the way back to the Alfresco back-end, which could be running on a different host.) All I have to do is provide the path to the web script and the list of assets:

Alfresco.util.Ajax.jsonPost(
    {
        url: Alfresco.constants.PROXY_URI + "ecmarchitect/example-action-multi",
        responseContentType: "application/json",
        dataObj: assets,
        successMessage: this.msg("message.example-action.success"),
        failureMessage: this.msg("message.example-action.failure"),
        successCallback:
            {
                fn: function exampleSuccess() {
                    console.log("Success");
                },
                scope: this
            },
        failureCallback:
            {
                fn: function exampleFailure() {
                    console.log("Failure");
                },
                scope: this
            }
});

In the above JSON POST I am also setting a success message and a failure message so that the Share user sees feedback after the web script is invoked. Just to show you how it works, I’m also including success and failure callback functions. Mine just write a message to the browser developer console, but you could do whatever you need to, such as display data that came back in the web script response.

You can review the full source of custom-action.js or go on to the next step, which is to implement the web script.

Step 3: Implement the web script

I need a web script that will accept the list of selected nodes and invoke the action for each one. Webscript controllers can be written in either JavaScript or Java (tutorial) but I almost always write them in Java these days.

You can see the full source of ExampleActionMulti.java on GitHub–I am just going to include the relevant snippet here:

Action exampleAction = actionService.createAction("example-action");
JSONArray dataObj = (JSONArray) content;
for (int i = 0; i < dataObj.length(); i++) {
    JSONObject obj = (JSONObject) dataObj.get(i);
    NodeRef nodeRef = new NodeRef((String) obj.get("nodeRef"));
    if (nodeService.exists(nodeRef)) {
        actionService.executeAction(exampleAction, nodeRef);
    }
}

Alfresco Share handed my client-side JavaScript function an array of node objects which I then post to this web script. So the web script iterates over that array and grabs the node reference string to construct an actual NodeRef object. I use the node service to make sure that node ref actually exists, then I execute the action against it.

Other parts of the web script that I’m not showing here are the web script descriptor, the web script view template, and the Spring bean that injects the node service and action service dependencies into my controller.

Step 4: Test

With everything in place, I’m ready to test. My project was bootstrapped with the Alfresco SDK, version 4.1.0, so it uses Alfresco Community Edition 6.2.0 by default and runs locally using Docker. After running run.sh build_start I can log in to Share, go to a site’s document library, selected multiple objects, and see this in the Selected Items menu (click to enlarge):

Alfresco Share selected items menu showing the custom action

And I can see this in the log:

Log output showing that the action worked

That’s it. Now you know how to add your own custom actions to the Selected Items menu in Alfresco Share.

Alfresco Developer Series Tutorials for SDK 4.0

Back in February, when SDK 4.0 was still in beta, I upgraded the Alfresco Developer Series tutorials on a branch to help early adopters learn how to use the platform. I was holding off on merging the branch into master until SDK 4.0 was out of beta and until I was seeing more end-user adoption of Alfresco 6.x.

Since then, SDK 4.0 has left beta. The vast majority of my customers are still running 5.2, but I’m seeing enough interest in 6.x on StackOverflow and in the forums that I decided it was time to merge to master.

If you are looking for the older tutorials based on SDK 3.0, they are still there–just use the SDK-3.0.1 tag.

In addition to the source code projects, the tutorial content itself also changed slightly. Those HTML pages have been re-generated, published, and linked to from the Alfresco Developer Series home page.

Now when developers come across the tutorials they’ll be referencing source code and instructions that are up-to-date with the latest SDK.

Alfresco Developer Series Tutorials Upgraded to SDK 4.0

Alfresco SDK 4.0 has not been released yet, but it is in beta and developers are starting to use it.

To help developers who want to learn the platform using SDK 4.0 and Alfresco 6, I’ve created an sdk-4.0 branch on the Alfresco Developer Series tutorials project. The tutorials on that branch have been upgraded to the new SDK 4.0 structure.

Aside from the project reorganization and the introduction of the Docker modules the underlying code did not have to change.

In addition to the tutorial source code projects, I also updated the tutorial content bodies to reflect the Docker and Docker Compose features of SDK 4.0 and the minor changes regarding how projects are run and tested. While I was in there I enhanced the markdown with syntax highlighting for code snippets.

Once SDK 4.0 goes GA I will merge the sdk-4.0 branch in the tutorials project into master. Those needing tutorials that leverage the old SDK 3.0.1 will still be able to get them via the sdk-3.0.1 tag.

Incidentally, I used the Alfresco SDK Upgrader script to upgrade these projects and it saved me a lot of time. If you want to know more about upgrading your 3.0 projects to 4.0, either manually or via the script, see this blog post on upgrading.

So, if you are going to learn Alfresco using the latest release, give the updated tutorials a try. If you find any issues, please create an issue on GitHub, or, better yet, fix the issue and open a pull request.

Photo Credit: Veere: tools, by docman, CC BY-NC 2.0

Spring Cleaning: Alfresco Developer Series Tutorials Get an Update

Over the weekend I completed a major update to the Alfresco Developer Series Alfresco Tutorials. The improvements are aimed at making it easier on new learners by simplifying each tutorial’s project structure and by making that structure and the module naming match the default structure and naming conventions used by version 3.0.1 of the SDK.

I made the following improvements across all tutorials:

  1. Upgrade all projects to Alfresco SDK 3.0.1. This was easy–it was a one line change because the tutorials were already at 3.0.0.
  2. Refactored all projects to use the “all-in-one” archetype. This was a bit more painful, but it needed to be done. More on this in a minute.
  3. Removed the old “common” projects, which were there because I needed JARs of certain common classes so they could be used as dependencies across tutorials. With SDK 3.0.x that’s no longer necessary.
  4. Renamed repo tier and share tier modules to match the defaults for SDK 3.0.1. I hate that the repo tier and Share tier modules have “jar” in the name because no one should be building JARs to deploy to their production Alfresco server, but I decided it was less confusing for developers trying to follow along.
  5. Changed all tutorial project module versions to be major.minor instead of major.minor.patch. This is another minor annoyance of the current SDK–the default should be major.minor.patch. But I wanted the tutorials to be consistent with the SDK defaults so what-are-ya-gonna-do.
  6. Refactored old SDK 2.0.x integration tests to be compatible with SDK 3.0.x.

The move to the all-in-one archetype was a big one. I don’t always use the all-in-one archetype, particularly if I know I’m not going to need to make Share customizations. But the tutorials have Share customizations in all but one case.

Another reason to use two projects rather than all-in-one is because using two projects allows you to run the repo on one Tomcat and the Share WAR on another. When focusing on Share development, it’s nice to not have to restart the entire repo just to pick up changes. With JRebel, which I’ve been using for the last year, restarts aren’t necessary as often, so running separate Tomcats is less of a necessity. Plus, one project is easier on the tutorial reader.

The all-in-one setup also gives you an “integration-tests” module. When Alfresco upgraded from SDK 2.x to 3.x, the old way of doing integration tests broke. To run integration-tests in SDK 3.x you really need that integration-tests module, but it is not provided when you use the “platform-jar” archetype. You can add it back, as I recently did for the ACL Templates project, but I didn’t want tutorial readers to have to do that.

In the end, I figured it was worth restructuring all of the tutorials to use “all-in-one” for the above reasons and just to simplify the whole thing.

Incidentally, if you aren’t writing integration tests for your add-ons, you really ought to. The SDK comes with samples to get you started. And if you are using JRebel, you can stay productive. You basically just fire up the embedded Tomcat server, which deploys your AMPs (and your integration tests) to the alfresco and share WAR files and starts up the server. Then, you can make changes to your classes and your tests and JRebel will sync those changes without a restart. When you run the integration tests from your IDE, they will look for a running Alfresco instance and remotely invoke your tests for you on the running server.

I still need to port my integration tests to the 3.0.x way of doing things in several of my open source Alfresco add-on projects, but at least the tutorials are taken care of.

These updates were a lot of work, but I continue to get feedback from readers all over the world that the tutorials are helpful (thanks for that, BTW!), so I want them to stay current.

I may have missed a thing or two. As always, if you find something wrong, please create an issue on GitHub, or, better yet, a pull request.

Photo Credit: Cleaning, by Bob Jagendorf

Alfresco Maven SDK: Declaring other AMPs as dependencies

5634268846_9c59682a42_mI’ve seen a few questions about how to configure an Alfresco project to depend on other add-ons when using the Alfresco Maven SDK, so I thought I’d do a quick write-up on that.

When you use the Alfresco Maven SDK Archetypes to bootstrap a project, you can select one of three project types:

  1. alfresco-allinone-archetype: Referred to simply as “All-in-One”, this type, as the name implies, gives you a project that facilitates creating both a repository tier AMP and a Share tier AMP, plus it includes a working Solr installation.
  2. alfresco-amp-archetype: This sets up a project that generates only a repository tier AMP.
  3. share-amp-archetype: This sets up a project that generates only a Share tier AMP.

In my Alfresco Maven SDK Tutorial I use both the alfresco-amp-archetype and the share-amp-archetype but I don’t really talk about the All-in-One archetype at all. The reason is two-fold. First, I think for most beginners, the smaller, more focused archetypes are less confusing. Second, on the vast majority of my client projects, that’s the setup I use. Usually, I feel like the All-in-One archetype is overkill.

However, there is an advantage the All-in-One archetype has over the other two. It is able to pull in other AMPs as dependencies. With the single-purpose archetypes, your goal is simply to build an AMP. You can run integration tests against that AMP using the embedded Tomcat server, but if you want to test the AMP with other AMPs, you have to deal with that manually. Usually I just deploy the AMPs to a test Alfresco installation that already has the other AMPs deployed.

But with the All-in-One archetype, you can declare those other AMPs as dependencies, and Maven will go grab them from a repository and install them into your local test environment using something called an “overlay”.

Here’s an example. Suppose you are developing some customizations that you know will be deployed to an Alfresco server that will be running the following add-ons: Share Site Creators, Share Site Space Templates, Share Login Announcements, and Share Inbound Calendar Invites. You’d like to test your code alongside these add-ons.

One approach would be to go grab the source for each of these projects and build them locally to produce their AMPs (or download pre-built AMPs), then deploy all of those AMPs to a test server, then deploy your custom AMP and test.

But all of those add-ons happen to live on a public Maven artifact repository called Maven Central. So a better alternative might be to simply list those modules as dependencies and let Maven take care of the rest.

Here’s how it works. When you generate an all-in-one project, the structure includes not only folders that contain your repo and Share AMP source, but also folders representing the Alfresco and Share web applications. For example, here are the files and folders in the root folder of a project called “test-allinone-220”:

pom.xml
repo
run.bat
run.sh
runner
share
solr-config
test-allinone-220-repo-amp
test-allinone-220-share-amp

In our example, your custom code for the repo tier would go in test-allinone-220-repo-amp and your Share tier code would go in test-allinone-220-share-amp.

The repo and share directories are used to build the respective WAR files and they each have their own pom.xml. So to add the other add-ons to our setup, first edit repo/pom.xml. You need to add the add-ons as dependencies, like:

<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>inbound-invites-repo</artifactId>
    <version>1.1.0</version>
    <type>amp</type>
</dependency>
<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-space-templates-repo</artifactId>
    <version>1.1.2</version>
    <type>amp</type>
</dependency>
<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>share-login-ann-repo</artifactId>
    <version>0.0.2</version>
    <type>amp</type>
</dependency>
<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-creators-repo</artifactId>
    <version>0.0.5</version>
    <type>amp</type>
</dependency>

Then, add them again to the overlays section (without the “version” element), like this:

<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>inbound-invites-repo</artifactId>
    <type>amp</type>
</overlay>
<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-space-templates-repo</artifactId>
    <type>amp</type>
</overlay>
<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>share-login-ann-repo</artifactId>
    <type>amp</type>
</overlay>
<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-creators-repo</artifactId>
    <type>amp</type>
</overlay>

That covers the Alfresco WAR. Now for the Share WAR, edit share/pom.xml and add the Share tier dependencies, like:

<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>share-login-ann-share</artifactId>
    <version>0.0.2</version>
    <type>amp</type>
</dependency>
<dependency>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-creators-share</artifactId>
    <version>0.0.5</version>
    <type>amp</type>
</dependency>

And then the overlays:

<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>share-login-ann-share</artifactId>
    <type>amp</type>
</overlay>
<overlay>
    <groupId>com.metaversant</groupId>
    <artifactId>share-site-creators-share</artifactId>
    <type>amp</type>
</overlay>

Now you can run the whole thing–your custom AMPs plus all of the dependencies–by running ./run.sh. As part of the build, you should see the dependencies being downloaded from Maven Central. As the server starts up, if you watch the log, you’ll see the modules get initialized. And when you log in, you’ll be able to test all of the functionality together.

When you are ready to deploy to one of your actual server environments, you have a choice. You can either copy all of your AMPs to the amps and amps_share directories, then run bin/apply_amps.sh. Or you can deploy the WAR files that are now sitting under repo/target and share/target. Personally, I think re-applying the AMPs against a fresh WAR on the target server is less confusing and less error-prone, but I’ve seen it done both ways.

If the add-ons you want to depend on are not on a public Maven artifact repository like Maven Central, you can still build them locally, run “mvn install” to install them into your local Maven repository, then depend on them as shown.

Alfresco tutorials updated to SDK 2.0 and Alfresco 5.0

I’ve recently updated the Alfresco Developer Series tutorials to work with version 2.0 of the Alfresco SDK and Alfresco 5.0.d (and Enterprise 5.0).

Note that the SDK is not backwards compatible. If you are running Alfresco 4.x you need to use the older version of the SDK. When you move to 5.0 you need to move to SDK 2.0. The steps to do that are roughly:

1. Merge your pom.xml with the one generated by the 2.0 archetype.
2. Copy/merge tomcat/context.xml.
3. Copy run.sh from a 2.0 project into yours. Only needed if you are using spring-loaded.
4. Copy/merge src/test/resources.
5. Copy/merge src/test/properties.

That part was easy for all of the tutorial projects. The time-consuming part was just updating the screenshots and a few of the steps. The code stayed the same across all projects.

If you are still on 4.x and you want to use the tutorials that are specific to the older version, just use the source tagged with “4.x” on github.

How I successfully studied for the Alfresco Certified Engineer Exam

Back in March I blogged about why I took the Alfresco Certified Administrator exam (post). Today I passed the Alfresco Certified Engineer exam. I took it for the same reasons I took the ACA exam, as outlined in that post, so in this post, I thought I’d share how I studied for the test.

Let me start off with a complaint: There is nowhere I could find that describes which specific version of Alfresco the test covers. This wasn’t that big of a deal for the ACA exam, but for the ACE exam, I felt a little apprehensive not knowing.

I know Alfresco probably doesn’t want to lock the exam version to an Alfresco version. But the blueprint really needs to give people some idea. Ultimately, I decided 4.1 was a safe bet.

I can’t tell you what was on the test, but I can tell you how I studied.

First, review the blueprint

The exam blueprint is the only place that gives you hints as to what’s on the test. If you look at the blueprint, you’ll see that the test is divided into five areas: Architectural Core, Repository Customization, Web Scripting, UI Customization, and Alfresco API.

The blueprint breaks down each of those five areas into topics, but they are still pretty broad. Some of them helped me figure out what to review and some of them didn’t. For example, under Architectural Core, topics like “Repository”, “Subsystems”, and “Database” were too vague to be that helpful in guiding my study plans.

Next, identify your focus areas

Looking at the blueprint, most of those topics have been in the product since the early days and haven’t changed much. I figured I could take the test cold and pass those. But Share Configuration and Customization has changed here and there between releases. With a lot of different ways to do things, and ample opportunity for testing around minutiae, I figured this would be where I’d need to spend most of my study time. I also wanted to spend time reviewing the various API’s listed under Architectural Core because I typically just look those up rather than commit the details to memory.

To validate where I thought my focus areas should be I took the sample test on the blueprint page, which was helpful.

Now, study

For Architectural Core, I spent most of my time reviewing the list of public services in the Foundation API found in Appendix A of the Alfresco Developer Guide, the JavaScript API (also in Appendix A as well as the official documentation), and the Freemarker Templating API documentation.

For the Repository Customization I figured I had most of that down cold and just spent a little time reviewing Activiti BPM XML and associated workflow content models. The workflow tutorial on this site is one place with sample workflows to review and obviously the out-of-the-box workflows are also good examples.

According to the blueprint, the UI Customization section is now focused entirely on Alfresco Share, so I didn’t spend any time reviewing Alfresco Explorer customization. Instead, I read through the Share Configuration and Share Customization sections of the documentation. There are now tutorials on Share Customization in the Alfresco docs so I went through those again just to make sure everything was fresh. The Share configuration examples in my custom content types tutorial are another resource.

The Alfresco API section consists of questions about the Alfresco REST API and CMIS. This is only 5% of the test so I spent no time reviewing this. I also ignored Web Scripts, figuring my existing knowledge was good enough.

After studying the resources in my focus areas I took the sample test once more. It’s always the same set of questions, so taking it repeatedly isn’t a great way to prove your readiness, but at least you know you won’t miss those questions if they show up on the real test.

Feel ready? Go for it

If you get paid to work with Alfresco, you really ought to take this exam (and the ACA exam). Obviously, what I’ve reviewed here is a study plan for someone who has significant experience with the platform doing real world projects. If you are new to Alfresco you’ll have to adjust your plan and preparation time accordingly. Better yet, get a few projects under your belt first. I think it would be tough for someone with no practical experience to pass the test with any amount of study time, which is the whole point.

So there you go, that’s how I studied. Your mileage will vary based on what your focus areas need to be. Now go hit the books!