Archive for October 28th, 2010

Oracle, Android and the copy claims: SCO all over again?

Second important update: After quite some digging, I have finally found out that the file had the following history:

  • An initial import, from a private branch to the public git repo, with this commitdiff, at Sat, 10 Jan 2009 01:50:54
  • A deletion of the file and of most of the imported branch at Wed, 4 Mar 2009 02:28:14, with this commitdiff
  • The branch got re-imported, with all new files, at Wed, 4 Mar 2009 03:28:47, with this commitdiff, without PolicyNodeImpl.java

So the file was present in the git repo from Jan, 10 to March, 4; after checking the test execution code, it is clear that the code itself was not included in the final build (delivered to handsets) but was part of a test harness, that (funnily) was mostly silenced during the development period due to the majority of tests failing :-) It is however clear that the code itself was distributed, as it was freely accessible online and through the git tree (and – funnily – it is still available under the same means). It was, however, not part of the Android SDK release, as Android 1.6 rel2 was released in December 2009, while rel3 was released in May, with the commit diff already applied.

Important update: I have verified that the code included in Android is actually a decompilation of an old Java 1.5 class file, and my own comparison is invalid, as it was done with a recent Java edition (that does have more changes – thus suggesting a reimplementation). Also, it seems that Harmony actually has not that code in its repo – despite the fact that in Android is part of the initial import under the “Harmony” subproject. My apologies.

Sigh. I had higher hopes for Oracle – while the initial complaint was dull and unsubstantial, the claims of actual copying of source code was an interesting twist (apart from the patents, of course – but that is a totally different world). Now, as ZDnet promptly writes, Oracle amended the complaint, and published the scribd link with the entire text (by the way: the HTML5 version of Scribd really, really rocks. End of parenthesis.) The relevant parts are:

40. Android includes infringing class libraries and documentation. Approximately one third of Android’s Application Programmer Interface (API) packages (available at http://developer.android.com/reference/packages.html) are derivative of Oracle America’s copyrighted Java API packages (available at http://download-llnw.oracle.com/javase/1.5.0/-docs/api/ and http://download-llnw.oracle.com/javase/1.4.2/docs/api/) and corresponding documents. The infringed elements of Oracle America’s copyrighted work include Java method and class names, definitions, organization, and parameters; the structure, organization and content of Java class libraries; and the content and organization of Java’s documentation. Examples of this copying are illustrated in Exhibit I to this complaint. In at least several instances, Android computer program code also was directly copied from copyrighted Oracle America code. For example, as may be readily seen in Exhibit J, the source code in Android’s “PolicyNodeImpl.java” class is nearly identical to “PolicyNodeImpl.java” in Oracle America’s Java, not just in name, but in the source code on a line-for-line basis.”

Hm. First of all, the definition of line-for-line equality is not correct here, as the lines are different (but quite similar). I have not developed for quite some time, but I would say that it is not strange to see similarities within the API constraints. Second, while technically part of Android, the code is Apache Harmony, a reimplementation of J2SE that tried (for many, many years) to get the compliance toolkit from Sun, but never did (and now will never do).

But the relevant point is different: the PolicyNodeImpl.java that is presented comes from the OpenJDK distribution, and was as such released under the GPL+ClassPath exception (something that is not mentioned anywhere within the complaint, by the way). Here, the claims are two and different: the first is that Android (actually, Harmony) copied its API that Oracle claims is copyrighted. The second claim is that the actual source code of the PolicyNodeImpl.java file has been copied verbatim.

Let’s start with the first one: the claim that Oracle Java APIs are protected and copyrighted. On this, it seem to me that the interface definition themselves (not the actual source code) as a mere interface does not fall within the copyright provisions, unless the actual names are trademarked, and thus its implementation requires the actual copying of a protected name in a way that is deemed incompatible by its licensee (something similar was done by Autodesk, embedding a copyrighted phrase that if not included in the file prevented the application from opening it directly). I believe that this is not the case, as the names within the source code seem to lack any “TM” or the direct use of “Java” in a way that may suggest its direct protection as a trademark.I(t should be noted that both “Java” and “OpenJDK” are trademarked, and its use is explicitly forbidden unless the code is a

And now, for the claim that the code is actually copied, here is in its glory the full diff (note: if you download the OpenJDK source you don’t get the file; I had to grab it from here in the Mercurial publication site, the raw file is this one). It seem to me that this is actually a reimplementation and not a straight copy as Oracle claims – but I would like to ask my readers for their opinion.

A disclaimer: I am not a Oracle hater, a Google or Apache fanboy, and I have no relationship with them all.

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or moreCopyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License atDO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *     http://www.apache.org/licenses/LICENSE-2.0This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the LicenseThis code is distributed on an "AS IS" BASIS,in the hope that it will be useful, but WITHOUT
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressWARRANTY; without even the implied warranty of MERCHANTABILITY or implied.
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the specific language governing permissionsLICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package sun.security.provider.certpath;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.security.cert.*;
/**
 * Implements the <code>PolicyNode</code> interface.
 * <p>
 * This class provides an implementation of the <code>PolicyNode</code>
 * interface, and is used internally to build and search Policy Trees.
 *  limitations underWhile the License.implementation is mutable during construction, it is immutable
 * before returning to a client and no mutable public or protected methods
 * are exposed by this implementation, as per the contract of PolicyNode.
 *
 * @since       1.4
 * @author      Seth Proctor
 * @author      Sean Mullan
 */
package org.apache.harmony.security.tests.support.cert;
import java.security.cert.PolicyNode;
import java.util.*;
publicfinal class PolicyNodeImpl implements PolicyNode {
    /**
     * Use to specify the special policy "Any Policy"
     */
    private static final String ANY_POLICY = "2.5.29.32.0";
    // every node has one parent, and zero or more children
    private PolicyNodeImpl mParent;
    private HashSetHashSet<PolicyNodeImpl> mChildren;
    // the 4 fields specified by RFC 3280
    private String mValidPolicy;
    private HashSetHashSet<PolicyQualifierInfo> mQualifierSet;
    private boolean mCriticalityIndicator;
    private HashSetHashSet<String> mExpectedPolicySet;
    private boolean mOriginalExpectedPolicySet;
    // the tree depth
    private int mDepth;
    private// immutability flag
    private boolean isImmutable;
    public PolicyNodeImpl(PolicyNodeImpl policynodeimpl,isImmutable = false;
    /**
     * Constructor which takes a <code>PolicyNodeImpl</code> representing the
     * parent in the Policy Tree to this node. If null, this is the
     * root of the tree. The constructor also takes the associated data
     * for this node, as found in the certificate. It also takes a boolean
     * argument specifying whether this node is being created as a result
     * of policy mapping.
     *
     * @param parent the PolicyNode above this in the tree, or null if this
     *               node is the tree's root node
     * @param validPolicy a String s,representing this node's valid policy OID
     * @param qualifierSet the Set set,
                   boolean flag,of qualifiers for this policy
     * @param criticalityIndicator a boolean representing whether or not the
     *                             extension is critical
     * @param expectedPolicySet a Set set1,of expected policies
     * @param generatedByPolicyMapping a boolean flag1)indicating whether this
     * node was generated by a policy mapping
     */
    PolicyNodeImpl(PolicyNodeImpl parent, String validPolicy,
                Set<PolicyQualifierInfo> qualifierSet,
                boolean criticalityIndicator, Set<String> expectedPolicySet,
                boolean generatedByPolicyMapping) {
        isImmutable = false;
        mParent = policynodeimpl;parent;
        mChildren = new HashSet();
        if(sHashSet<PolicyNodeImpl>();
        if (validPolicy != null) {
            mValidPolicy = s;
        } else {validPolicy;
        else
            mValidPolicy = "";
        }
        if(setif (qualifierSet != null) {
            mQualifierSet = new HashSet(set);
        } else {HashSet<PolicyQualifierInfo>(qualifierSet);
        else
            mQualifierSet = new HashSet();
        }HashSet<PolicyQualifierInfo>();
        mCriticalityIndicator = flag;
        if(set1criticalityIndicator;
        if (expectedPolicySet != null) {
            mExpectedPolicySet = new HashSet(set1);
        } else {HashSet<String>(expectedPolicySet);
        else
            mExpectedPolicySet = new HashSet();
        }HashSet<String>();
        mOriginalExpectedPolicySet = !flag1;
        if(mParent!generatedByPolicyMapping;
        // see if we're the root, and act appropriately
        if (mParent != null) {
            mDepth = mParent.getDepth() + 1;
            mParent.addChild(this);
        } else {
            mDepth = 0;
        }
    }
    PolicyNodeImpl(PolicyNodeImpl policynodeimpl,
                   PolicyNodeImpl policynodeimpl1)/**
     * Alternate constructor which makes a new node with the policy data
     * in an existing <code>PolicyNodeImpl</code>.
     *
     * @param parent a PolicyNode that's the new parent of the node, or
     *               null if this is the root node
     * @param node a PolicyNode containing the policy data to copy
     */
    PolicyNodeImpl(PolicyNodeImpl parent, PolicyNodeImpl node) {
        this(policynodeimpl, policynodeimpl1.mValidPolicy, ((Set) (policynodeimpl1.mQualifierSet)), policynodeimpl1.mCriticalityIndicator, ((Set) (policynodeimpl1.mExpectedPolicySet)),
        this(parent, node.mValidPolicy, node.mQualifierSet,
             node.mCriticalityIndicator, node.mExpectedPolicySet, false);
    }
    public PolicyNode getParent() {
        return mParent;
    }
    public IteratorIterator<PolicyNodeImpl> getChildren() {
        return Collections.unmodifiableSet(mChildren).iterator();
    }
    public int getDepth() {
        return mDepth;
    }
    public String getValidPolicy() {
        return mValidPolicy;
    }
    public SetSet<PolicyQualifierInfo> getPolicyQualifiers() {
        return Collections.unmodifiableSet(mQualifierSet);
    }
    public SetSet<String> getExpectedPolicies() {
        return Collections.unmodifiableSet(mExpectedPolicySet);
    }
    public boolean isCritical() {
        return mCriticalityIndicator;
    }
    /**
     * Return a printable representation of the PolicyNode.
     * Starting at the node on which this method is called,
     * it recurses through the tree and prints out each node.
     *
     * @return a String describing the contents of the Policy Node
     */
    public String toString() {
        StringBuffer stringbufferbuffer = new StringBuffer(asString());
        for(Iterator iteratorStringBuffer(this.asString());
        Iterator<PolicyNodeImpl> it = getChildren(); iterator.hasNext(); stringbuffer.append((PolicyNodeImpl)iterator.next()));
        while (it.hasNext()) {
            buffer.append(it.next());
        }
        return stringbuffer.toString();buffer.toString();
    }
    // private methods and package private operations
    boolean isImmutable() {
        return isImmutable;
    }
    /**
     * Sets the immutability flag of this node and all of its children
     * to true.
     */
    void setImmutable() {
        if(isImmutable)  return;
        PolicyNodeImpl policynodeimpl;
        for(Iterator iterator = mChildren.iterator(); iterator.hasNext(); policynodeimpl.setImmutable())
            policynodeimpl = (PolicyNodeImpl)iterator.next();if (isImmutable)
            return;
        for (PolicyNodeImpl node : mChildren) {
            node.setImmutable();
        }
        isImmutable = true;
    }
    private/**
     * Private method sets a child node. This is called from the child's
     * constructor.
     *
     * @param child new <code>PolicyNodeImpl</code> child node
     */
    private void addChild(PolicyNodeImpl policynodeimpl)child) {
        if(isImmutable)
        if (isImmutable) {
            throw new IllegalStateException("PolicyNode is immutable");
        } else {
            mChildren.add(policynodeimpl);
            return;
        }
        mChildren.add(child);
    }
    void/**
     * Adds an expectedPolicy to the expected policy set.
     * If this is the original expected policy set initialized
     * by the constructor, then the expected policy set is cleared
     * before the expected policy is added.
     *
     * @param expectedPolicy a String representing an expected policy.
     */
    void addExpectedPolicy(String s)expectedPolicy) {
        if (isImmutable) {
        if(isImmutable)
            throw new IllegalStateException("PolicyNode is immutable");
        if(mOriginalExpectedPolicySet)}
        if (mOriginalExpectedPolicySet) {
            mExpectedPolicySet.clear();
            mOriginalExpectedPolicySet = false;
        }
        mExpectedPolicySet.add(s);mExpectedPolicySet.add(expectedPolicy);
    }
    void/**
     * Removes all paths which don't reach the specified depth.
     *
     * @param depth an int representing the desired minimum depth of all paths
     */
    void prune(int i)depth) {
        if(isImmutable)
        if (isImmutable)
            throw new IllegalStateException("PolicyNode is immutable");
        if(mChildren.size()// if we have no children, we can't prune below us...
        if (mChildren.size() == 0)
            return;
        Iterator iteratorIterator<PolicyNodeImpl> it = mChildren.iterator();
        do
        while (it.hasNext()) {
            if(!iterator.hasNext())  break;
            PolicyNodeImpl policynodeimplnode = (PolicyNodeImpl)iterator.next();
            policynodeimpl.prune(i);
            if(policynodeimpl.mChildren.size()it.next();
            node.prune(depth);
            // now that we've called prune on the child, see if we should
            // remove it from the tree
            if ((node.mChildren.size() == 00) && i(depth > mDepth + 1)
                iterator.remove();1))
                it.remove();
        } while(true);
    }
    void/**
     * Deletes the specified child node of this node, if it exists.
     *
     * @param childNode the child node to be deleted
     */
    void deleteChild(PolicyNode policynode)childNode) {
        if(isImmutable)
        if (isImmutable) {
            throw new IllegalStateException("PolicyNode is immutable");
        } else {
            mChildren.remove(policynode);
            return;
        }
        mChildren.remove(childNode);
    }
    /**
     * Returns a copy of the tree, without copying the policy-related data,
     * rooted at the node on which this was called.
     *
     * @return a copy of the tree
     */
    PolicyNodeImpl copyTree() {
        return copyTree(null);
    }
    private PolicyNodeImpl copyTree(PolicyNodeImpl policynodeimpl)parent) {
        PolicyNodeImpl policynodeimpl1newNode = new PolicyNodeImpl(policynodeimpl,PolicyNodeImpl(parent, this);
        PolicyNodeImpl policynodeimpl2;
        for(Iterator iterator = mChildren.iterator(); iterator.hasNext(); policynodeimpl2.copyTree(policynodeimpl1))
            policynodeimpl2 = (PolicyNodeImpl)iterator.next();
        for (PolicyNodeImpl node : mChildren) {
            node.copyTree(newNode);
        }
        return policynodeimpl1;newNode;
    }
    Set/**
     * Returns all nodes at the specified depth in the tree.
     *
     * @param depth an int representing the depth of the desired nodes
     * @return a <code>Set</code> of all nodes at the specified depth
     */
    Set<PolicyNodeImpl> getPolicyNodes(int i)depth) {
        HashSet hashset
        Set<PolicyNodeImpl> set = new HashSet();
        getPolicyNodes(i, ((Set) (hashset)));HashSet<PolicyNodeImpl>();
        getPolicyNodes(depth, set);
        return hashset;set;
    }
    private/**
     * Add all nodes at depth depth to set and return the Set.
     * Internal recursion helper.
     */
    private void getPolicyNodes(int i, Setdepth, Set<PolicyNodeImpl> set) {
        if(mDepth
        // if we've reached the desired depth, then return ourself
        if (mDepth == i)depth) {
            set.add(this);
        } else {
            PolicyNodeImpl policynodeimpl;
            for(Iterator iterator = mChildren.iterator(); iterator.hasNext(); policynodeimpl.getPolicyNodes(i, set))
                policynodeimpl = (PolicyNodeImpl)iterator.next();for (PolicyNodeImpl node : mChildren) {
                node.getPolicyNodes(depth, set);
            }
        }
    }
    Set getPolicyNodesExpected(int i,/**
     * Finds all nodes at the specified depth whose expected_policy_set
     * contains the specified expected OID (if matchAny is false)
     * or the special OID "any value" (if matchAny is true).
     *
     * @param depth an int representing the desired depth
     * @param expectedOID a String s,encoding the valid OID to match
     * @param matchAny a boolean flag)indicating whether an expected_policy_set
     * containing ANY_POLICY should be considered a match
     * @return a Set of matched <code>PolicyNode</code>s
     */
    Set<PolicyNodeImpl> getPolicyNodesExpected(int depth,
        String expectedOID, boolean matchAny) {
        if (expectedOID.equals(ANY_POLICY)) {
        if(s.equals("2.5.29.32.0"))
            return getPolicyNodes(i);
        elsegetPolicyNodes(depth);
        } else {
            return getPolicyNodesExpectedHelper(i, s, flag);getPolicyNodesExpectedHelper(depth, expectedOID, matchAny);
        }
    }
    private SetSet<PolicyNodeImpl> getPolicyNodesExpectedHelper(int i, String s,depth,
        String expectedOID, boolean flag)matchAny) {
        HashSet hashset
        HashSet<PolicyNodeImpl> set = new HashSet();
        if(mDepthHashSet<PolicyNodeImpl>();
        if (mDepth < i)depth) {
            PolicyNodeImpl policynodeimpl;
            for(Iterator iterator = mChildren.iterator(); iterator.hasNext(); hashset.addAll(policynodeimpl.getPolicyNodesExpectedHelper(i, s, flag)))
                policynodeimpl = (PolicyNodeImpl)iterator.next();
            for (PolicyNodeImpl node : mChildren) {
                set.addAll(node.getPolicyNodesExpectedHelper(depth,
                                                             expectedOID,
                                                             matchAny));
            }
        } else if(flag) {
            if(mExpectedPolicySet.contains("2.5.29.32.0"))
                hashset.add(this);
        }
            if (matchAny) {
                if (mExpectedPolicySet.contains(ANY_POLICY))
                    set.add(this);
            } else if(mExpectedPolicySet.contains(s)) {
            hashset.add(this);
                if (mExpectedPolicySet.contains(expectedOID))
                    set.add(this);
            }
        }
        return hashset;set;
    }
    Set/**
     * Finds all nodes at the specified depth that contains the
     * specified valid OID
     *
     * @param depth an int representing the desired depth
     * @param validOID a String encoding the valid OID to match
     * @return a Set of matched <code>PolicyNode</code>s
     */
    Set<PolicyNodeImpl> getPolicyNodesValid(int i,depth, String s)validOID) {
        HashSet hashset
        HashSet<PolicyNodeImpl> set = new HashSet();
        if(mDepthHashSet<PolicyNodeImpl>();
        if (mDepth < i)depth) {
            PolicyNodeImpl policynodeimpl;
            for(Iterator iterator = mChildren.iterator(); iterator.hasNext(); hashset.addAll(policynodeimpl.getPolicyNodesValid(i, s)))
                policynodeimpl = (PolicyNodeImpl)iterator.next();
            for (PolicyNodeImpl node : mChildren) {
                set.addAll(node.getPolicyNodesValid(depth, validOID));
            }
        } else if(mValidPolicy.equals(s)) {
            hashset.add(this);
            if (mValidPolicy.equals(validOID))
                set.add(this);
        }
        return hashset;set;
    }
    private static String policyToString(String s)oid) {
        if(s.equals("2.5.29.32.0"))
        if (oid.equals(ANY_POLICY)) {
            return "anyPolicy";
        } else {
            return s;oid;
        }
    }
    /**
     * Prints out some data on this node.
     */
    String asString() {
        if(mParentif (mParent == null) {
            return "anyPolicy  ROOT\n";
        StringBuffer stringbuffer} else {
            StringBuffer sb = new StringBuffer();
        int
            for (int i = 0;
        for(int j0, n = getDepth(); i < j;n; i++)
            stringbuffer.append(" {
                sb.append("  ");
        stringbuffer.append(policyToString(getValidPolicy()));
        stringbuffer.append("
            }
            sb.append(policyToString(getValidPolicy()));
            sb.append("  CRIT: ");
        stringbuffer.append(isCritical());
        stringbuffer.append("
            sb.append(isCritical());
            sb.append("  EP: ");
        for(Iterator iterator = getExpectedPolicies().iterator(); iterator.hasNext(); stringbuffer.append(" "))
            for (String policy : getExpectedPolicies()) {
            String s = (String)iterator.next();
            stringbuffer.append(policyToString(s));
                sb.append(policyToString(policy));
                sb.append(" ");
            }
            sb.append(" (");
            sb.append(getDepth());
            sb.append(")\n");
            return sb.toString();
        }
        stringbuffer.append(" (");
        stringbuffer.append(getDepth());
        stringbuffer.append(")\n");
        return stringbuffer.toString();
    }
}

,

19 Comments

Some data on OSS TCO: results from past projects

During the development of the EU Cospa project, we found that one of the most common criteria used to evaluate “average” TCO was actually not very effective in providing guidance – as the variability of the results was so large that made any form of “average” basically useless. For this reason, we performed a two-step action: the first was to define a clearly measurable set of metrics (including material and immaterial expenses) and you can find it here:
D3.1 – Framework for evaluating return/losses of the transition to ODS/OSS

The second aspect is related to “grouping”. We found that the optimal methodology for evaluating migration was different for different kind of transitions, like server vs. desktop, full-environment migration vs. partial, and so on; the other orthogonal aspect is whether the migration was successful or not. In fact, *when* the migration is successful, the measured (both short-term and over 5 years) TCO was substantially lower in OSS compared to pre-existing proprietary software. I highlight two cases: a group of municipalities in the North of Italy, and a modern hospital in Ireland. For the municipalities:

Initial acquisition cost: proprietary 800K€, OSS 240K€

annual support/maintenance cost (over 5 years): proprietary 144K€, OSS 170K€

The slightly higher cost for the OSS part is related to the fact that an external consultancy was paid to provide the support. An alternative strategy could have been to retrain the staff for Linux support, using consultancies only in year 1 and 2- leading to an estimated total cost for the OSS solution exactly in line with the proprietary one. The municipalities also performed an in-depth analysis of efficiency; that is, documents processed per day, comparing openoffice and MS office. This was possible thanks to a small applet installed (with users and unions consent) on the PC, recording the user actions and the applications and files used during the migration evaluation. It was found that users were actually *more* productive with OOo in a substantial way. This is probably not related to a relative technical advantage of OOo vs. MS office, but on the fact that some training was provided on OpenOffice.org before beginning the migration – something that was not done before for internal personnel. So many users actually never had any formal training on any office application, and the limited (4 hours) training performed before the migration actually substantially improved their overall productivity.

On the other hand, it is clear that OOo is – from the point of view of the user – not lowering the productivity of employees, and can perform the necessary tasks without impacting the municipality operations.

- Hospital:
The migration was done in two steps; a first one (groupware, content management, openoffice) and a second one (ERP, medical image management).
In the first, the Initial acquisition cost was: proprietary 735K€, OSS 68K€

annual support/maintenance cost (over 5 year): proprietary 169K€, OSS 45K€

Second stage Initial acquisition cost: proprietary 8160K€, OSS 1710K€

annual support/maintenance cost (over 5 year): proprietary 1148K€, OSS 170K€

The hospital does have a much larger saving percentage when compared with other comparable cases because they were quite more mature in terms of OSS adoption; thus, most of the external, paid consulting was not necessary for their larger migration.

Some of the interesting aspects that we observed:

  • In both tangible and intangible costs, the reality is that one of the most important expense is software search and selection, and the costs incurred in selecting the “wrong” one. This is one of the reasons why in our guidelines we have included the use of established, pragmatic software selection methodologies like FLOSSMETRICS or QUALIPSO (actually we found no basic difference in “effectiveness” among methods: just use at least one!)
    This information is also something that can be reused and disseminated among similar groups; for example, the information on suitability of a backup solution for municipalities can be spread as a “best practice” among similar users, thus reducing the cost of searching and evaluating it. If such a widespread practice can be performed, we estimate that OSS adoption/migration costs can be reduced of something between 17% and 22% with just information spreading alone.
  • On average, the cost of migration (tangible vs. intangible) was nearly equal with one exception that was 27% tangible vs. 73% intangible, due to the pressure to use older pcs, and reuse resources when possible for budgetary reasons. In general, if you want to know the “real” TCO, simply take your material costs and multiply by two. Rough, but surprisingly accurate.
  • Both in COSPA, OpenTTT and our own consulting activity we found that 70% of users *do not need* external support services after the initial migration is performed. For example, while most of COSPA users paid for server support fees for RedHat Enterprise, a substantial percentage could have used a clone like Centos or Oracle linux with the same level of service and support. Also, while not universally possible, community-based support has been found sufficient and capable in a large number of environments. A problem with community support has been found in terms of “attitude”; some users accessed the forums with the same expectations of a paid offering, seriously damaging the image and possibility of support (something like “I need an answer NOW or I’ll sue you!” sent to a public support forum for an open source product). For this reason, we have suggested in our best practices to have a single, central point of contact between the internal users and the external OSS communities that is trained and expert in how OSS works to forward requests and seek solutions. This can reduce, after initial migration and a 1-2 year period of “adaptation”, support costs by shifting some of the support calls to communities. This can reduce costs of a further 15-20% on average.

, , ,

3 Comments