Adam Tuttle

My Thoughts on James Harvey's Apology

In my last entry I detailed all of the evidence we could amass for the systematic and repeated plagiarism of James Harvey, aka WebDevSourcerer before he erased his existence from the internet. (We still have archive.org and the google cache, but I think we've built a pretty solid case against him already, why spend more effort?)

I knew at the time that Dave and Scott, the hosts of the CFHour podcast were also trying to reach out to him and get his side of the story. (How journalistic!) I never expected him to respond, given his track record of silently pulling down content instead of apologizing to those he was stealing from.

But he did.

As you may have heard on the most recent episode, if you've listened to it yet, he sent them back a response and attached an apology. He specifically said they could "disperse it as [they] see fit," and Scott sent me a copy for my use here. I'll reproduce the apology letter in full, and then comment on each section below.

To those out there on the internet, calling me a plagarist, I say this:

I am most sincerely sorry.

I meant no disrespect, nor did I intend (or have it interpreted) that I was taking credit for items I had posted. I was using a very well assembled reference, and appending to it, however that wasn't ever mentioned, nor does it need to be any further as the offending post was removed and purged.

I meant only repsect and admiration of my community peers, not trying to take credit for thier works in any way. Yes, perhaps I had "written" I had developed or written certain codes, as I've done similiar projects, and often do get them mixed up.

I am trying to get the exposure out about ColdFusion, and how dynamic and powerful a development language it is, and I can appreciate everyone's views about that. That's it isn't as "dead" as others would like to think, and assist in making it a prevelant language again.

Of that I am guilty, and I am sorry, with all my soul, about that.

Yes, perhaps I should have made it exceedingly clear in my writting that I was not the source of the reference, but merely a messenger and trying to get the exposure for the language back out there.

For a long time, I hadn't seen what other communities were saying about the coldfusion one, until now.

I'm sorry for promoting a fantastic language, I'm sorry for not citing my sources properly or clearly, and I'm most sorry that you all aren't happy with getting your work admired and respected by another peer.

-James Harvey


Ok... so first of all... I guess... I appreciate that he acknowledged he was in the wrong and (sort of) apologized. But let's look at what each individual section is actually saying, and see if that mounts to a successful apology.

To those out there on the internet, calling me a plagarist, I say this:

I am most sincerely sorry.

A decent start, I suppose.

I meant no disrespect, nor did I intend (or have it interpreted) that I was taking credit for items I had posted.

I find this disingenuous given that he literally said "This is a ColdFusion Component the Ol'Sourcerer wrote some time back" (that is a verbatim quote) in his blog post taking credit for Nathan's pagination CFC, and had a git commit that removed Nathan's copyright info. I think he very much meant to take credit for it.

I was using a very well assembled reference, and appending to it, however that wasn't ever mentioned, nor does it need to be any further as the offending post was removed and purged.

That you kept meticulous records of what you stole and from whom does not excuse the stealing. And he referred to it as if it were a single offending post to make it seem like we're blowing things out of proportion when at least 4 were identified and documented (screen shots still available), and he took down his entire website, Twitter account, and GitHub account to scrub any more offending content before it could be found. Shady? Shady.

I meant only repsect and admiration of my community peers, not trying to take credit for thier works in any way. Yes, perhaps I had "written" I had developed or written certain codes, as I've done similiar projects, and often do get them mixed up.

More disingenuous hand waving, in my opinion. I use jQuery a lot, and I write a lot of my own JavaScript, including modules and micro-libraries, but I never forget that I didn't write jQuery. Even if you forget...somehow... If you don't remove the copyright text, you'll have that to remind you.

Respect and admiration manifests as "check out this awesome code that Nathan wrote" not "The Ol' Sourcerer is at it again."

I am trying to get the exposure out about ColdFusion, and how dynamic and powerful a development language it is, and I can appreciate everyone's views about that. That's it isn't as "dead" as others would like to think, and assist in making it a prevelant language again.

Whether or not he's right about CF's abilities and perception (and if you want to think highly enough of him to believe him capable of such manipulation, this could be seen as attempting to garner support by claiming altruism for the platform, something most CF community members would respect), that does not excuse stealing in any way, shape, or form. Altruism or no.

Of that I am guilty, and I am sorry, with all my soul, about that.

I think I mostly believe this. If I were him I'd be losing a lot of sleep over this. If future would-be employers Google him, there's a chance his past could cost him a lot in the future. As well it should.

Yes, perhaps I should have made it exceedingly clear in my writting that I was not the source of the reference, but merely a messenger and trying to get the exposure for the language back out there.

And doing so properly would have taken less effort than what he did. A paragraph or two and a link is a heck of a lot easier to write than copying the code, reformatting it, and removing the copyright information. He systematically stole, and tried to hide the evidence.

If his intention was to shine a spotlight on available great content, he did a terrible job, and temporarily made himself look marginally better in the process. Which is more likely: that he did extra work to achieve a worse result and accidentally propped up his own reputation in the process, or that he did exactly what he was trying to do?

For a long time, I hadn't seen what other communities were saying,about the coldfusion one, until now.

What does that even mean? I could speculate about this, but I'll just let it go for now.

I'm sorry for promoting a fantastic language, I'm sorry for not citing my sources properly or clearly, and I'm most sorry that you all aren't happy with getting your work admired and respected by another peer.

Nobody once criticized his admiration of CF. And I'd bet a beer (redeemable at dev.Objective() next May) that I speak for everyone that blogs about CFML or shares code when I say: We love the admiration and respect of peers. That's not why we blog and share code, but it does give us a warm, happy feeling.

Maybe he was promoting CF... great? But in what reality does stealing count as respect and admiration?

Sorry James. I'm not impressed by your "apology." You admitted wrongdoing but barely took responsibility. Still pretty unprofessional, in my book.

Do Not Hire: James Harvey "WebDevSourcerer"

If you see this man, do not hire him. He is of low moral character and not to be trusted.

I've taken this above photo of James Harvey from his own About Me page (google cache), and copied it to my own server, without his permission, on the off chance that he takes it down or renames it to try and remove it from this entry. He's already proven he's capable of worse.

Indeed, before I could even finish writing this entry he seems to have taken his whole website offline.


Over the last few days, a few of us have discovered and begun investigating what seemed to be recurring plagiarism of authors and works in the ColdFusion community. It all started with Adam Cameron noticing someone stealing his CFScript documentation and claiming it to be his own work (google cache).

When prodded about it, James Harvey, who also goes by WebDevSourcerer on Twitter and on Github, simply removed the offending blog post without so much as a, "Sorry about that!"

Of course we're still digging!

So far, Sean Corfield has found two more instances: One in which he claims that he wrote (google cache) some code that Nathan Strutz posted. Since I expect he'll take that down too, here's a screenshot for posterity: (click for full size)

And the second instance that Sean has found so far is one where he is claiming a stack overflow answer by Tony Petruzzi to be his own (google cache). Screenshot for posterity: (click for full size)

As I've started writing this, it looks like Kev McCabe has found another instance, where James has ripped off (google cache) content from Ray Camden. Again, screenshot for posterity: (click for full size)

And that's where I'm going to stop. ...For now? For the canonical list of things that we know James Harvey has plagiarized, check Adam Cameron's similar blog entry: Public service announcement: James Harvey / @webdevsourcerer is a systematic plagiariser

Why am I so riled up about this? Because I have spent almost 8 years creating free (not always great, but always honest!) content for the ColdFusion community. I would be absolutely sick if I found any of my content on his website with his name on it. (And believe me, James: I'm on the hunt.) And these are my friends he's been ripping off. I know I've had beers (or other non-alcoholic bevvie's in Nathan's case) with 3 of the 4 victims mentioned so far and I'm about half sure on the 4th.

Furthermore, in this industry without your integrity you're nothing. James Harvey? You're nothing, buddy. I hope all of our announcements about your abhorrent behavior outrank every other page about you on Google, and every potential employer you ever apply to Googles you. I hope your self-aggrandizing behavior was worth it, because it might turn out to have been career suicide.


To anyone else that happens to be named James Harvey and be unfortunate enough to work in the IT industry, I feel sorry for you. But this must not stand. That's a big part of why I've included his photograph. At least you can have facial comparison on your side...

Save Elvis! er, I mean ?:

Today was an interesting day.

I got an alert from the Adobe Bug Tracker, as I often do, to let me know that there was a change to one of the bugs I created / voted for / commented on. I clicked through to see what bug it was and what was new. It was a new comment on this beauty (CF11u3 broke the ?: operator) from everyone's favorite CFML curmudgeon (I think?), Adam Cameron. He asked what the status was for releasing the fix.

"A valid question!" I thought to myself, considering the bug was marked fixed on December 22nd, 2014. What gives, Adobe? Where's the fix? So I tweeted it, too. I believe strongly that the prescribed methods of communication should be used, until you start getting ignored or otherwise let down. Then, if the cause is just, it's time for a small dose of public shaming:

A few tweets were had back and forth between myself, Elishia, and Anit, with the end result being that the fix is scheduled for the next regularly scheduled update. Sounds great, right? Except updates are quarterly. Who wants to wait 3 months for a regression to be fixed? Is that even reasonable for a commercial platform? I don't think so, personally. Here's what I wrote in a comment on the bug, following that conversation:

I've already stated this elsewhere but just to put it on the record: I think that waiting for a quarterly update to fix a regression Adobe caused is terrible.

You say, "If you need a fix sooner, please contact our support team to request a private fix for this."

This implies that you have or can fairly easily create an update that fixes the issue. Why not just release that as update 4 and push all of the other non-update3-regressions back to update 5 on its normal schedule?

Further, "If you need a fix" can be rewritten as, "If you're using this feature" and I can assure you that either people have rolled back to update 2 or removed the feature from their code, because having syntax errors in your code is not just something you can leave be while you wait 3 months for a fix.

Either one is a losing proposition: There were things that are very nice to have included in Update 3 (many, many things, if my memory is correct. And if so, good and thank you for the update!) so rolling back to Update 2 denies us of useful features and bug fixes JUST like staying on Update 3 would deny us of this feature.

And lest you think I'm just being a jerk-nose for the sake of jerk-nosing...

Alas, that was the last they seem to have to say on the matter. Unacceptable, in my opinion. They did, however, offer to provide a "private fix" (their words) for anyone that asked.

For my part, while I do want it fixed, I don't consider myself special. I don't have a support contract — aside, you know, from the fact that CF11 was just released and is still in core support. So I'm not requesting a "private fix." But that doesn't mean you shouldn't.

In fact, I had the idea that we should more or less flood them with (legitimate) requests for the "private fix" in hopes that, if we can get enough, it will be easier for them just to go ahead and release an out-of-band patch. Also known as "doing the right thing."

So I made a form for you. Just click on over here, enter your name and email address, and submit the form. They'll be emailed, and you and I will be copied. (I keep a copy for posterity. Don't want any funny business with fudged numbers...)

Please feel free to share the link, and please only use the form once. It does not make any attempt to prevent duplicate submissions, and in my estimation they'll do more harm than good, so let's just keep things on the up and up, ok?

In Response to CFHour #226

The latest episode of the CFHour podcast mentioned me in two consecutive segments, and in the latter they said that they fully expected me to respond... And who am I to let down a podcast to which I regularly listen?

First, they very kindly mention that I wrote a book! Thanks for that mention, guys. If you had reached out to let me know you were going to mention it, I would have been more than happy to offer a discount code for your listeners. Alas, I'll have to give that out here instead. CFHour listeners can save 10% by using this link.

The second segment was about my previous blog post, detailing the reasons that we gave up attempting to switch to Railo.

ORM Mapped Superclass entities named the same

As I mentioned in my previous post:

using MappedSuperClass with ORM entities that have the same name (e.g. User and base/User) — for reasons I don't feel like elaborating on but I assure you are completely legitimate — causes a Stack Overflow error

I guess Scott didn't accept my assurances of legitimacy. Not that I blame him, I'm always a skeptic, myself...

Scott: I guess I kind of understood, but I would never think to do that. Like, I would probably name the base user cfc, baseUser.cfc
Dave: Mmmhmm
Scott: I've used MappedSuperClasses a lot in the stuff that I do. As a matter of fact, all of my ORM entities extend a BaseEntity that has common methods that I want available to each one of the entities.
Dave: Yeah, most of mine do too.
Scott: I would just never think to name them the same name. Or more to the point, I wouldn't think to not name them base{Blah}, as part of the actual name itself—
Dave: Yeah
Scott: —rather than—
Dave: Uh-huh
Scott: —using a path to delineate them.

Ok guys, here goes. We've got lots of entities, and this system is only fractionally complete. We're sitting on around 50 entities right now and can easily see that expanding to be several hundred. In addition to that, this is for a resellable product, not a one-install bespoke system. So we're writing with customer-customizations in mind. Our customers are universities and colleges across the country (and maybe eventually the world?) and I'm sure you realize that they all have their own favorite way of storing data and their own favorite custom fields; so we're planning ahead to support that sort of thing.

Maybe User was a poor example. Universities tend to refer to their students, alumni, and sometimes even faculty and staff, as Constituents, so let's go with that. Uni A wants an isFaculty flag, an isAlumni flag, an isParent flag, and so-on (not a terrible idea, considering it's possible to be all 3 of those, and more)... Meanwhile Uni B might prefer to just have a type_code column with coded values to represent every possible case. Neither is objectively wrong, and it would be a crappy reason to lose a potential customer if we couldn't support their desired data formats.

So how did this drive the naming decision? Well, with potentially several hundred entities on our hands, our first goal was to set things up so that customizing the entities for one customer didn't make it difficult to maintain the source code (remember, this is all going into git) for other customers. So the strategy we decided on was to put the system properties in the MappedSuperClass orm/base/Constituent.cfc, and the custom properties into the child class orm/Constituent.cfc. Each customer will have their own branch of the repo, so the child class will stay empty in the master branch and the customer modifications to it will be done only in the customer's branch. This should (in theory!) practically eliminate merge conflicts (at least in the case of ORM entities), and keep each branch pretty clean.

We wanted to make maintenance as easy as possible, too. I suppose baseConstituent.cfc is just as easy to remember as base/Constituent.cfc — no argument there. But what happens when an entity legitimately needs to start with the word "Base?" Sure, we can have BaseBaseSalary.cfc (Yes, that's completely contrived: Why would you need a Base Salary entity? I've no idea!) but that just has a certain smell to it that I'm not fond of... like milk that hasn't quite expired yet, but still makes you think twice before drinking it. So instead we stuffed them in a base/ folder and called it a day.

As you've probably figured out, this was something that we were able to deal with relatively quickly: It wasn't "the last straw," so it had already been resolved by the time that last straw was reached. I do have to say, though, that I was very happy to revert the commit that made this work on Railo and go back to my base/Constituent.cfc strategy.

CFImport vs. App-Specific Mappings

I think we're mostly of the same mind on this, so just one quick note here: I think that the only way I'll consider this functionality "fixed" is when mappings are respected in all cases on all platforms. Anything short of that is a cop-out for what amounts to "academic" reasons. (If you're asking yourself what constitutes "academic" reasoning, think about your ipod playing the same song 5 times in a row in shuffle mode even though you'd hate that, because that's how random numbers work sometimes... Academic reasoning is not always the best.) And I don't think CF11 has, as you mentioned Scott, "fixed" (removed) the include-based workaround. I'm pretty sure it still works fine for me on CF11, though I did not revert the code changes I made to get this functionality running on Railo.

Metadata

Scott: This is the one that really had me kind of scratching my head

No worries, I'll explain!

Your description of the problem is dead-on: We've got functions with a definition like this:

function remove( rc ) iq:role="users:delete" {}

In fact, nearly every Controller method looks like that. It's the configuration necessary for our role-based security implementation. We combine this metadata with FW/1's before() lifecycle event to authorize (nearly) every pageview / form submission / etc a user makes in the application. This makes the code very terse, which is a particular passion of mine, but does come with its own challenges. For one, getting that metadata is on the "expensive" (in computational time) side. Again, we have to make use of a base class, but in this case it's just a generic baseController that all controllers extend:

component {

    function init(){
        var md = getMetadata( this );

        for ( var fn in md.functions ){
            if ( structKeyExists( fn, "IQ:ROLE" ) ){
                variables.permissions[ fn.name ] = fn['IQ:ROLE'];
            }
        }
    }

    function before( rc ){
        //if the requested action requires user to be logged in, verify that they are
        verifyLoggedIn( rc.action );

        //if user is logged in, check role-based security for the requested action
        verifyPermissions( framework.getItem() );
    }

    function verifyPermissions( 
        methodName
        ,failMessage = 'You do not have access to the requested functionality'
        ,jailEvent = 'main:jail/home'
    ){
        //action doesn't have role requirement
        if ( !structKeyExists( variables.permissions, methodName ) ){
            return true;
        }

        var requiredRole = variables.permissions[ arguments.methodName ];
        if ( session.user.isAssignedRole( trim(requiredRole) ) ){
            return true;
        }

        request.context.requiredRole = requiredRole;

        framework.redirect( arguments.jailEvent, "requiredRole" );
    }

}

When each controller is initialized (infrequently) it grabs a copy of its own metadata (see init()) and stores a copy of the iq:role metadata in the component instance (variables scope) for later reference. Then, during the before() lifecycle event, it is referenced to make sure the logged in user has access to the requested action, before the action happens.

Apologies, that was a rather long-winded way of getting to this next part: Why is the metadata named iq:role? Why not iq_role? The short answer is: Because when you can, it makes sense to... And you can on Adobe CF10+... So I did.

In a few more words: Namespacing. You never know what crazy feature the platform vendor will come out with next, right? I mean... CFClient, anyone? So by appending custom metadata to your functions, you run the risk that one day they will release a new version that you really want to use, but now they have revitalized <cflogin> to actually be relevant, and oh by the way, it uses a role metadata attribute on your functions. So you have to go change every function that uses your custom role metadata attribute if you're not lucky enough to have been using it "their way" before they were. By prepending some characters that are unique to your application, you reduce the risk of a collision later down the line. Just to be clear, the extra characters don't change the functionality in any way, they just reduce that risk of collision. That's what namespacing is for, but it still doesn't fully explain why ours uses a colon instead of an underscore or something else.

And the answer to that question is: because I said so! When you're in charge of decisions like that (and I am, right now), you sometimes get to pick things because you like the way they look. On Adobe ColdFusion, iq_role and iq:role are functionally identical, so I picked the one that I found more visually appealing. I can't really explain why I find the colon more pleasing to the eye, but I do. I probably saw it somewhere else (Java? Python? Who knows...) and it stuck with me. And it continues to serve us well on the ACF platform.

Taffy users are also probably familiar with it, as Taffy supports both taffy_uri and taffy:uri metadata attributes for specifying a resource's uri mapping (for both Railo and ACF8-9 support reasons).

So why doesn't Railo support it? Because they also support this: role:"foo". At some point in the past they chose to accept a colon as the delineator between the LHS and RHS of attributes, and as a consequence they can not support colons in the LHS. For them, it is like trying to write: iq=role="foo" — and I accept that. That is a particularly hard problem to solve, and I don't blame them for not considering it a high priority. I find it unfortunate that they dug that hole for themselves, but there's no use dwelling on the past: This is the world we live in, so this is what we have to deal with. Doing otherwise would be like fighting gravity.


Scott: You know, when people run into these problems and I don't, I can't help but think, "Are they doing something really weird, or am I doing something really weird?" Are they doing it wrong, or am I doing it wrong? And you know, when you're talking about me and Adam Tuttle, I would err on the side of me doing it wrong.

That's very kind of you to say so, Scott, but I think that this time it's very clearly my fault that it doesn't work on Railo... I didn't originally make the choice with platform agnosticism in mind. But would I go so far as to say it is "wrong"? I think not.


One last thing about that previous post ("We Tried (and Failed) to Switch to Railo")... While it was not meant to be putting down Railo in any way, I think it would be easy to see it in that light. I actually really think Railo is a great option; even more so if you get to start with a clean slate. But I just wanted to put out some information that showed a first-hand account that switching from one platform to the other is not all unicorns and rainbows. There is significant work involved. So when the fanboys spout off on mailing lists that you should just switch, you can rightfully start ignoring them. It's not that simple.

Scott had mentioned toward the end of that segment that he was impressed that these were the only issues that we ran into, and there is some truth to that statement, but probably not the kind he intended. It's more like the truth that your keys are always in the last place you look for them because you stop looking when you find them. We gave up once we ran into the metadata issue. It's possible that could have been the last thing; but it's equally possible that there could have been 30 other differences to deal with once the metadata problem was resolved.

In truth, we were working at a running pace. Steve was about to leave for a road trip and I was head-down on something unrelated, and while we were switching to a new AWS EC2 instance, we thought it would be a good opportunity to give Railo a try. We didn't really have the time to deal with a lot of issues that needed sorting out, so our threshold for conversion pain was pretty low. It ended up being about a 2 hour experiment that proved it wasn't going to be quick and painless to switch. Would we make these changes down the road and try again? We might... When we have time available to deal with it.