Java Plugin Architecture

Sometimes you need to design an application in such a way that users (devs) can drop in their own implementation of a service. Generally this is done via interfaces. But how do we communicate to our application that a new implementation has been created and how do we know which implementation of the service to use? This is where the plugin architecture comes in. In this blog I’ll be summarizing the Java Tutorial for Creating Extensible Applications. Their example uses a Dictionary that allows to you look up the definition of a word. Let’s take a look at classes and what each one of them does.

Sample Code Explained

DictionaryDemo.class is the main class that uses the Dictionary to look up words. It’s simply the caller of the service.

public class DictionaryDemo { public static void main(String[] args) { DictionaryService dictionary = DictionaryService.getInstance(); String definition = dictionary.getDefinition("book"); } }

Dictionary.class is the service provider interface (SPI). This is what the service knows about and what the service providers must implement.

public interface Dictionary { public String getDefinition(String word); }

DictionaryService.class is a singleton that uses the ServiceLoader to get the implementations of Dictionary.class and does the work of interacting with the actual implementation.

public class DictionaryService { private static DictionaryService service; private ServiceLoader loader; private DictionaryService() { loader = ServiceLoader.load(Dictionary.class); } public static synchronized DictionaryService getInstance() { if (service == null) { service = new DictionaryService(); } return service; } public String getDefinition(String word) { String definition = null; try { Iterator dictionaries = loader.iterator(); while (definition == null && dictionaries.hasNext()) { Dictionary d = dictionaries.next(); definition = d.getDefinition(word); } } catch (ServiceConfigurationError serviceError) { definition = null; serviceError.printStackTrace(); } return definition; } }

Then you have GeneralDictionary.class and ExtendedDictionary.class which are two actual implementations of Dictionary. They are the service providers. I’m not going to show the code here because the whole point is that the exact implementation doesn’t matter!

Configuration

So let’s say you are the dev who wants to provide the implementation of Dictionary, how do you actually communicate to the application that you have created the implementation? You do this by registering your service provider. Do this by creating a configuration file that contains the fully qualified class name of your service provider implementation. The name of the file must match the fully qualified class name of the service provider interface. Then, the configuration file should be stored in the META-INF/services directory of the service provider’s JAR file.

For example, if you have created the GeneralDictionary implementation of Dictionary, then the config file name is dictionary.spi.Dictionary (since Dictionary lives in the dictionary.spi package). The file contains one line, dictionary.GeneralDictionary (since GeneralDictionary lives in the dictionary package). Finally, the implementation and config file need to be packaged in a jar that should be placed in the application’s classpath.

Things to Know for Technical Interviews

Google style interviews are becoming commonplace. If you are in tech and are in the job market you may need to brush up on your skills or revisit some of those things you learned in college 12 years ago (space and time complexity I’m looking at you). The following list was compiled based on this video from Google about how to prepare for one of their interviews:

  • Data Structures: Hash tables, Stacks, Arrays, Linked Lists, Trees (construction and manipulation, traversal)
  • Algorithms: Dijkstra, A*, etc
  • Space and Time Complexity
  • System Design
  • Object Oriented Programming
  • Testing: Unit testing, interesting inputs, corner and edge cases, integration, load and performance, security
  • NP Complete Problems: Traveling Salesman and the knapsack for example
  • Math: Basic discrete math: Counting, Probability theory, Combinatorics, n choose k
  • Recursion
  • Operating Systems: Processes, threads, concurrency issues (semaphores, mutexes, locks), resources allocation, context switching, scheduling
  • System design: feature sets, interfaces, class hierarchies, distributed systems
  • Internet: Routers, DNS, load balancers, firewalls, search

How To: Build a Trello Power-Up for YouTube

In this “How To” I’m going to show you how to build a Trello Power-Up that will allow users to search for and embed YouTube videos in their cards. We’ll be using Trello, GitHub, and YouTube. When we’re done, you’ll be able to create cards that look like this:

Trello card with YouTube Power-Up enabled and an embedded video

I’ll walk you through the process step by step, but you can also see the full code for the project on GitHub. I also recommend you refer to the Power-Up documentation before you begin.

GitHub Set Up

Let’s get started with GitHub. If you don’t have an account, you can create one at github.com. Once your account is created and you are logged in you will also need to set up your GitHub Pages website since that is where we’ll be hosting our Power-Up. Follow the instructions at pages.github.com to set up GitHub Pages. Now when you create a new GitHub repository it will be hosted at https://yourusername.github.io/your-repository-name. Pretty cool, right?

Fork the Power-Up Template

Now that GitHub is set up, let’s get started with the Power-Up! The easiest way to start building a new Power-Up is to fork Trello’s template project which is a full featured sample Power-Up. Go to the power-up-template repository and click the “Fork” button in the upper right corner of the page. I gave my fork a new name (youtube-power-up) and description. Then clone the repository to get a local copy. I like working with GitHub Desktop.

Update the Manifest

Next we’re going to edit the manifest to reflect that this is a YouTube Power-Up. Open up manifest.json in your favorite editor. We’ll edit the name, description, details, icon, author, and capabilities. Notice that I changed the url of the icon, that’s because we’re going to use the official YouTube icon instead of the icons that came with the template. The other section to pay attention to is the “capabilities”. I recommend reading more about capabilities here, but for now you need to know that we’ll be using attachment-sections, card-buttons, and callbacks. My finished manifest looks like this:

{
    "name": "YouTube",
    "description": "A Power-Up for integrating YouTube content in your cards",
    "details": "This Power-Up is used for integrating YouTube content in your cards",
    "icon": {
        "url": "./images/yt_icon_rgb.png"
    },
    "author": "Amber Ream",
    "capabilities": [
        "attachment-sections",
        "callback",
        "card-buttons"
    ],
    "connectors": {
        "iframe": {
            "url": "./index.html"
        }
    }
}

Be sure to add the YouTube icon to your images folder. When you’re done with your edits, push your changes to the manifest and the new icon to GitHub.

Create a new Power-Up in Trello

Now we’re ready to create the new Power-Up in Trello. If you don’t already have a Trello account, sign up for one at trello.com. Since Power-Ups are associated with teams, you’ll also need to create a new team. You can do this by clicking the “Create a new team…” link underneath your boards.

Click the

Now you’re ready to create a new Power-Up from the Power-Ups Administration Page. Under “Teams” click on the team you’d like to add the Power-Up to, then choose “Create new Power-Up”. Give your Power-Up a name (I chose YouTube Power-Up) and then enter the path to manifest.json. If you’re using GitHub Pages, the url will look something like this:

https://username.github.io/repository-name/manifest.json

Click save and your new Power-Up will show up in the list of Power-Ups.

Add the Power-Up to a Board

Now that our Power-Up has been created, let’s go to our boards so we can add our Power-Up and see it in action! At this point it will have the template functionality, we’ll implement the YouTube code later. To add the Power-Up to a board, go back to trello.com and if you haven’t already created a board for your team go ahead and do that now. Then click on your board to open it. To add the Power-Up, go to your team’s board and choose “Power-Ups” from the menu.

Open the menu on the right hand side of the page and select the

Scroll down and you should find the new “YouTube” Power-Up in the list (it is sorted alphabetically). Note that the name, details, and icon you see in the Power-Up list are all coming from manifest.json. Click “enable” to enable this Power-Up (if you are using the free version of Trello only one Power-Up is allowed per board so you may need to disable another Power-Up first). I’ve noticed it can take anywhere from a few minutes to a few hours for the Power-Up’s icon & description to show up in the list, so don’t worry if all you see is the name at this point! Likewise there is some lag before the Power-Up is available in your cards. In the meantime, we can get started on other parts of our project.

YouTube API

We will be using the YouTube Data API to search for videos. Before we can connect to the API, we’ll have to set up credentials and grant access to the YouTube Data API. Make a note of your API key because you’ll be using it soon, and please remember to restrict your API key! For example, I chose to only allow YouTube requests coming from my GitHub Pages site.

YouTube credentials accepting referrers from my github pages site

Implement client.js

Now it’s time to make our Power-Up do something! But first, a little set up. Since I like to use jQuery for ajax requests, I’m going to import it in index.html.

<script src=”http://code.jquery.com/jquery-3.2.1.min.js”integrity=”sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=” crossorigin=”anonymous”></script>

Next, since I have a custom icon that I would like to display for the YouTube Power-Up on the back of the card, let’s drop the gray play button icon in the images folder.

You may remember defining card-buttons, attachment-sections, and callback in manifest.json. Let’s take a look at how those are used in client.js. We don’t have to define anything specifically for “callback” here, but you can read more about why we need the “callback” defined in the manifest here.

card-buttons

We’ll start by adding a button to the back of our card. In client.js, find the ‘card-buttons‘ function inside TrelloPowerUp.initialize. We will need to define a button that will trigger the YouTube Power-Up. We’ll give it an icon, text, and a callback function that will be called when the button is clicked. This button will be using the gray play button icon, so you’ll need to update the GRAY_ICON constant at the top of the file to refer to the gray icon you just added to the images folder.

var GRAY_ICON = './images/yt_icon_gray.png';

We can also remove the template code since we won’t be using it, so when we’re finished, our card-buttons function will look like this:

'card-buttons': function(t, options) {
    return [{
      // YouTube!
      icon: GRAY_ICON,
      text: 'YouTube',
      callback: youTubeButtonCallback
    }];
}

Now we need to define the youTubeButtonCallback. This is where we’ll display a popup so the user can search for a video on YouTube. We’ll make requests to the YouTube Data API as the user types and then return a list of results that will get displayed in our popup. In the code below we’re using jQuery to make a request to the YouTube Data API to get a list of results based on what the user has typed into the search box. Each result contains “text” and “callback” properties. The text is what gets displayed in the list, and the callback is the function that gets called when an item in the list is clicked. In this case, we want to attach the YouTube video to the card. Remember to update the YouTube key to your own key which you created above or searching will fail!

var youTubeButtonCallback = function (t) {

    return t.popup({
        title: 'YouTube',
        items: function (t, options) {

            // use options.search which is the search text entered so far
            // return a Promise that resolves to an array of items
            // similar to the items you provided in the client side version above
            var response = $.ajax({
                url: "https://www.googleapis.com/youtube/v3/search",
                data: {
                    maxResults: '25',
                    q: options.search,
                    type: 'video',
                    part: 'snippet',
                    key: 'AIzaSyCawso6-SQJS2JAw7FCXQD-sNeLtzDPxE0'
                },
                success: function (data) {
                    //                console.log(data);
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    alert(errorThrown);
                }
            });

            // when the response is finished, then return a list of items
            return response.then(function (data) {
                //                console.log(data);
                var ret = new Array();
                var items = data.items;
                for (var i = 0; i < items.length; i++) {
                    ret.push({
                        text: items[i].snippet.title,
                        callback: (function (item) {
                            return function (t, opts) {
                                return t.attach({
                                    name: item.snippet.title, // optional
                                    url: "https://www.youtube.com/embed/" + item.id.videoId // required
                                });
                            }
                        })(items[i])
                    });
                }
                return ret;
            });
        },
        search: {
            // optional # of ms to debounce search to
            // defaults to 300, override must be larger than 300
            debounce: 300,
            placeholder: 'Search',
            empty: 'No results',
            searching: 'Searching'
        }
    });

};

attachment-sections

On to "attachment-sections"! This is what defines how the YouTube url we are attaching in the YouTube callback will get displayed on the card. There are three files that need to be updated, let's start with section.html. In this file we're simply defining a container to hold our YouTube videos:

<body>
    <div id="content">
        <div id="videos"></div>
    </div>
    <script src="./js/section.js"></script>
</body>

Notice the section.js import. This is the next file we need to edit. Let's look at the render function:

t.render(function () {
    // make sure your rendering logic lives here, since we will
    // recall this method as the user adds and removes attachments
    // from your section
    t.card('attachments')
        .get('attachments')
        .filter(function (attachment) {
            return attachment.url.indexOf('https://www.youtube.com') == 0;
        })
        .then(function (youtubeAttachments) {
            var urls = youtubeAttachments.map(function (a) {
                return '<div class="video"><iframe src="' + a.url + '?rel=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></div>';
            });
            document.getElementById('videos').innerHTML = urls.join(' ');
        })
        .then(function () {
            return t.sizeTo('#content');
        });
});

Since we are interested in embedding YouTube videos, we're filtering for urls that begin with "https://www.youtube.com". For each video we find, we're adding a new iframe and adding it to the videos div that we defined in section.html. Finally, we need to update the "attachment-sections" function in client.js.

    'attachment-sections': function (t, options) {
        // options.entries is a list of the attachments for this card
        // you can look through them and 'claim' any that you want to
        // include in your section.

        // we will just claim urls for YouTube
        var claimed = options.entries.filter(function (attachment) {
            // claim youtube urls
            return attachment.url.indexOf('https://www.youtube.com') === 0;
        });

        // you can have more than one attachment section on a card
        // you can group items together into one section, have a section
        // per attachment, or anything in between.
        if (claimed && claimed.length > 0) {
            // if the title for your section requires a network call or other
            // potentially length operation you can provide a function for the title
            // that returns the section title. If you do so, provide a unique id for
            // your section
            return [{
                id: 'YouTube', // optional if you aren't using a function for the title
                claimed: claimed,
                icon: YOUTUBE_ICON,
                title: 'YouTube Videos',
                content: {
                    type: 'iframe',
                    url: t.signUrl('./section.html', {
                        arg: 'you can pass your section args here'
                    }),
                    height: 230
                }
            }];
        } else {
            return [];
        }
}

As in section.js we need a filter for urls that begin with "https://www.youtube.com" because these are the only urls we want to include in our YouTube attachment section. The "id" will be the name of the attachment section, and the "icon" will show up next to the name. We are using the standard YouTube icon here, so we'll need to create a constant at the top of our file that points to the red YouTube icon.

var YOUTUBE_ICON = './images/yt_icon_rgb.png';

Code Cleanup

You're done with the hard part! Since we cloned this project from the sample codebase there is a lot of code we don't need, so let's delete it. Starting with client.js, there are several hooks defined that we're not using. You can delete everything except "card-buttons" and "attachment-sections". In client.js we can also get rid of the callback functions we're not using (just be sure to keep the youTubeButtonCallback).

All done!

That's it! Go back to trello.com, open up your board, and if you don't already have a card you should create one. Then when you edit your card you will see the YouTube button on the right side of the card under "Power-Ups". When you click the button, the search popup will come up and you can start searching for a YouTube video. Click on the name of the video you want to embed, and Voila, it will appear near the top of your card under the YouTube heading.

Congratulations on completing your YouTube Power-Up!