Category Archives: Uncategorized

OSS: mind the license

This article is triggered by my frustrations at and by reading Bruno Lowagie’s post “FOSS and liberal licenses“. Bruno’s post was a reaction to “Open Source, Software Hygiene and STDs” (which includes a link to an old but interesting article about software hygiene).

It is my impression that only few developers are aware of the implications of licenses of the open source libraries they use. Most don”t seem to care. They think that once it is “open source” they can do whatever they want. It is often used as “free as in free beer”. As Richard Stallman pointed out long ago (what is free software), it should be about freedom, about “free as in free speech”.
Unfortunately though, the limitations imposed by many of the open source licenses can limit your freedom a lot. These limitations can be commercially viable, which may serve the users, but this is not always the case.

Before continuing, let me be clear, I am a developer, not a lawyer. This is my interpretation from reading licenses and trying to interpret them. Examples given are from the Java world because that is what I know best. I am focussing on open source libraries and frameworks, especially the commercial implications can be quite different when not embedding the project.

Most applications which use an open source library to not respect the license

Really!

Many licenses (for example BSD style licenses) require you to include a copyright notice. It is unclear to me whether emitting the copyright notice to the console is sufficient. The safe bet (sometimes required) would be to include it in something like an about box or a notices page which can be accessed from anywhere in the application. Some popular Java libraries for which this applies include jdom and xstream.

Some licenses are viral. This especially applies to GPL and AGPL licenses. By including such a library in your software, you are forced to use that license for your software. If your software uses iText (version 5+), it should be open source with AGPL license. If your software uses the MySQL JDBC drivers, it should be GPL.

Not all licenses can be combined (there goes your freedom). This is specifically relevant for the GPL family of licenses (see FSF’s “Various licenses and comments about them“). Sometimes licenses are updated to remedy such incompatibilities (for example Apache License v2 is GPL v3 compatible while earlier versions are not compatible).
A notable example in the Java world is the increasingly popular EPL license which is incompatible with GPL/LGPL.

More complications

When reading the above, it might seem that a very permissive license like MIT or BSD is best for included libraries. Think again. Patent law (especially in the United States, mush less in the European Union – for now, see here) might change that. Maybe the license should include a notice assuring that contributors cannot force you to pay them patent licenses on code they contributed. Maybe the license should try to at least make patent infringements clearer. Some licenses like Apache v2, EPL and the GPL family of licenses seem to do this – though not necessarily in a compatible way, see “Software patents and free software“.

Which license for your project?

There are way too many open source licenses, see the OSI list. Do not create another one!
Like David A. Wheeler, I would recommend using a GPL compatible license as there are many useful libraries in with a license from the GPL family (most JBoss libraries for example).

I would choose one of the following:

  • MIT : very liberal, almost no restrictions (actually ISC is probably even simpler/better, but this is too obscure).
  • Apache (ASLv2) : also very liberal but with some extra constraints regarding patents. These restrictions make me favor it over MIT.
  • Affero GPL (AGPLv3) : a restrictive license which allows you to sell commercial licenses for inclusion in non-free (specifically non-AGPL) software. This can be an advantage if you want to build the software into a business.

I would also recommend that all contributors sign a CLA. This assures copyright is held by one organization which can greatly simplify things when license changes are being discussed or when litigation occurs.

Effect of license on commercial success

Open source software is commercial software. Most projects (especially those with a significant code base) are developed commercially, meaning that at least part of the development work is done by professional developers in their paid time.

Some projects are owned by a company (for example JBoss, iText, Activiti, Geomajas). The company will then use the project to generate sufficient revenue to pay developers to maintain and improve the product.

The license can significantly affect this ability to generate revenue.

  • A more permissive license like Apache or LGPL can enhance the popularity of the product. Developers can often include such libraries in their project without needing permission (or risking blame). Once the library is important in the project, a need for paid support, service level agreements or extra features can arise.
  • A more restrictive license like GPL or AGPL restricts including the library for free, giving the opportunity to sell licenses which are less restrictive.

The advantages of permissive licenses are demonstrated by projects like the JBoss application server and Activiti.

JBoss was originally (ten years ago) used by developers because it was free and faster then the for-pay options of that time. Licenses fees and migration costs opened the door to using JBoss in production, giving the backing company opportunity to sell support contracts and training.

Activiti was started (my interpretation, simplifying) because users (including Alfresco) requested the popular jBPM product to change license from LGPL to Apache. This was denied by the project owner. Alfresco stepped up to build a new BPM engine and managed to lure core jBPM developers to do this. The permissive license allowed other companies to see commercial opportunities in this project allowing a community and successful library to be built very quickly.

The iText case shows a mixture of both approaches. The permissive license from the early versions (in combination with the quality of the product) allowed a large body of users to grow. Even today developers can include the old version in new applications without asking permission growing the user base even more. At some point a more recent version may be needed which forces users (which now rely on the library) to buy a license. Mind you that this would not be a good strategy to follow, some users are alienated by the change of license as they feel they suddenly need to pay for something they were given.

End note

You are a developer. You don’t want your work to be pirated, so please don’t pirate my work either.

If you use software, check the license, see if it is compatible with your project. If you cannot change your project to be compatible (typically by including the necessary notices or changing the license on your software) then either buy a waver (“commercial license”) or don’t use that software!

JIRA workflows, advance parent, clone and reverse link

The JIRA issue tracking system is very powerful. It supports customizable work-flows which can be tuned to allow you to have better control over the issue life-cycle.

When you need additional functions in a work-flow, the Groovy script runner plug-in gives you many useful extra checks and post-functions or allows you to easily add more.

In my case, I needed to tweak some of the provided post-functions.

Transition parent

On a sub-task, it can be useful to force transitions on the parent issue. For example to assure that the parent is marked as “in progress” when progress is started on a sub-task.

Put the following code in $JIRA_INSTALLATION_DIR/atlassian-jira/WEB-INF/classes/com/onresolve/jira/groovy/canned/workflow/postfunctions/TransitionParent.groovy:

package com.onresolve.jira.groovy.canned.workflow.postfunctions
 
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.util.ErrorCollection
import com.atlassian.jira.workflow.JiraWorkflow
import com.onresolve.jira.groovy.canned.CannedScript
import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
import com.onresolve.jira.groovy.canned.utils.WorkflowUtils
import com.opensymphony.workflow.loader.ActionDescriptor
import com.opensymphony.workflow.loader.StepDescriptor
import org.apache.log4j.Category
import com.atlassian.crowd.embedded.api.User
 
class TransitionParent implements CannedScript {
 
    ComponentManager componentManager = ComponentManager.getInstance()
    Category log = Category.getInstance(ResolveParentAfterSubtasks.class)
    def projectManager = componentManager.getProjectManager()
    public final static String FIELD_PARENTACTION = "FIELD_PARENTACTION"
    public final static String FIELD_RESOLUTION_ID = "FIELD_RESOLUTION_ID"
 
    String getName() {
        return "Transition parent"
    }
 
    String getDescription() {
        return """This will do the given action on the parent<br>
        """
    }
 
    List getCategories() {
        ["Function"]
    }
 
 
    Integer getActionId(Issue issue, String actionName) {
        JiraWorkflow workflow = componentManager.getWorkflowManager().getWorkflow(issue)
        StepDescriptor step = workflow.getLinkedStep(issue.status)
        ActionDescriptor ad = step.getActions().find {it.name == actionName} as ActionDescriptor
        ad?.id
    }
 
    List getParameters(Map params) {
        [
            [
                Name:FIELD_PARENTACTION,
                Label:"Parent action",
                Description:"Choose the action to do on the parent when the sub-tasks are resolved",
                Type: "list",
                Values: CannedScriptUtils.getAllWorkflowActions(false),
            ],
            [
                Label:"Resolution",
                Name:FIELD_RESOLUTION_ID,
                Type: "list",
                Description:"Resolution to use on the parent",
                Values: CannedScriptUtils.getResolutionOptions(true),
            ],
            // todo: need to allow a sub-task resolution
        ]
 
    }
 
    public ErrorCollection doValidate(Map params, boolean forPreview) {
        // todo: check this is on a sub-task type
        null
    }
 
    Map doScript(Map params) {
        log.debug ("TestCondition.doScript with params: ${params}");
 
        String actionName = params[FIELD_PARENTACTION] as String
        MutableIssue subtask = params['issue'] as MutableIssue
        User user = WorkflowUtils.getUser(params)
        String resolutionId = params[FIELD_RESOLUTION_ID] as String
 
        log.debug ("actionName: $actionName")
        log.debug ("subtask: $subtask")
        log.debug ("subtask.isSubTask(): ${subtask.isSubTask()}")
 
        // if this action is resolve and all sub-tasks are resolved
        if (subtask.isSubTask()) {
            MutableIssue parent = subtask.getParentObject() as MutableIssue
 
            log.debug ("Resolve parent")
            Integer actionId = actionName?.replaceAll(/ .*/, "") as Integer
            if (WorkflowUtils.hasAction(parent, actionId)) {
                WorkflowUtils.resolveIssue(parent, actionId, user, resolutionId, [:])
            } else {
               log.warn("Action name: $actionName not found for this step.")
            }
        }
 
        return params
    }
 
    String getDescription(Map params, boolean forPreview) {
        ConstantsManager constantsManager = ComponentManager.getInstance().getConstantsManager()
 
        StringBuffer sb = new StringBuffer()
        sb << getName() + "<br>Transition " + params[FIELD_PARENTACTION]
        sb.toString()
    }
 
    public Boolean isFinalParamsPage(Map params) {
        true
    }
}

The script has been submitted for inclusion in future versions of the plug-in, see GRV-123.

Clone and link with reverse link

The normal clone and link post-function always uses the forward link between the issues. The script was patched to allow reverse links as well.

Put the following code in $JIRA_INSTALLATION_DIR/atlassian-jira/WEB-INF/classes/com/onresolve/jira/groovy/canned/workflow/postfunctions/CloneIssue.groovy:

package com.onresolve.jira.groovy.canned.workflow.postfunctions
 
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager
import com.atlassian.jira.util.ErrorCollection
import com.atlassian.jira.util.SimpleErrorCollection
import com.onresolve.jira.groovy.canned.CannedScript
import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
import com.onresolve.jira.groovy.canned.utils.ConditionUtils
 
class CloneIssue extends CopyIssueWithAttachments implements CannedScript{
 
    public static String FIELD_LINK_DIRECTION = "FIELD_LINK_DIRECTION"
 
    String getName() {
        return "Clones an issue and links."
    }
 
    String getDescription() {
        return """Clones this issue to another issue, optionally in another project, and optionally a different issue type.
        """
    }
 
    List getCategories() {
        ["Function", "Listener"]
    }
 
    List getParameters(Map params) {
        [
            ConditionUtils.getConditionParameter(),
            [
                Name:FIELD_TARGET_PROJECT,
                Label:"Target Project",
                Type: "list",
                Description:"Target project. Leave blank for the same project as the source issue.",
                Values: CannedScriptUtils.getProjectOptions(true),
            ],
            [
                Name:FIELD_TARGET_ISSUE_TYPE,
                Label:"Target Issue Type",
                Type: "list",
                Description:"""Target issue type. Leave blank for the same issue type as the source issue.
                    <br>NOTE: This issue type must be valid for the target project""",
                Values: CannedScriptUtils.getAllIssueTypes(true),
            ],
            getOverridesParam(),
            [
                Name:FIELD_LINK_TYPE,
                Label:'Issue Link Type',
                Type: "list",
                Description:"What link type to use to create a link to the cloned record.",
                Values: CannedScriptUtils.getAllLinkTypes(true),
            ],
            [
                Name:FIELD_LINK_DIRECTION,
                Label:'Link direction',
                Type: "list",
                Description:"What should be the direction of the link",
                Values: [NORMAL:'Normal', REVERSE:'Reverse'],
            ],
        ]
    }
 
    public ErrorCollection doValidate(Map params, boolean forPreview) {
        SimpleErrorCollection errorCollection = new SimpleErrorCollection()
        if (!params[FIELD_LINK_TYPE]) {
            errorCollection.addError(FIELD_LINK_TYPE, "You must provide a link type.")
        }
        // todo: validation for issue type if set
        return errorCollection
    }
 
    Map doScript(Map params) {
        MutableIssue issue = params['issue'] as MutableIssue
 
        Boolean doIt = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, issue, false, params)
        if (! doIt) {
            return [:]
        }
 
        params = super.doScript (params)
 
        Issue newIssue = params['newIssue'] as Issue
 
 
        String linkTypeId = params[FIELD_LINK_TYPE] as String
 
        // get the current list of outwards depends on links to get the sequence number
        IssueLinkManager linkMgr = ComponentManager.getInstance().getIssueLinkManager()
 
        if (linkTypeId && linkMgr.isLinkingEnabled()) {
            IssueLinkTypeManager issueLinkTypeManager = (IssueLinkTypeManager) ComponentManager.getComponentInstanceOfType(IssueLinkTypeManager.class)
 
            IssueLinkType linkType = issueLinkTypeManager.getIssueLinkType(linkTypeId as Long)
 
            if (linkType) {
                if ("REVERSE".equals(params[FIELD_LINK_DIRECTION])) {
                    linkMgr.createIssueLink (newIssue.genericValue.id, issue.id, linkType.id, 0, getUser(params))
                } else {
                    linkMgr.createIssueLink (issue.id, newIssue.genericValue.id, linkType.id, 0, getUser(params))
                }
            }
            else {
                log.warn ("No link type $linkTypeId found")
            }
        }
 
        params
    }
 
 
    String getDescription(Map params, boolean forPreview) {
        ConstantsManager constantsManager = ComponentManager.getInstance().getConstantsManager()
 
        StringBuffer sb = new StringBuffer()
        sb << getName() + "<br>Issue will be cloned to project " + (params[FIELD_TARGET_PROJECT] ?: "same as parent")
        sb << "<br>With issue type: " + (params[FIELD_TARGET_ISSUE_TYPE] ? constantsManager.getIssueTypeObject(params[FIELD_TARGET_ISSUE_TYPE] as String)?.name : "same as parent")
        sb.toString()
    }
 
    public Boolean isFinalParamsPage(Map params) {
        true
    }
 
}

The patch has been submitted for inclusion in future versions of the plug-in, see GRV-124.

Adding an admin user in JIRA with embedded database

I was testing a JIRA migration today but when I restored the backup, I could not login anymore. The original system used external authentication (crowd) but that server was not available from my test machine. Oops, no users available, no ability to login.

After searching, I found the solution by modifying the database script. In the JIRA home directory, subdirectory database, add the following lines to the jiradb.script file:

INSERT INTO CWD_USER VALUES(999999,1,'localadmin','localadmin',1,'2012-01-04 19:49:08.864000000','2012-01-04 19:49:08.864000000','local','local','admin','admin','local admin','local admin','localadmin@localadmin.com','localadmin@localadmin.com','uQieO/1CGMUIXXftw3ynrsaYLShI+GTcPS4LdUGWbIusFvHPfUzD7CZvms6yMMvA8I7FViHVEqr6Mj4pCLKAFQ==')
INSERT INTO CWD_GROUP VALUES('888888','jira-administrators','jira-administrators',1,0,'2011-03-21 12:20:29.864000000','2011-03-21 12:20:29.864000000',NULL,NULL,'GROUP',1)
INSERT INTO CWD_GROUP VALUES('777777','jira-users','jira-users',1,0,'2011-03-21 12:20:29.864000000','2011-03-21 12:20:29.864000000',NULL,NULL,'GROUP',1)
INSERT INTO SCHEMEPERMISSIONS VALUES(444444,NULL,44,'group','jira-administrators')
INSERT INTO SCHEMEPERMISSIONS VALUES(333333,NULL,1,'group','jira-users')
INSERT INTO CWD_MEMBERSHIP VALUES(666666,888888,999999,'GROUP_USER','','jira-administrators','jira-administrators','localadmin','localadmin',1)
INSERT INTO CWD_MEMBERSHIP VALUES(555555,777777,999999,'GROUP_USER','','jira-users','jira-users','localadmin','localadmin',1)

Restarting JIRA you can now login using localadmin with password sphere.