Last minute geek

last minute tech news from around the net

Friday, Sep 22nd

Last update01:00:00 AM

You are here: English WTF

WTF

Tales from the Interview: The In-House Developer

James was getting anxious to land a job that would put his newly-minted Computer Science degree to use. Six months had come to pass since he graduated and being a barista barely paid the bills. Living in a small town didn't afford him many local opportunities, so when he saw a developer job posting for an upstart telecom company, he decided to give it a shot.

Lincoln Log Cabin 2

We do everything in-house! the posting for CallCom emphasized, piquing James' interest. He hoped that meant there would be a small in-house development team that built their systems from the ground up. Surely he could learn the ropes from them before become a key contributor. He filled out the online application and happily clicked Submit.

Not 15 minutes later, his phone rang with a number he didn't recognize. Usually he just ignored those calls but he decided to answer. "Hi, is James available?" a nasally female voice asked, almost sounding disinterested. "This is Janine with CallCom, you applied for the developer position."

Caught off guard by the suddenness of their response, James wasn't quite ready for a phone screening. "Oh, yeah, of course I did! Just now. I am very interested."

"Great. Louis, the owner, would like to meet with you," Janine informed him.

"Ok, sure. I'm pretty open, I usually work in the evenings so I can make most days work," he replied, checking his calendar.

"Can you be here in an hour?" she asked. James managed to hide the fact he was freaking out about how to make it in time while assuring her he could be.

He arrived at the address Janine provided after a dangerous mid-drive shave. He felt unprepared but eager to rock the interview. The front door of their suite gave way to a lobby that seemed more like a walk-in closet. Janine was sitting behind a small desk reading a trashy tabloid and barely looked up to greet him. "Louis will see you now," she motioned toward a door behind the desk and went back to reading barely plausible celebrity rumors.

James stepped through the door into what could have been a walk-in closet for the first walk-in closet. A portly, sweaty man presumed to be Louis jumped up to greet him. "John! Glad you could make it on short notice. Have a seat!"

"Actually, it's James..." he corrected Louis, while also forgiving the mixup. "Nice to meet you. I was eager to get here to learn about this opportunity."

"Well James, you were right to apply! We are a fast growing company here at CallCom and I need eager young talent like you to really drive it home!" Louis was clearly excited about his company, growing sweatier by the minute.

"That sounds good to me! I may not have any real-world experience yet, but I assure you that I am eager to learn from your more senior members," James replied, trying to sell his potential.

Louis let out a hefty chuckle at James' mention of senior members. "Oh you mean stubborn old developers who are set in their ways? You won't be finding those around here! I believe in fresh young minds like yours, unmolded and ready to take the world by storm."

"I see..." James said, growing uneasy. "I suppose then I could at least learn how your code is structured from your junior developers? The ones who do your in-house development?"

Louis wiped his glistening brow with his suit coat before making the big revelation. "There are no other developers, James. It would just be you, building our fantastic new computer system from scratch! I have all the confidence in the world that you are the man for the job!"

James sat for a moment and pondered what he had just heard. "I'm sorry but I don't feel comfortable with that arrangement, Louis. I thought that by saying you do everything in-house, that implied there was already a development team."

"What? Oh, heavens no! In-house development means we let you work from home. Surely you can tell we don't have much office space here. So that's what it means. In. House. Got it?

James quickly thanked Louis for his time and left the interconnected series of closets. In a way, James was glad for the experience. It motivated him to move out of his one horse town to a bigger city where he eventually found employment with a real in-house dev team.

[Advertisement] Otter, ProGet, BuildMaster – robust, powerful, scalable, and reliable additions to your existing DevOps toolchain.

Read all

CodeSOD: A Dumbain Specific Language

I’ve had to write a few domain-specific-languages in the past. As per Remy’s Law of Requirements Gathering, it’s been mostly because the users needed an Excel-like formula language. The danger of DSLs, of course, is that they’re often YAGNI in the extreme, or at least a sign that you don’t really understand your problem.

XML, coupled with schemas, is a tool for building data-focused DSLs. If you have some complex structure, you can convert each of its features into an XML attribute. For example, if you had a grammar that looked something like this:

The Source specification obeys the following syntax

source = ( Feature1+Feature2+... ":" ) ? steps

Feature1 = "local" | "global"

Feature2 ="real" | "virtual" | "ComponentType.all"

Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

Feature4 = "first" | "last" | "DayAllocation.all"

If features are specified, the order of features as given above has strictly to be followed.

steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

oneOrMoreNameSteps = nameStep ( "." nameStep ) *

zeroOrMoreNameSteps = ( nameStep "." ) *

nameStep = "#" name

name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.

componentSteps is a list of valid values, see below.

Valid 'componentSteps' are:

- GlobalValue
- Product
- Product.Brand
- Product.Accommodation
- Product.Accommodation.SellingAccom
- Product.Accommodation.SellingAccom.Board
- Product.Accommodation.SellingAccom.Unit
- Product.Accommodation.SellingAccom.Unit.SellingUnit
- Product.OnewayFlight
- Product.OnewayFlight.BookingClass
- Product.ReturnFlight
- Product.ReturnFlight.BookingClass
- Product.ReturnFlight.Inbound
- Product.ReturnFlight.Outbound
- Product.Addon
- Product.Addon.Service
- Product.Addon.ServiceFeature

In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 
'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.

You could turn that grammar into an XML document by converting syntax elements to attributes and elements. You could do that, but Stella’s predecessor did not do that. That of course, would have been work, and they may have had to put some thought on how to relate their homebrew grammar to XSD rules, so instead they created an XML schema rule for SourceAttributeType that verifies that the data in the field is valid according to the grammar… using regular expressions. 1,310 characters of regular expressions.

<xs:simpleType>
    <xs:restriction base="xs:string">
            <xs:pattern value="(((Scope.)?(global|local|current)+?)?((((ComponentType.)?
(real|virtual))|ComponentType.all)+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)+?)?
((((DayAllocation.)?(first|last))|DayAllocation.all)+?)?:)?(#[A-Za-z0-9-_]+(.(#[A-Za-z0-9-_]+))*|(#[A-Za-z0-
9-_]+.)*
(ThisComponent|GlobalValue|Product|Product.Brand|Product.Accommodation|Product.Accommodation.SellingAccom|Prod
uct.Accommodation.SellingAccom.Board|Product.Accommodation.SellingAccom.Unit|Product.Accommodation.Selling
Accom.Unit.SellingUnit|Product.OnewayFlight|Product.OnewayFlight.BookingClass|Product.ReturnFlight|Product.
ReturnFlight.BookingClass|Product.ReturnFlight.Inbound|Product.ReturnFlight.Outbound|Product.Addon|Product.
Addon.Service|Product.Addon.ServiceFeature|Brand|Accommodation|Accommodation.SellingAccom|Accommodation.Selli
ngAccom.Board|Accommodation.SellingAccom.Unit|Accommodation.SellingAccom.Unit.SellingUnit|OnewayFlight|Onewa
yFlight.BookingClass|ReturnFlight|ReturnFlight.BookingClass|ReturnFlight.Inbound|ReturnFlight.Outbound|Addon|A
ddon.Service|Addon.ServiceFeature|SellingAccom|SellingAccom.Board|SellingAccom.Unit|SellingAccom.Unit.Sellin
gUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit.SellingUnit|SellingUnit))"/>
    </xs:restriction>
</xs:simpleType>
</xs:union>

There’s a bug in that regex that Stella needed to fix. As she put it: “Every time you evaluate it a few little kitties die because you shouldn’t use kitties to polish your car. I’m so, so sorry, little kitties…”

The full, unexcerpted code is below, so… at least it has documentation. In two languages!

<xs:simpleType name="SourceAttributeType">
                <xs:annotation>
                        <xs:documentation xml:lang="de">
                Die Source Angabe folgt folgender Syntax

                        source = ( Eigenschaft1+Eigenschaft2+... ":" ) ? steps

                        Eigenschaft1 = "local" | "global"

                        Eigenschaft2 ="real" | "virtual" | "ComponentType.all"

                        Eigenschaft3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

                        Eigenschaft4 = "first" | "last" | "DayAllocation.all"

                        Falls Eigenschaften angegeben werden muss zwingend die oben angegebene Reihenfolge der Eigenschaften eingehalten werden.

                        steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

                        oneOrMoreNameSteps = nameStep ( "." nameStep ) *

                        zeroOrMoreNameSteps = ( nameStep "." ) *

                        nameStep = "#" name

                        name ist eine Folge von Zeichen aus der Menge "A"-"Z", "a"-"z", "0"-"9", "-" und "_". Keine Umlaute. Mindestens ein Zeichen

                        componentSteps ist eine Liste gültiger Werte, siehe im folgenden

                Gültige 'componentSteps' sind zunächst:

                        - GlobalValue
                        - Product
                        - Product.Brand
                        - Product.Accommodation
                        - Product.Accommodation.SellingAccom
                        - Product.Accommodation.SellingAccom.Board
                        - Product.Accommodation.SellingAccom.Unit
                        - Product.Accommodation.SellingAccom.Unit.SellingUnit
                        - Product.OnewayFlight
                        - Product.OnewayFlight.BookingClass
                        - Product.ReturnFlight
                        - Product.ReturnFlight.BookingClass
                        - Product.ReturnFlight.Inbound
                        - Product.ReturnFlight.Outbound
                        - Product.Addon
                        - Product.Addon.Service
                        - Product.Addon.ServiceFeature

                Desweiteren sind alle Unterschrittfolgen aus obigen Pfaden erlaubt, also 'Board', 'Accommodation.SellingAccom' oder 'SellingAccom.Unit.SellingUnit'.
                'Accommodation.Unit' hingegen ist nicht erlaubt, da in diesem Fall einige Zwischenschritte fehlen.

                                </xs:documentation>
                        <xs:documentation xml:lang="en">
                                The Source specification obeys the following syntax

                                source = ( Feature1+Feature2+... ":" ) ? steps

                                Feature1 = "local" | "global"

                                Feature2 ="real" | "virtual" | "ComponentType.all"

                                Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

                                Feature4 = "first" | "last" | "DayAllocation.all"

                                If features are specified, the order of features as given above has strictly to be followed.

                                steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

                                oneOrMoreNameSteps = nameStep ( "." nameStep ) *

                                zeroOrMoreNameSteps = ( nameStep "." ) *

                                nameStep = "#" name

                                name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.

                                componentSteps is a list of valid values, see below.

                                Valid 'componentSteps' are:

                                - GlobalValue
                                - Product
                                - Product.Brand
                                - Product.Accommodation
                                - Product.Accommodation.SellingAccom
                                - Product.Accommodation.SellingAccom.Board
                                - Product.Accommodation.SellingAccom.Unit
                                - Product.Accommodation.SellingAccom.Unit.SellingUnit
                                - Product.OnewayFlight
                                - Product.OnewayFlight.BookingClass
                                - Product.ReturnFlight
                                - Product.ReturnFlight.BookingClass
                                - Product.ReturnFlight.Inbound
                                - Product.ReturnFlight.Outbound
                                - Product.Addon
                                - Product.Addon.Service
                                - Product.Addon.ServiceFeature

                                In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
                                'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.

                        </xs:documentation>
                </xs:annotation>
                <xs:union>
                        <xs:simpleType>
                                <xs:restriction base="xs:string">
                                        <xs:pattern value="(((Scope.)?(global|local|current)+?)?((((ComponentType.)?(real|virtual))|ComponentType.all)+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)+?)?((((DayAllocation.)?(first|last))|DayAllocation.all)+?)?:)?(#[A-Za-z0-9-_]+(.(#[A-Za-z0-9-_]+))*|(#[A-Za-z0-9-_]+.)*(ThisComponent|GlobalValue|Product|Product.Brand|Product.Accommodation|Product.Accommodation.SellingAccom|Product.Accommodation.SellingAccom.Board|Product.Accommodation.SellingAccom.Unit|Product.Accommodation.SellingAccom.Unit.SellingUnit|Product.OnewayFlight|Product.OnewayFlight.BookingClass|Product.ReturnFlight|Product.ReturnFlight.BookingClass|Product.ReturnFlight.Inbound|Product.ReturnFlight.Outbound|Product.Addon|Product.Addon.Service|Product.Addon.ServiceFeature|Brand|Accommodation|Accommodation.SellingAccom|Accommodation.SellingAccom.Board|Accommodation.SellingAccom.Unit|Accommodation.SellingAccom.Unit.SellingUnit|OnewayFlight|OnewayFlight.BookingClass|ReturnFlight|ReturnFlight.BookingClass|ReturnFlight.Inbound|ReturnFlight.Outbound|Addon|Addon.Service|Addon.ServiceFeature|SellingAccom|SellingAccom.Board|SellingAccom.Unit|SellingAccom.Unit.SellingUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit.SellingUnit|SellingUnit))"/>
                                </xs:restriction>
                        </xs:simpleType>
                </xs:union>
</xs:simpleType>
[Advertisement] Release! is a light card game about software and the people who make it. Play with 2-5 people, or up to 10 with two copies - only $9.95 shipped!

Read all

Poor Shoe

OldShoe201707

"So there's this developer who is the end-all, be-all try-hard of the year. We call him Shoe. He's the kind of over-engineering idiot that should never be allowed near code. And, to boot, he's super controlling."

Sometimes, you'll be talking to a friend, or reading a submission, and they'll launch into a story of some crappy thing that happened to them. You expect to sympathize. You expect to agree, to tell them how much the other guy sucks. But as the tale unfolds, something starts to feel amiss.

They start telling you about the guy's stand-up desk, how it makes him such a loser, such a nerd. And you laugh nervously, recalling the article you read just the other day about the health benefits of stand-up desks. But sure, they're pretty nerdy. Why not?

"But then, get this. So we gave Shoe the task to minify a bunch of JavaScript files, right?"

You start to feel relieved. Surely this is more fertile ground. There's a ton of bad ways to minify and concatenate files on the server-side, to save bandwidth on the way out. Is this a premature optimization story? A story of an idiot writing code that just doesn't work? An over-engineered monstrosity?

"So he fires up gulp.js and gets to work."

Probably over-engineered. Gulp.js lets you write arbitrary JavaScript to do your processing. It has the advantage of being the same language as the code being minified, so you don't have to switch contexts when reading it, but the disadvantage of being JavaScript and thus impossible to read.

"He asks how to concat JavaScript, and the room tells him the right answer: find javascripts/ -name '*.js' -exec cat {} ; > main.js"

Wait, what? You blink. Surely that's not how Gulp.js is meant to work. Just piping out to shell commands? But you've never used it. Maybe that's the right answer; you don't know. So you nod along, making a sympathetic noise.

"Of course, this moron can't just take the advice. Shoe has to understand how it works. So he starts googling on the Internet, and when he doesn't find a better answer, he starts writing a shell script he can commit to the repo for his 'jay es minifications.'"

That nagging feeling is growing stronger. But maybe the punchline is good. There's gotta be a payoff here, right?

"This guy, right? Get this: he discovers that most people install gulp via npm.js. So he starts shrieking, 'This is a dependency of mah script!' and adds node.js and npm installation to the shell script!"

Stronger and stronger the feeling grows, refusing to be shut out. You swallow nervously, looking for an excuse to flee the conversation.

"We told him, just put it in the damn readme and move on! Don't install anything on anyone else's machines! But he doesn't like this solution, either, so he finally just echoes out in the shell script, requires npm. Can you believe it? What a n00b!"

That's it? That's the punchline? That's why your friend has worked himself into a lather, foaming and frothing at the mouth? Try as you might to justify it, the facts are inescapable: your friend is TRWTF.

[Advertisement] Manage IT infrastructure as code across all environments with Puppet. Puppet Enterprise now offers more control and insight, with role-based access control, activity logging and all-new Puppet Apps. Start your free trial today!

Read all

CodeSOD: Mutex.js

Just last week, I was teaching a group of back-end developers how to use Angular to develop front ends. One question that came up, which did suprise me a bit, was how to deal with race conditions and concurrency in JavaScript.

I’m glad they asked, because it’s a good question that never occurred to me. The JavaScript runtime, of course, is single-threaded. You might use Web Workers to get multiple threads, but they use an Actor model, so there’s no shared state, and thus no need for any sort of locking.

Chris R’s team did have a need for locking. Specifically, their .NET backend needed to run a long-ish bulk operation against their SqlServer. It would be triggered by an HTTP request from the client-side, AJAX-style, but only one user should be able to run it at a time.

Someone, for some reason, decided that they would implement this lock in front-end JavaScript, since that’s where the AJAX calls were coming from..

var myMutex = true; //global (as in page wide, global) variable
function onClickHandler(element) {
    if (myMutex == true) {
        myMutex = false;
        // snip...
        if ($(element).hasClass("addButton") == true) {
            $(element).removeClass("addButton").addClass("removeButton");
            // snip...
            $.get(url).done(function (r) {
                // snip... this code is almost identical to the branch below
                setTimeout("myMutex = true;", 100);
            });
        } else {
            if ($(element).hasClass("removeButton") == true) {
                $(element).removeClass("removeButton").addClass("addButton");
                // snip...
                $.get(url).done(function (r) {
                    // snip... this code is almost identical to the branch above
                    setTimeout("myMutex = true;", 100);
                });
            }
        }
    }
}

You may be shocked to learn that this solution didn’t work, and the developer responsible never actually tested it with multiple users. Obviously, a client side variable isn’t going to work as a back-end lock. Honestly, I’m not certain that’s the worst thing about this code.

First, they reinvented the mutex badly. They seem to be using CSS classes to hold application state. They have (in the snipped code) duplicate branches of code that vary only by a handful of flags. They aren’t handling errors on the request- which, when this code started failing, made it that much harder to figure out why.

But it’s the setTimeout("myMutex = true;", 100); that really gets me. Why? Why the 100ms lag? What purpose does that serve?

Chris threw this code away and put a mutex in the backend service.

[Advertisement] High availability, Load-balanced or Basic – design your own Universal Package Manager, allow the enterprise to scale as you grow. Download and see for yourself!

Read all

Error'd: Have it Your Way!

"You can have any graphics you want, as long as it's Intel HD Graphics 515," Mark R. writes.

 

"You know, I'm pretty sure that I've been living there for a while now," writes Derreck.

 

Sven P. wrote, "Usually, I blame production outages on developers who, I swear, have trouble counting to five. After seeing this, I may want to blame the compiler too."

 

"Whenever I hear someone complaining about their device battery life, I show them this picture," wrote Renan.

 

"Prepaying for gas, my credit card was declined," Rand H. writes, "I was worried some thief must've maxed it out, but then I saw how much I was paying in taxes."

 

Brett A. wrote, "Yo Dawg I heard you like zips, so you should zip your zips to send your zips."

 

[Advertisement] BuildMaster integrates with an ever-growing list of tools to automate and facilitate everything from continuous integration to database change scripts to production deployments. Interested? Learn more about BuildMaster!

Read all

CodeSOD: string isValidArticle(string article)

Anonymous sends us this little blob of code, which is mildly embarassing on its own:

    static StringBuilder vsb = new StringBuilder();
    internal static string IsValidUrl(string value)
    {
        if (value == null)
        {
            return """";
        }

        vsb.Length= 0;
        vsb.Append("@"");

        for (int i=0; i<value.Length; i++)
        {
            if (value[i] == '"')
                vsb.Append("""");
            else
                vsb.Append(value[i]);
        }

        vsb.Append(""");
        return vsb.ToString();
    }

I’m willing to grant that re-using the same static StringBuilder object is a performance tuning thing, but everything else about this is… just plain puzzling.

The method is named IsValidUrl, but it returns a string. It doesn’t do any validation! All it appears to do is take any arbitrary string and return that string wrapped as if it were a valid C# string literal. At best, this method is horridly misnamed, but if its purpose is to truly generate valid C# strings, it has a potential bug: it doesn’t handle new-lines. Now, I’m sure that won’t be a problem that comes back up before the end of this article.

The code, taken on its own, is just bad. But when placed into context, it gets worse. This isn’t just code. It’s part of .NET’s System.Runtime.Remoting package. Still, I know, you’re saying to yourself, ‘In all the millions of lines in .NET, this is really the worst you’ve come up with?’

Well, it comes up because remember that bug with new-lines? Well, guess what. That exact flaw was a zero-day that allowed code execution… in RTF files.

Now, skim through some of the other code in wsdlparser.cs, and you'll see the real horror. This entire file has one key job: generating a class capable of parsing data according to an input WSDL file… by using string concatenation.

The real WTF is the fact that you can embed SOAP links in RTF files and Word will attempt to use them, thus running the WSDL parser against the input data. This is code that’s a little bad, used badly, creating an exploited zero-day.

[Advertisement] Manage IT infrastructure as code across all environments with Puppet. Puppet Enterprise now offers more control and insight, with role-based access control, activity logging and all-new Puppet Apps. Start your free trial today!

Read all

CodeSOD: You Absolutely Don't Need It

The progenitor of this story prefers to be called Mr. Syntax, perhaps because of the sins his boss committed in the name of attempting to program a spreadsheet-loader so generic that it could handle any potential spreadsheet with any data arranged in any conceivable format.

The boss had this idea that everything should be dynamic, even things that should be relatively straightforward to do, such as doing a web-originated bulk load of data from a spreadsheet into the database. Although only two such spreadsheet formats were in use, the boss wrote it to handle ANY spreadsheet. As you might imagine, this spawned mountains of uncommented and undocumented code to keep things generic. Sin was tasked with locating and fixing the cause of a NullPointerException that should simply never have occurred. There was no stack dump. There were no logs. It was up to Sin to seek out and destroy the problem.

Just to make it interesting, this process was slow, so the web service would spawn a job that would email the user with the status of the job. Of course, if there was an error, there would inevitably be no email.

It took an entire day to find and then debug through this simple sheet-loader and the mountain of unrelated embedded code, just to find that the function convertExcelSheet blindly assumed that every cell would exist in all spreadsheets, regardless of potential format differences.

[OP: in the interest of brevity, I've omitted all of the methods outside the direct call-chain...]


  public class OperationsController extends BaseController {
    private final JobService jobService;

    @Inject
    public OperationsController(final JobService jobService) {
      this.jobService = jobService;
    }

    @RequestMapping(value = ".../bulk", method = RequestMethod.POST)
    public @ResponseBody SaveResponse bulkUpload(@AuthenticationPrincipal final User               activeUser,
                                                 @RequestParam("file")    final MultipartFile      file, 
                                                                          final WebRequest         web, 
                                                                          final HttpServletRequest request){
      SaveResponse response = new SaveResponse();
      try {
          if (getSystemAdmin(activeUser)) {
             final Map<String,Object> customParams = new HashMap<>();
             customParams.put(ThingBulkUpload.KEY_FILE,file.getInputStream());
             customParams.put(ThingBulkUpload.KEY_SERVER_NAME,request.getServerName());
             response = jobService.runJob((CustomUserDetails)activeUser,ThingBulkUpload.JOB_NAME, customParams);
          } else {
             response.setWorked(false);
             response.addError("ACCESS_ERROR","Only Administrators can run bulk upload");
          }
      } catch (final Exception e) {
        logger.error("Unable to process file",e);
      }
      return response;
    }
  }

  @Service("jobService")
  @Transactional
  public class JobServiceImpl implements JobService {
    private static final Logger logger = LoggerFactory.getLogger(OperationsService.class);
    private final JobDAO jobDao;

    @Inject
    public JobServiceImpl(final JobDAO dao){
      this.jobDao = dao;
    }

    public SaveResponse runJob(final @NotNull CustomUserDetails user, 
                               final @NotNull String            jobName, 
                               final Map<String,Object>         customParams) {
      SaveResponse response = new SaveResponse();
      try {
          Job job = (Job) jobDao.findFirstByProperty("Job","name",jobName);
          if (job == null || job.getJobId() == null || job.getJobId() <= 0) {
             response.addError("Unable to find Job for name '"+jobName+"'");
             response.setWorked(false);
          } else {
            JobInstance ji = new JobInstance();
            ji.setCreatedBy(user.getUserId());
            ji.setCreatedDate(Util.getCurrentTimestamp());
            ji.setUpdatedBy(user.getUserId());
            ji.setUpdatedDate(Util.getCurrentTimestamp());
            ji.setJobStatus((JobStatus) jobDao.findFirstByProperty("JobStatus", "jobStatusId", JobStatus.KEY_INITIALZING) );
            ji.setStartTime(Util.getCurrentTimestamp());
            ji.setJob(job);
            Boolean created = jobDao.saveHibernateEntity(ji);
            if (created) {
               String className = job.getJobType().getJavaClass();
               Class<?> c = Class.forName(className);
               Constructor<?> cons = c.getConstructor(JobDAO.class,CustomUserDetails.class,JobInstance.class,Map.class);
               BaseJobImpl baseJob = (BaseJobImpl) cons.newInstance(jobDao,user,ji,customParams);
               baseJob.start();
               ji.setUpdatedDate(Util.getCurrentTimestamp());
               ji.setJobStatus((JobStatus) jobDao.findFirstByProperty("JobStatus", "jobStatusId", JobStatus.KEY_IN_PROCESS) );
               jobDao.updateHibernateEntity(ji);
                                 
               StringBuffer successMessage = new StringBuffer();
               successMessage.append("Job '").append(jobName).append("' has been started. ");
               successMessage.append("An email will be sent to '").append(user.getUsername()).append("' when the job is complete. ");
               String url = baseJob.generateCheckBackURL();
               successMessage.append("You can also check the detailed status here: <a href="").append(url).append("">").append(url).append("</a>");
               response.addInfo(successMessage.toString());
               response.setWorked(true);
            } else {
               response.addError("Unable to create JobInstance for Job name '"+jobName+"'");
               response.setWorked(false);
            }
          }
      } catch (Exception e) {
        String message = "Unable to runJob. Please contact support";
        logger.error(message,e);
        response.addError(message);
        response.setWorked(false);
      }
      return response;
    }
  }

  public class ThingBulkUpload extends BaseJobImpl {
    public static final String JOB_NAME = "Thing Bulk Upload";
    public static final String KEY_FILE = "file";

    public ThingBulkUpload(final JobDAO             jobDAO, 
                           final CustomUserDetails  user, 
                           final JobInstance        jobInstance, 
                           final Map<String,Object> customParams) {
                super(jobDAO,user,jobInstance,customParams);
        }

        @Override
        public void run() {
                SaveResponse response = new SaveResponse();
                response.setWorked(false);
                try {
                        final InputStream inputStream = (InputStream) getCustomParam(KEY_FILE);
                        if(inputStream == null) {
                                response.addError("Unable to run ThingBulkUpload; file is NULL");
                        } else {
                                final AnotherThingImporter cri = new AnotherThingImporter(customParams);
                                cri.changeFileStream(inputStream);
                                response = cri.importThingData(user);
                        }
                } catch (final Exception e) {
                        final String message = "Unable to finish ThingBulkUpload";
                        logger.error(message,e);
                        response.addError(message + ": " + e.getMessage());
                } finally {
                        finalizeJob(response);
                }
        }
}

public class AnotherThingImporter {

        // Op: Instantiated this way, even though the impls are annotated with Spring's @Repository.
        private final LocationDAO locationDAO = new LocationDAOImpl();
        private final ContactDAO contactDAO = new ContactDAOImpl();
        private final EntityDAO entityDAO = new EntityDAOImpl();
        private final BaseHibernateDAO baseDAO = new BaseHibernateDAOImpl();
        // Op: snip a few dozen more DAOs

        private       InputStream         workbookStream = null;
        private final Map<String, Object> customParams;

        public ClientRosterImporter(final Map<String, Object> customParams) {
                this.customParams = customParams;
        }

        public void changeFileStream(final InputStream fileStream) {
                workbookStream = fileStream;
        }

        public SaveResponse importThingData(final CustomUserDetails adminUser) {
                final SaveResponse response = new SaveResponse();
                if (workbookStream == null) {
                        throw new ThreeWonException("MISSING_FILE", "ClientRosterImporter was improperly created. No file found.");
                }
                try {
                        final XSSFWorkbook workbook = new XSSFWorkbook(workbookStream);

                        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {

                                final XSSFSheet sheet = workbook.getSheetAt(i);
                                final String sheetName = sheet.getSheetName();

                                // Op: snip 16 unrelated else ifs...
                                
                                } else if (sheetName.equalsIgnoreCase("History")) {
                                        populateHistory(adminUser, response, sheet);
                                }
                                
                                // Op: snip 3 more unrelated else ifs...
                        }
                } catch (final IOException e) {
                        throw new ThreeWonException("BAD_EXCEL_FILE", "Unable to open excel workbook.");
                }
                if (response.getErrors() == null || response.getErrors().size() <= 0) {
                        response.setWorked(true);
                }
                return response;
        }

        // Op: snip 19 completely unrelated methods
        
        private void populateEducationHistory(final CustomUserDetails adminUser, final SaveResponse response,
                                              final XSSFSheet sheet) {
                final ThingDataConverter converter = new ThingDataConverterImpl(entityDAO, locationDAO,
                                contactDAO);
                converter.convertExcelSheet(adminUser, response, sheet, customParams);
        }
}


public class ThingChildAssocConverter extends ThingDataConverter {
        public void convertExcelSheet(final CustomUserDetails adminUser, final SaveResponse response, final XSSFSheet sheet,
                final Map<String, Object> customParams) {
                initialize(customParams);
                final int rowCount = sheet.getPhysicalNumberOfRows();
                Integer numCreated = 0;

                for (int rowIndex = DEFAULT_HEADER_ROW_COUNT; rowIndex < rowCount; rowIndex++) {

                    final XSSFRow currentRow = sheet.getRow(rowIndex);

                    ...
                        
                    // Op: Null pointer thrown from row.getCell(...)
                    //final String name = df.formatCellValue(currentRow.getCell(COL_INST_NUM)); 
                    final String name = getValue(currentRow, COL_INST_NUM);
						
                    ...
                        
                    // Op: creation of the record here
                }
        }
		
        protected String getValue(final XSSFRow row, final Integer column) {
		// Op: We can not assume that any given cell will exist on all spreadsheets
                try {
                        return df.formatCellValue(row.getCell(column)).trim();
                } catch (final Exception e) {
                        // avoid NullPointers by returning "" instead of null
                        return "";
                }
        }
}
 

As opposed to two simple methods that just retrieved the cells, in order, from each specific spreadsheet format.

[Advertisement] Atalasoft’s imaging SDKs come with APIs & pre-built controls for web viewing, browser scanning, annotating, & OCR/barcode capture. Try it for 30 days with included support.

Read all