tag:blogger.com,1999:blog-121680382024-03-05T21:55:02.820+05:30Vinod SinghVinod Singhhttp://www.blogger.com/profile/05770985867453681608noreply@blogger.comBlogger50125tag:blogger.com,1999:blog-12168038.post-33177917663816808852020-08-01T09:57:00.001+05:302020-08-01T09:57:41.596+05:30SOLR: Non English (Latin) Characters in Field NameThe <a href="https://lucene.apache.org/solr/guide/8_5/defining-fields.html" target="_blank">SOLR documentation</a> mentions following requirement for defining name of a field.<div><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><dt style="background-color: white; box-sizing: border-box; color: rgba(0, 0, 0, 0.8); font-family: "Noto Sans", sans-serif; font-size: 1.0625rem; font-weight: bold; margin: 0px 0px 0.3125em; padding: 0px;"><code style="background-color: #f7f7f8; border-radius: 4px; box-sizing: border-box; color: rgba(0, 0, 0, 0.9); font-family: Inconsolata, monospace; font-size: 1em; letter-spacing: 0px; line-height: 1.45; margin-bottom: 0.3125em; padding: 0.1em 0.5ex; word-spacing: -0.15em;"><i>name</i></code></dt></div><div><dd style="background-color: white; box-sizing: border-box; color: rgba(0, 0, 0, 0.8); font-family: "Noto Sans", sans-serif; font-size: 16px; margin: 10px 0px 1.25em 1.125em; padding: 0px;"><i>The name of the field. Field names should consist of <b>alphanumeric</b> or <b>underscore</b> characters only and not start with a digit. . . .</i></dd></div></blockquote><div><div>While working on a <a href="https://अमरकोश.भारत/" target="_blank">dictionary website</a>, the JSON documents that I created had field names in Hindi. After indexing the data I was surprised to see that field names in the data were converted to multiple underscore letters e.g. field name शब्द was converted to ____. According to SOLR documentation शब्द should have been allowed as field name.</div></div><div><br /></div><div>Looks like SOLR developers have assumed that only 26 letters in Latin script are alphabets. Mentioning this assumption explicitly in documentation would have been helpful.</div><div><br /></div><div>After a closer scrutiny of <b>solrconfig.xml</b> file, I found following configuration, which converts anything that is not Latin alphanumeric in field name to underscore while indexing the data.</div><div><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><div><font face="courier"><updateProcessor class="solr.FieldNameMutatingUpdateProcessorFactory" name="field-name-mutating"></font></div></div><div><div><font face="courier"> <str name="pattern">[^\w-\.]</str></font></div></div><div><div><font face="courier"> <str name="replacement">_</str></font></div></div><div><div><font face="courier"></updateProcessor></font></div></div></blockquote><div><br /></div><div>Changing the pattern regex for FieldNameMutatingUpdateProcessorFactory to something like below will allow SOLR to accept non Latin alphabets in field name.</div><div><br /></div><blockquote style="border: none; margin: 0 0 0 40px; padding: 0px;"><div style="text-align: left;"><font face="courier"> <str name="pattern">[\s]</str></font></div></blockquote><div><br /></div><div>[\s] is a too generic pattern to use in real life scenario. This pattern should be further restricted to a limited set of characters that one intend to use in field name.</div>Vinod Singhhttp://www.blogger.com/profile/05770985867453681608noreply@blogger.com3tag:blogger.com,1999:blog-12168038.post-19934038097584903822020-05-25T07:04:00.001+05:302020-05-25T07:04:41.881+05:30How to customize Moodle Mobile App?<div>Chinese Virus (COVID-19) has created unimaginable chaos that no one could have thought about. As a side effect most of us have forced spare time to share with family at home. I utilized a part of this time to explore something new and that is <a href="https://moodle.org/" rel="nofollow" target="_blank">Moodle</a>.</div><div><br /></div><div>Moodle is a widely popular Learning Management System and it has a Mobile app to deliver the content to complement the web deliver channel. Installation of Moodle and setting up few courses was a fairly simple activity. Sample courses were rendering well on standard <a href="https://play.google.com/store/apps/details?id=com.moodle.moodlemobile" rel="nofollow" target="_blank">Moodle App</a> for Android.</div><div><br /></div><div>Then I decided customize the mobile app to give it a unique branding. I was expecting it to be a simple task but it came out to be a tough activity. My lack of previous experience in mobile app development was a big contributor to that. Moodle for Mobile forum was not of much help. After putting lots of effort I was able to get it done. I have documented the steps required to build and customize the Moodle App.</div><div><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Environment Setup</span></h2></div><div>Moodle has a <a href="https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2" rel="nofollow" target="_blank">good document</a> to setup the local development environment. Though it is bit outdated at places but following that I was able to setup the development environment on my Mac.</div><div><br /></div><div>Now clone the source code from <a href="https://github.com/moodlehq/moodleapp">https://github.com/moodlehq/moodleapp</a></div><div><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Configuration</span></h2></div><div>Next step is to change the configuration to give it a custom branding. Here is the list of files and the relevant configuration properties that needs to be changed.</div><div><br /></div><div><span id="docs-internal-guid-086efdf9-7fff-4360-7766-0292b8647c12"><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Change following properties in </span><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">config.xml</span><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">.</span></p></li><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Widget id</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Widget version</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">App name</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Description</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Author</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 14.6667px; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><font face="arial">AppendUserAgent</font></span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 14.6667px; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><font face="arial">URL_SCHEME (change it only if plan to block official Moodle App from accessing your website)</font></span></p></li><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: square; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Update server side property “tool_mobile | forcedurlscheme” at the above-mentioned URL.</span></p></li></ul></ul><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Make following changes in </span><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">src/config.json</span></p></li><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">app_id</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">appname</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">desktopname</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">versioncode</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">versionname</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Empty </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">demo_sites</span><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> element</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 14.6667px; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><font face="arial">customurlscheme (related to URL_SCHEME)</font></span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">siteurl</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">sitename</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: circle; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">privacypolicy</span></p></li></ul><li><font face="arial"><span style="font-size: 14.6667px; white-space: pre-wrap;">Update custom url scheme in <b>package.json</b> (related to URL_SCHEME)</span></font></li></ul></span></div><div><span id="docs-internal-guid-abf3384f-7fff-6d86-d1b2-276641fb204c"><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Logo & Splash Screen</span></h2>Having own logo and splash screen is what people want in custom moodle app. Below is the list of images for logo and splash screen that you have to put in source code.</span></div><div><br /><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/icon.png</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/splash.png</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/icon/icon-background.png</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/icon/icon-foreground.png</span></p></li></ul><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Following images should be removed, so that above-mentioned images are used during build process.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/splash.png.md5</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/android/icon.png</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/android/icon.png.md5</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/android/icon-foreground.svg</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/ios/icon.png</span></p></li><li dir="ltr" style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">resources/ios/icon.png.md5</span></p></li></ul><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-family: arial; font-size: 14.6667px;">Now run </span>ionic cordova resources</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> command to generate icons and splash images for various sized screens. Successful execution of this command will print logs as shown below.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">IMUL-ML0245:moodleapp vinodsingh$ ionic cordova resources</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">> </span><span style="color: #38b9c7; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">cordova-res</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[cordova-res]</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Generated 24 resources for android</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[cordova-res]</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Generated 50 resources for ios</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[cordova-res]</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Wrote to config.xml</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> ──────────────────────────────────────</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Update available: </span><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">5.2.3</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> → </span><span style="color: #39c026; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">5.4.16</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Run </span><span style="color: #38b9c7; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">npm i -g ionic</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> to update</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> ──────────────────────────────────────</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-style: italic; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Note:- In my case it did not created 4 android icons whose name were ending in *-smallicon.png. I removed those images and deleted corresponding entries from config.xml.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Very informative documentation about creating icon and splash images is available here -</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><a href="https://ionicframework.com/docs/cli/commands/cordova-resources" style="text-decoration-line: none;"><span style="color: #1155cc; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; text-decoration-line: underline; text-decoration-skip-ink: none; vertical-align: baseline; white-space: pre-wrap;">https://ionicframework.com/docs/cli/commands/cordova-resources</span></a></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="color: #1155cc; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; text-decoration-line: underline; text-decoration-skip-ink: none; vertical-align: baseline; white-space: pre-wrap;"><a href="https://ionicframework.com/blog/automating-icons-and-splash-screens/" style="text-decoration-line: none;">https://ionicframework.com/blog/automating-icons-and-splash-screens/</a></span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Firebase Project</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">For every Android app a Firebase project is madatory. Go to </span><a href="https://console.firebase.google.com/" style="text-decoration-line: none;"><span style="color: #1155cc; font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; text-decoration-line: underline; text-decoration-skip-ink: none; vertical-align: baseline; white-space: pre-wrap;">Firebase Console</span></a><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> and create a new project and add the app to that. Download the </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">google-services.json</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> file and place it at the root folder.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Build - Debug</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Now all required changes are done and we are ready to build a debug version of the app. Run </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">ionic cordova build android</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> to build the app with debug messages. It <b>may fail</b> with the following error.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Using cordova-fetch for cordova-android@8.0.0</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Adding android project...</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Creating Cordova project for the Android platform:</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Path: platforms/android</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Package: bharat.sarathi.mobile</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Name: _____.____</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Activity: MainActivity</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Android target: android-28</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Subproject Path: CordovaLib</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Subproject Path: app</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Android project created with cordova-android@8.0.0</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="color: red; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Unable to graft xml at selector "/manifest/uses-feature[@android:name='android.hardware.location.gps']" from "/Users/vinodsingh/code/GitHub/moodleapp/platforms/android/app/src/main/AndroidManifest.xml"</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> during config install</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[</span><span style="background-color: black; color: #850002; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">ERROR</span><span style="color: #747474; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">]</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="color: #ca3323; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">An error occurred while running subprocess </span><span style="color: #38b9c7; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">cordova</span><span style="color: #ca3323; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="color: #38b9c7; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">cordova platform add android --save</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> exited with exit code 1.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> Re-running this command with the </span><span style="color: #38b9c7; font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">--verbose</span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> flag may provide more information.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Running same command with </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">--verbose</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> provides precise error message as shown below.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[12:31:35] Local gulp not found in ~/code/GitHub/moodleapp</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-left: 36pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">[12:31:35] Try running: npm install gulp</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">After installing local gulp reran the </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">ionic cordova build android --verbose</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> command and it succeeded. The APK file will be placed here - </span><span style="font-family: "courier new"; font-size: 14.6667px; white-space: pre-wrap;">platforms/android/app/build/outputs/apk/debug/app-debug.apk</span><span style="font-family: arial; font-size: 11pt; white-space: pre-wrap;">. Debug app can be tested on an android emulator.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Running the build command again may fail with following error -</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Users/vinodsingh/code/GitHub/moodleapp/platforms/android/app/src/main/AndroidManifest.xml:33:5-66 Error:</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Element uses-feature#android.hardware.location.gps at AndroidManifest.xml:33:5-66 duplicated with element declared at AndroidManifest.xml:32:5-91</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">/Users/vinodsingh/code/GitHub/moodleapp/platforms/android/app/src/main/AndroidManifest.xml Error:</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span class="Apple-tab-span" style="white-space: pre;"> </span></span><span style="font-family: arial; font-size: 8.5pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Validation failed, exiting</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">To fix this error remove the below line from AndroidManifest.xml file and run the build command again.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <uses-feature android:name="android.hardware.location.gps" android:required="false" /></span></p><h2 style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt; text-align: left;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Intent Filters</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Testing revealed an issue with the app. After authentication in browser control does not go back to the app. User is stuck in the browser itself. This issue makes app unusable. After lots of struggle, I figured out that</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> the following intent-filters are missing from main activity in </span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 700; vertical-align: baseline; white-space: pre-wrap;">AndroidManifest.xml</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <intent-filter></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <action android:name="android.intent.action.VIEW" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <category android:name="android.intent.category.DEFAULT" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <category android:name="android.intent.category.BROWSABLE" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <data android:scheme="URL_SCHEME" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </intent-filter></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <intent-filter></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <action android:name="android.intent.action.VIEW" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <category android:name="android.intent.category.DEFAULT" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <category android:name="android.intent.category.BROWSABLE" /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> <data android:host=" " android:pathPrefix="/" android:scheme=" " /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"> </intent-filter></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Ideally they should have been there due to the inclusion of </span><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">customurl-plugin</span><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">. Adding these intent-filters manually fixed the issue.</span></p><h2 dir="ltr" style="line-height: 1.38; margin-bottom: 6pt; margin-top: 18pt;"><span style="font-family: arial; font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Build - Release</span></h2><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">After successful testing the app on emulator, I am ready for production build and test on a real device. Reading <a href="https://ionicframework.com/docs/v1/guide/publishing.html" rel="nofollow" target="_blank">ionic publishing document</a> is recommended before proceeding with next steps.</span></p><h3 style="line-height: 1.38; margin-bottom: 4pt; margin-top: 16pt; text-align: left;"><span style="color: #434343; font-family: arial; font-size: 14pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Create key [one time]</span></h3><div><span style="font-family: arial; font-size: 14.6667px; white-space: pre-wrap;">We have to create a key for signing the app. The Java keytool command as shown below can generate the required key.</span></div><div><span style="font-family: arial; font-size: 14.6667px; white-space: pre-wrap;"><br /></span></div><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">keytool -genkey -v -keystore my-release-key.keystore -alias alias-name -keyalg RSA -keysize 2048 -validity 10000</span></p><h3 dir="ltr" style="line-height: 1.38; margin-bottom: 4pt; margin-top: 16pt;"><span style="color: #434343; font-family: arial; font-size: 14pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Build</span></h3><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Moodle suggests few edits to the source code to make it production ready. Suggested code changes are <a href="https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Compiling_using_AOT" rel="nofollow" target="_blank">documented here</a>. After making code changes run the build command to create APK without debug messages.</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">ionic cordova build android --prod --release</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-family: arial; font-size: 14.6667px;">It will create a apk file at location - </span><span style="font-size: 14.6667px;">platforms/android/app/build/outputs/apk/release/</span></span><font face="courier new"><span style="font-size: 14.6667px; white-space: pre-wrap;">app-release-unsigned.apk</span></font><span style="font-family: arial; font-size: 14.6667px; white-space: pre-wrap;"> location.</span></p><h3 dir="ltr" style="line-height: 1.38; margin-bottom: 4pt; margin-top: 16pt;"><span style="color: #434343; font-family: arial; font-size: 14pt; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Sign the APK</span></h3><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-family: arial; font-size: 14.6667px;">Now this release apk file needs to be signed with key that we generated a while back.</span></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-family: arial; font-size: 14.6667px;"><br /></span></span></p><pre style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore app-release-unsigned.apk alias-name</span></pre><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Next is to zip align the APK using </span><span style="font-family: "courier new"; font-size: 14.6667px; white-space: pre-wrap;">zipalign</span><span style="font-family: arial; font-size: 11pt; white-space: pre-wrap;"> tool available in Android SDK.</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: "courier new"; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">~/Library/Android/sdk/build-tools/29.0.3/zipalign -v 4 app-release-unsigned.apk my-production-ready.apk</span></p><br /><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">Now APK is similar to what one will download from the Play Store. It is ready for testing on an emulator or real Android device.</span></p></div>Vinod Singhhttp://www.blogger.com/profile/05770985867453681608noreply@blogger.com8tag:blogger.com,1999:blog-12168038.post-87152183675527763442011-09-07T19:15:00.000+05:302011-09-07T19:15:08.915+05:30Why NoSQL is in fashion?<div dir="ltr" style="text-align: left;" trbidi="on">
Couple of years back NoSQL was relatively unheard term. It has become a new buzzword in town, catching people's imagination. Almost all big daddies of Internet (Google, Facebook, Yahoo, Twitter, Amazon, Linked-in etc.) are using NoSQL at massive scale and many of them have their own NoSQL products. Every month there is a launch of new NoSQL product catering to some specific use cases / requirements. So what are the reasons behind NoSQL's rise to popularity?<br />
<br />
<b>Background</b><br />
<br />
RDBMS and SQL has ruled the world for last 3 decades. They were (are) the defacto standard for storing business data. In fact there was no alternative to RDBMS and SQL combination. When RDBMS were developed the disk space was scarce, hence a lot of thought has gone around saving precious disk space. Development in hardware technologies have made available literally unlimited storage space at lower prices. Evolution of Internet in last decade has paved way for altogether different kind of applications and use cases, which were never thought earlier. Web2 applications (say social networking) have entirely different requirements than typical enterprise applications. Other than that Internet has changed the way application developers think about the data and its usage & storage.<br />
<br />
<b>Requirement</b><br />
<br />
Nowadays enormous amount of data is being produced everyday and the rate at which data is produced is also increasing. Data size is so huge that organizations spread it across data center as it does not fit in a single rack or data center. Though data availability is also an equally important factor in using multiple data centers for storage. Applications have to available 24x7 no matter what. Data has to survive the data center failure as well, forget about failure of a disk. Instead of archiving data, Facebook type of applications keep it online forever.<br />
<br />
Social networking sites are coming up with new features quite frequently and sometimes rolling back them. At times their new features were never thought about earlier. They don't want to be confined by the schema constraints of RDBMS which is quite difficult (though not impossible) to change. Support for evolving schema has become a necessity and NoSQL solutions are a right fit there instead of RDBMS.<br />
<br />
Data size, flexible schema and availability are the 3 key factors where traditional RDBMS do not perform well after a certain limit.<br />
<div>
<br /></div>
<div>
<div>
<b>Thought Process</b></div>
<div>
<br />
Web2 applications has changed how people think about application architecture. It is an altogether different thought process then what it was few years back.<br />
<br /></div>
<div>
While designing an enterprise application we used to think about "<u>how to store the data</u>" but today the primary thought is "<u>how we will use the data</u>". This is a paradigm shift. Data storage is designed based on how that is going to be used, leading to optimized performance for a given requirement at times at the cost of flexibility.</div>
<div>
<br /></div>
<div>
"<u>Referencing</u>" entities was a wonderful feature in RDBMS but for large scale web 2 application that does not matter at all. For them instead of referencing "<u>embedding</u>" is the killer feature. Storing related data (say personal detail and address) together makes sense as they don't have any meaning independently. This is similar to denormalization in RDBMS.</div>
<div>
<br /></div>
<div>
For many features of web 2 applications <u>ACID transactions</u> are also not a primary requirement. Say if a tweet does not gets published many users won't mind to type that again and hit the enter button as long as that does not happen frequently. If avoiding ACID transactions increases the throughput many fold for not so critical features, what else you want :-). It is similar to old MySQL though with much larger data-set than what MySQL can support.<br />
<br />
<b>Conclusion</b></div>
</div>
<div>
<br />
There are several use cases where NoSQL perform much better and it makes perfect sense to use NoSQL instead of trying to scale the RDBMS by using expensive methods. RDBMS’s are really good at doing what they do, which is storing flat, relational, tabular data in a consistent manner and getting wonderful reports out of that data. They still remain the best solution for storing relational data.While NoSQL databases are good at performance, availability, schema-less persistent etc., which has become a basic requirement for today's applications. Support for these features is the main reason for meteoric rise to fame of the NoSQL.</div>
</div>
Vinod Singhhttp://www.blogger.com/profile/05770985867453681608noreply@blogger.com58tag:blogger.com,1999:blog-12168038.post-2412569670618393882011-04-21T21:44:00.000+05:302011-12-24T11:45:30.120+05:30Blessed with a baby girl<div dir="ltr" style="text-align: left;" trbidi="on">
It has been an year since I blogged last time. last one year was very hectic for me both at workplace and home. There was tons of workload at office at times eating into weekends. We were expecting a baby, so I have to take additional responsibilities at home as well. In December we were blessed with a gorgeous baby girl. That was the most fulfilling moment of our life. It has become an everyday celebration for our son. For quite long time he was dreaming of having a brother or sister.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitFkvzdHw7hf2sDHVUVm0Ws46dhXKdQ6at93wUWRihafiea_NzZTM-b9VPjt3rGmZWP5Cy7M-LN2Nh4avUpMbHGOtMuPxcu0hNkfrJvOXnTfhUnME11Tgb9VRbZNN9yeN26ucozg/s1600/DSC01512+%2528Large%2529.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitFkvzdHw7hf2sDHVUVm0Ws46dhXKdQ6at93wUWRihafiea_NzZTM-b9VPjt3rGmZWP5Cy7M-LN2Nh4avUpMbHGOtMuPxcu0hNkfrJvOXnTfhUnME11Tgb9VRbZNN9yeN26ucozg/s640/DSC01512+%2528Large%2529.JPG" width="640" /></a></div>
<br />
<br />
Next month I will be joining a new workplace, which is closer to my home. That will spare some precious time to spare with family.<br />
<br />
Hopefully I will be back to blogging soon.</div>Anonymousnoreply@blogger.com8tag:blogger.com,1999:blog-12168038.post-53228546206239432212010-04-12T15:48:00.000+05:302010-04-12T15:48:40.620+05:30JAX-WS Web service with MavenOne of my earlier post <a href="/2008/09/building-jax-ws-web-service.html">Building JAX-WS Webservice</a> was using Ant as build tool for developing web services. With each passing day Maven is gaining more popularity and became preferred build tool for many developers. I used to get emails from several readers about working JAX-WS examples with Maven as build tool. In this post I will try to demonstrate to publish and consume a JAX-WS web service using Maven.<br />
<br />
The source code used in this post is available <a href='http://www.vinodsingh.com/WebserviceMavenCode.zip'>here</a>, which will be referred at several places in this post.<br />
<br />
The <a href="https://jax-ws-commons.dev.java.net/jaxws-maven-plugin/" rel="nofollow">jaxws-maven-plugin</a> has two goals <b>wsgen</b> and <b>wsimport</b>.<br />
<br />
<ul style="list-style-type: none"><li><b>wsgen</b> - This reads a service endpoint implementation class and generates all of the portable artifacts for a JAX-WS web service. With newer versions (tested with 2.2) of JAX-WS, execution of this task is not required for publishing webservice</li>
<li><b>wsimport</b> - This tool reads WSDL and generates client side artifacts. We will be using this goal for generating client and consuming a web service.</li></ul><br />
<p><b>Publish Web service</b><br />
<br />
For publishing a web service we need to write an interface and its implementation then annotate them with relevant annotations. The JAX-WS libraries should be added as dependency in pom.xml. A sample (partial) build script is shown below-<pre class='xml' name='code'><dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<scope>compile</scope>
<version>2.2</version>
</dependency>
</dependencies>
. . .
<build>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<connectors>
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
<port>8080</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build></pre><br />
We can see in the above Maven script that the <b>wsgen</b> is not required. The JAX-WS implementation will take care of that at runtime. To make testing easier I have used Jetty plugin, which will publish the web service for further consumption by the client in next step. To publish the web service invoke following command in 'webservice' directory of the source code of this post-<br />
<blockquote><font face="Courier New">mvn -e clean compile jetty:run</font></blockquote></p><p><b>Create Client</b><br />
<br />
For creating web service client the <b>wsimport</b> goal of <font face="Courier New">jaxws-maven-plugin</font> will be used. The <b>wsimport</b> goal creates portable client artifacts by parsing a WSDL. In this example the WSDL URL is the one which was published by starting the web service at previous step. The pom.xml of client side application will look like below-<pre class='xml' name='code'><dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<scope>compile</scope>
<version>2.2</version>
</dependency>
</dependencies>
. . .
<build>
<plugins>
<!-- Generate client using WSDL -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<configuration>
<packageName>wsclient</packageName>
<wsdlUrls>
<wsdlUrl>http://localhost:8080/webservice/myService?wsdl</wsdlUrl>
</wsdlUrls>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>client.WsClient</mainClass>
</configuration>
</plugin>
</plubins>
</build></pre></p>For the sake of simplicity I have used <font face="Courier New">exec-maven-plugin</font> here, which will run the test class to invoke the web service from build script itself. To generate client and run the test class, trigger the build from 'webservice-client' directorty using following command-<br />
<blockquote><font face="Courier New">mvn -e clean compile exec:java</font></blockquote></p><br />
The examples shown above were tested with following configuration-<br />
<ul><li>Java 1.6.18</li>
<li>JAX-WS 2.</li>
<li>Maven 2.2.1</li>
<li>Windows XP</li></ul><br />
<b>Note:-</b> If JAX-WS 2.2 is used with older versions of Java 6 (e.g. Java 1.6.03) then you may encounter errors like shown below-<br />
<blockquote><font face="Courier New">SEVERE: WSSERVLET11: failed to parse runtime descriptor: java.lang.LinkageError: JAXB 2.1 API is being loaded from the bootstrap classloader, but this RI (from jar:file:/C:/Documents%20and%20Settings/Vinod%20Singh/.m2/repository/com/sun/xml/bind/jaxb-impl/2.2/jaxb-impl-2.2.jar!/com/sun/xml/bind/v2/model/impl/ModelBuilder.class) needs 2.2 API. Use the endorsed directory mechanism to place jaxb-api.jar in the bootstrap classloader. (See http://java.sun.com/j2se/1.6.0/docs/guide/standards/)<br />
java.lang.LinkageError: JAXB 2.1 API is being loaded from the bootstrap classloader, but this RI (from jar:file:/C:/Documents%20and%20Settings/Vinod%20Singh/.m2/repository/com/sun/xml/bind/jaxb-impl/2.2/jaxb-impl-2.2.jar!/com/sun/xml/bind/v2/model/impl/ModelBuilder.class) needs 2.2 API. Use the endorsed directory mechanism to place jaxb-api.jar in the bootstrap classloader. (See http://java.sun.com/j2se/1.6.0/docs/guide/standards/) at com.sun.xml.bind.v2.model.impl.ModelBuilder.(ModelBuilder.java:173)</font></blockquote><br />
The reason of these errors is that the earlier versions of Java 6 are using lower versions of JAX-WS while we are trying to use higher version of JAX-WS. To overcome these errors either we need to downgrade the JAX-WS version in our runtime environment or use endorsed directory mechanism.<br />
<br />
Hope this will be useful to some of the readers.Anonymousnoreply@blogger.com17tag:blogger.com,1999:blog-12168038.post-34149538656828294442010-02-25T17:00:00.000+05:302010-02-25T17:00:40.298+05:30m2eclipse plugin makes M2_REPO variable non modifiable in EclipseYesterday for first time I installed m2eclipse plugin and all of a sudden projects failed to compile. I was using Maven for quite long time hence adding all library dependencies for a project using a variable named <b>M2_REPO</b>, which was pointing towards a location where Maven downloaded jars were stored. After installation of m2eclipse plugin Eclipse was not able to resolve the absolute path of the jars in classpath.<br />
<br />
<img border="0" height="194" kt="true" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;" src="http://1.bp.blogspot.com/_Kn0LXTsgm7w/S4ZPcEwgzGI/AAAAAAAAALo/pyq9ebHCMHU/s320/M2_Repo.PNG" width="320" /><br />
<br />
The value of the M2_REPO variable was changed itself to some path other than what I configured. When I tried to fix that, to my surprise I can't edit it any longer. It was marked with this text <b>"M2_REPO (non modifiable)"</b>. After poking here and there for a while I come to know that m2eclipse picks the value of M2_REPO variable from <font face="Courier New">$MAVEN_HOME/conf/settings.xml</font> (MAVEN_HOME is the currently selected Maven installation) file using <font face="Courier New"><localRepository></font> element-<br />
<blockquote><font face="Courier New"><localRepository>PATH_TO_REPOSITORY</localRepository></font></blockquote>If nothing is specifed in settings.xml then <font face="Courier New">${user.home}/.m2</font> directory becomes the value of M2_REPO variable.Anonymousnoreply@blogger.com20tag:blogger.com,1999:blog-12168038.post-91985592997259763522010-02-17T21:59:00.000+05:302010-02-17T21:59:19.723+05:30Remove old images of Linux (Ubuntu) kernelDevelopment of Linux is quite fast paced and new versions of kernel are released pretty frequently. I have a dual boot desktop with Ubuntu and Windows XP. With each update grub boot menu keeps on growing and I have to make Windows XP default OS as desktop is used by my 8 year son as well. Each version of kernel takes up > 100MB space, though that is not a big worry with huge storage available at much cheaper price.<br />
<br />
The unused (older) versions of kernel can be removed safely. Usually I prefer to keep the last version along with "last - 1", to be on safer side so that I have something to fallback upon if something goes wrong with the latest kernel. To remove a version of kernel go to Synaptic Package Manager and search for <b>linux-image</b> and select the ones you want to remove.<br />
<br />
<img border="0" src="http://2.bp.blogspot.com/_Kn0LXTsgm7w/S3rLxwRJCgI/AAAAAAAAALA/vAyRBCrk8KY/s400/Synaptic%20Package%20Manager%20.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;" /><br />
<br />
You'll get an option to remove completely (<b>Mark for Complete Removal</b>), that will get rid of everything including configuration files used by that package. Before removing kernel make sure to not remove the current version as that will make the system unusable. Use <font face="Courier New"><b>uname -a</b></font> command to know the currently used version of kernel. Output of this command looks like below on my machine-<br />
<br />
<blockquote><font face="Courier New">vinod@vinod-desktop:~$ uname -a<br />
vinod@vinod-desktop:~$ Linux vinod-desktop 2.6.31-19-generic #56-Ubuntu SMP Thu Jan 28 02:39:34 UTC 2010 x86_64 GNU/Linux</font></blockquote><br />
You can also find and remove linux-headers using this simple method.Anonymousnoreply@blogger.com1tag:blogger.com,1999:blog-12168038.post-50719130316315476282010-01-11T13:07:00.001+05:302010-01-11T13:13:15.804+05:30Share a datasource among several applications in Tomcat using AtomikosIn a previous <a href='/2009/12/jta-transactions-with-atomkios-in.html'>post</a> I discussed about how to use <a href='http://www.atomikos.com' rel='nofollow' target='_blank'>Atomikos</a> JTA implementation in <a href='http://tomcat.apache.org/' rel='nofollow' target='blank'>Tomcat</a>. We had a requirement of sharing a globally defined datasource among several applications deployed in Tomcat container. The datasource lookup from the one application (whichever does the lookup first) succeeds but it always fails from the second application with an error message like-<br />
<br />
<blockquote><font face='Courier New'>Another resource already exists with name DATA_SOURCE_NAME - pick a different name</font></blockquote><br />
Why? We were using the sample bean factory available at Atomikos' <a href='http://www.atomikos.com/Documentation/Tomcat6Integration33#Bean_Factory_java' rel='nofollow' target='_blank'>site</a>, which tries to recreate the datasource on each lookup. Within an application one can cache the datasource after initial JNDI lookup call. But what about doing a lookup of same resource from multiple applications?<br />
<br />
After poking around for a while in Atomikos source code I found that after creation, datasource instance is cached by the Atomikos. With a minor modification in bean factory as shown below, the cached instance can be retrieved instead of recreating it.<br />
<pre class='java' name='code'>// see if this DataSource is already initialized then return cached object
// if available, else Atomikos will throw an exception
try {
bean = IntraVmObjectRegistry.getResource(((AbstractDataSourceBean) bean).getUniqueResourceName());
if (log.isInfoEnabled())
log.info("Returning cached value of AbstractDataSourceBean (Atomikos): " + bean);
return bean;
} catch (NameNotFoundException nfe) {
// OK, it is not available go ahead and create one
}</pre>With this change a globally defined datasource can be used by multiple applications inside a Tomcat container to use Atomikos JTA implementation.Anonymousnoreply@blogger.com6tag:blogger.com,1999:blog-12168038.post-64092090120044294092009-12-23T15:21:00.000+05:302009-12-23T15:21:42.725+05:30Start Apache as service in Linux (Fedora)The Apache HTTP server is installed by default in all Linux distributions and it is configured to run as service. I usually prefer to install Apache from source instead of using the default one. After installing Apache I want to run that instance as service so that it can be restarted automatically whenever machine is rebooted. This can be achieved easily by modifying <span style="font-family: "Courier New",Courier,monospace; font-weight: bold;">/etc/rc.d/init.d/httpd</span> file, which is configured to run the Apache installed with OS.<br />
<br />
Comment the following section, though it is not required but I prefer to keep all configuration at one place<br />
<blockquote><span style="font-family: Courier New;"><br />
if [ -f /etc/sysconfig/httpd ]; then<br />
. /etc/sysconfig/httpd<br />
fi</span></blockquote>Modify the following lines- <br />
<blockquote><span style="font-family: Courier New;">apachectl=/usr/sbin/apachectl<br />
httpd=${HTTPD-/usr/sbin/httpd}<br />
prog=httpd<br />
pidfile=${PIDFILE-/var/run/httpd/httpd.pid}</span></blockquote>to point towards your Apache installation<br />
<blockquote><span style="font-family: Courier New;">apachectl=/opt/apps/httpd-2.2.14/bin/apachectl<br />
httpd=${HTTPD-/opt/apps/httpd-2.2.14/bin/httpd}<br />
prog=httpd<br />
pidfile=${PIDFILE-/opt/apps/httpd-2.2.14/logs/httpd.pid}</span></blockquote>Now your custom Apache instance will be started by OS instead of the default one.Anonymousnoreply@blogger.com2tag:blogger.com,1999:blog-12168038.post-55106594875243957162009-12-10T12:21:00.000+05:302009-12-10T12:21:08.172+05:30Send XML data over web serviceOccasionally I am being asked a common question, "How to send XML data over web service?". In Java (in fact in any programming language) XML is just a string (sequence of characters). So sending XML as request parameter or receiving it in response is just like handling any other string data.<br />
<br />
For the following XML data from web service client and server-<br />
<pre class="xml" name="code">XML from client
<name>vinod</name>
XML from server
<greeting>Hello! vinod</greeting></pre><br />
This is the web service (JAX-WS) request and response-<br />
<pre class="xml" name="code">REQUEST
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:xmlData xmlns:ns2="http://vinodsingh.com">
<data>&lt;name&gt;vinod&lt;/name&gt;</data>
</ns2:xmlData>
</S:Body>
</S:Envelope>
RESPONSE
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:xmlDataResponse xmlns:ns2="http://vinodsingh.com">
<return>&lt;greeting>Hello! vinod&lt;/greeting></return>
</ns2:xmlDataResponse>
</S:Body>
</S:Envelope></pre><br />
The above example clearly shows that JAX-WS runtime handles XML strings pretty well. The source code available <a href="http://www.vinodsingh.com/WsCodeDownload.html">here</a> includes an example of sending bigger XML data.Anonymousnoreply@blogger.com3tag:blogger.com,1999:blog-12168038.post-84889785069524109422009-12-01T14:40:00.001+05:302009-12-01T14:56:04.134+05:30JTA Transactions with Atomkios in TomcatTomcat is the leading Java Servlet container. Many of our applications do not need full blown Java EE Application Server and a Servlet container like Tomcat is more than enough to run them even in production. Some of our applications need JTA transactions capabilities, which are not provided by Tomcat out of the box. There are very few open source JTA implementations. <a rel='nofollow' href='http://jotm.objectweb.org/' target='_blank'>JOTM</a> used to be most popular among them, unfortunately its development is dead for last several years though it seems to getting revived lately at its <a rel='nofollow' href='http://jotm.ow2.org/' target='_blank'>new home</a>. <a rel='nofollow' href='http://atomikos.com/' target='_blank'>Atomikos</a> is another JTA implementation, which was open sourced (<a rel='nofollow' href='http://atomikos.com/Main/TransactionsEssentials' target='_blank'>Transactions Essentials</a>) when JOTM went into hibernation.<br />
<br />
Integrating Atomikos with Tomcat is fairly easy, it can be done by following simple steps given below-<br />
<br />
Create a configuration file for Atomikos at <font face='Courier New'>$TOMCAT_HOME/lib/transactions.properties</font> location with contents like below-<br />
<br />
<blockquote><font face='Courier New'>com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory</font><br />
<font face='Courier New'>com.atomikos.icatch.automatic_resource_registration=true</font><br />
<font face='Courier New'>com.atomikos.icatch.output_dir=../work</font><br />
<font face='Courier New'>com.atomikos.icatch.log_base_dir=../work</font><br />
<font face='Courier New'>com.atomikos.icatch.enable_logging=true</font><br />
<font face='Courier New'>com.atomikos.icatch.console_log_level=INFO</font></blockquote><br />
Define a DataSource in <font face='Courier New'>$TOMCAT_HOME/conf/context.xml</font> as shown below-<br />
<br />
<pre class="xml" name="code"><Transaction factory="com.atomikos.icatch.jta.UserTransactionFactory" />
<Resource name="jdbc/pgDS"
auth="Container"
type="com.atomikos.jdbc.AtomikosDataSourceBean"
factory="com.vinodsingh.atomikos.tomcat.DataSourceBeanFactory"
uniqueResourceName="jdbc/pgDS"
xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
xaProperties.serverName="localhost"
xaProperties.portNumber="5432"
xaProperties.databaseName="test"
xaProperties.user="test"
xaProperties.password="test"
maxPoolSize="3"
minPoolSize="1" /></pre><br />
Copy following files to <font face='Courier New'>$TOMCAT_HOME/lib</font> directory-<br />
<ul><li>JDBC driver of the Database being used.</li>
<li>JTA API (jta-1.1.jar).</li>
<li>Atomikos Essentials (transactions-essentials-all-3.5.9.jar), name may change according to the version being used.</li>
<li>Tomcat integration jars available at Atomikos' site or atomikos-tomcat.jar built from the source code attached with this post.<br />
</li>
</ul>All these changes will make <font face='Courier New'>UserTransaction</font> object available in Tomcat's JNDI tree and it can be used in same way as we do in any regular Java EE application server. An example of using <font face='Courier New'>UserTransaction</font> is shown in following code snippet-<br />
<pre class="java" name="code">public String insertRecord(String value) {
UserTransaction utx = null;
try {
utx = ServiceLocator.getTrasactionManager();
utx.begin();
// Do transactional work
pgDAO.insertRecord(value);
// commit the transaction
utx.commit();
} catch (Exception e) {
try {
if (utx != null)
utx.rollback();
String msg = "Oops! Transaction is rolled back due to an exception";
log.error(msg, e);
return msg;
} catch (Exception e1) {
log.error("Failed to rollback the transaction", e1);
return "Failed to rollback the transaction" + e1.getMessage();
}
}
return "Transaction succeded";
}</pre>To see transactions in action compile the attached source code, deploy the generated <font face='Courier New'>jta.war</font> on Tomcat and open <a href='http://localhost:8080/jta/'>http://localhost:8080/jta/</a> URL in your browser. Thats all it takes to integrate Atomikos JTA transactions in Tomcat.<br />
<br />
Note:-<br />
<ol><li>Though Atomikos' documentation about <a rel='nofollow' href='http://atomikos.com/Documentation/Tomcat6Integration33' target='_blank'>Tomcat 6 integration</a> talks about configuring <font face='Courier New'>AtomikosLifecycleListener</font> in <font face='Courier New'>$TOMCAT_HOME/conf/server.xml</font> as well. But Atomikos seems to work well inside Tomcat without that configuration.</li>
<li>Here I have slightly modified the code for Tomcat integration from what is available at Atomikos website, more details about the same will follow in upcoming posts.</li>
</ol><br />
Source code for this post is available <a href="http://www.vinodsingh.com/AtomikosTomcat.zip">here</a>.Anonymousnoreply@blogger.com13tag:blogger.com,1999:blog-12168038.post-52108383345321911242009-10-04T11:35:00.003+05:302010-02-13T12:49:48.183+05:30Migrating from Bugzilla to RedmineIt has been a while since I started <a href="/2009/08/getting-started-with-redmine.html" target="_blank">evaluating</a> <a href="http://redmine.org/" rel="nofollow" target="_blank">Redmine</a> and later started using as well. Earlier we were using Bugzilla for issue tracking, which contains good amount of data (current and historical as well). This data can't be left behind just for the sake of using some new tool. I was looking at ways to migrate this data. <a href="http://www.robertheath.net/2009/02/redmine-bugzilla-migration-script/" rel="nofollow" target="_blank">Robert Heath's</a> PHP script for migration was the best solution I found.<br />
<br />
His scripts deletes all Redmine data and migrates everything from Bugzilla to Redmine, which I did not want and also we have a highly customized instance of Bugzilla, so using that script out of box was not possible. I wanted to pick and choose the data for migration instead of dumping everything in Redmine. The Bugzilla was having internal authentication for users while we wanted to have a mix of LDAP and internal authentication in Redmine.<br />
<br />
To meet all these (and many more) requirements, big changes were required to Robert's script. Being a long time Java programmer, I hardly have any knowledge of PHP. Making changes to Robert's PHP script was difficult to me, so I rewrote the script again in Java. I used Robert's script as a reference to map Redmine and Bugzilla types and for SQL scripts. Without that it would have been quite difficult to write the script.<br />
<br />
Though it took some effort to write the migration script but the effort spent was worth it. Now with Redmine we have a consolidated view of issues and source code. Migration has been seamless and users are much happier with Redmine. Now I am looking at possibilities about writing few plugins for Redmine to customize it for some innovative usages.<br />
<br />
<i>Update Feb 13, 2010: As I was being asked for the script frequently, here is the code I used for migration. This may not work against each and every Bugzilla installation, modify it according to your needs.</i><br />
<br />
<span style="font-family: "Courier New",Courier,monospace;">/**</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> * </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> * 1. All internal IDs are associated as Developer (4) to projects, while external as</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> * Client (6).</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> * </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> */</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">public class Migrator {</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Log log = LogFactory.getLog(this.getClass());</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String filePath = "D:\\bugzillaAttachments"; // where to store bugzilla attachments</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> SimpleDateFormat sdf = new SimpleDateFormat("yyMMddhhmmss");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private String projectList = "1, 2, 3, 4, 5";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private static Map<string, integer=""> priority = new HashMap<string, integer="">();</string,></string,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private static Map<string, integer=""> status = new HashMap<string, integer="">();</string,></string,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private static Map<string, integer=""> tracker = new HashMap<string, integer="">();</string,></string,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // old values are key and new ones values</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<integer, integer=""> userMap = new HashMap<integer, integer="">();</integer,></integer,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<string, integer=""> existingUserMap = new HashMap<string, integer="">();</string,></string,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<integer, integer=""> projectMap = new HashMap<integer, integer="">();</integer,></integer,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<integer, integer=""> categoryMap = new HashMap<integer, integer="">();</integer,></integer,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<integer, integer=""> issueMap = new HashMap<integer, integer="">();</integer,></integer,></span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // key > product_id + version name</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private Map<string, integer=""> versionMap = new HashMap<string, integer="">();</string,></string,></span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> static {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> priority.put("P1", 5);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> priority.put("P2", 4);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> priority.put("P3", 3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> priority.put("P4", 2);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> priority.put("P5", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("UNCONFIRMED", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("NEW", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("ASSIGNED", 2);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("REOPENED", 7);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("RESOLVED", 3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("VERIFIED", 5);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> status.put("CLOSED", 5);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("trivial", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("minor", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("normal", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("major", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("critical", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> tracker.put("blocker", 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> public Migrator() {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Connection destCon = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Connection srcCon = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Class.forName("org.postgresql.Driver");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> destCon = DriverManager.getConnection("jdbc:postgresql://redmine_server:5432/redmine", "user",</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> "pwd");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> destCon.setAutoCommit(false);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("PostgreSQL/Redmine connection opened");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Class.forName("com.mysql.jdbc.Driver");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> srcCon = DriverManager.getConnection("jdbc:mysql://bugzilla_server:3306/test", "user", "pwd");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("MySql/Bugzilla connection opened");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> loadExistingUsers(destCon);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createUsers(destCon, srcCon); // create users</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createProjects(destCon, srcCon); // create projects</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> destCon.commit();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } catch (Exception e) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> destCon.rollback();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Rolled back changes");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } catch (Exception e3) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.error("Unable to rollback", e3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.error("", e);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> destCon.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("PostgreSQL/Redmine connection closed");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> srcCon.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("MySql/Bugzilla connection closed");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } catch (Exception e2) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.error("Error while closing connections", e2);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> public static void main(String[] args) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> new Migrator();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // load existing users and don't recreate them</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void loadExistingUsers(Connection destCon) throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT mail, id FROM users";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement stmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet rs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> stmt = destCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs = stmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (rs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> existingUserMap.put(rs.getString(1), rs.getInt(2));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(rs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(stmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createUsers(Connection destCon, Connection srcCon) throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT userid, login_name, realname, disabledtext FROM profiles "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "WHERE userid IN (SELECT ugm.user_id FROM user_group_map ugm, group_control_map gcm "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + " WHERE ugm.group_id = gcm.group_id AND gcm.product_id IN (" + projectList + "))";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO users (login, mail, firstname, lastname, language, status, auth_source_id, created_on, updated_on) "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "VALUES (?, ?, ?, ?, 'en', ?, ?, now(), now())";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Creating uses . . .");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet keysRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int userId = selectRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String loginName = selectRs.getString(2).trim();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // don't recreate an existing user</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (existingUserMap.containsKey(loginName)) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> userMap.put(userId, existingUserMap.get(loginName));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Bugzilla user [" + userId + ", " + loginName</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "] > already exists as Redmine user [" + existingUserMap.get(loginName)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "]");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> continue;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String realName = selectRs.getString(3).trim();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String disabledText = selectRs.getString(4);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Keep non internal user ids with full email address, this will enable</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // future employees with same id</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String newLoginName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (loginName.endsWith("@domain.com")) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> newLoginName = loginName.substring(0, loginName.indexOf("@"));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (newLoginName.length() > 20) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Our LDAP (AD) has a limit of 20 chars</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> newLoginName = loginName.substring(0, 20);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> newLoginName = loginName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String firstName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String lastName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (realName.contains(" ")) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> firstName = realName.substring(0, realName.indexOf(' '));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> lastName = realName.substring(realName.lastIndexOf(' ') + 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // in few cases 'realName' is email address</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (realName.contains("@"))</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> realName = realName.substring(0, loginName.indexOf("@"));</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> firstName = realName.length() <= 30 ? realName : realName.substring(0, 30);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> lastName = "";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // insert the user in Redmine</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(1, newLoginName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(2, loginName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(3, firstName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(4, lastName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Wherever 'disabledtext' contains something mark his status as 3 (lock)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (disabledText != null && disabledText.trim().length() > 0)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(5, 3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(5, 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // If user id is not @domain.com, mark their auth source as internal</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (loginName.endsWith("@domain.com"))</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(6, 1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setNull(6, java.sql.Types.INTEGER);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // update new key in bugzilla</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs = insertStmt.getGeneratedKeys();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs.next();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int newId = keysRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> userMap.put(userId, newId); // put in map for later retrieval</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(keysRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Bugzilla user [" + userId + ", " + loginName + " ] > Redmine user [" + newId + ", "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + newLoginName + "]");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(userMap.size() + " users created.");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(keysRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createProjects(Connection destCon, Connection srcCon) throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Creating project: ABC . . .");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createProject(destCon, srcCon, "INSERT INTO projects (name, description, identifier, created_on, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "updated_on) VALUES ('ABC', 'ABC', 'abc', now(), now())", 77);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // repaet it for more projects</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Following projects already exist there, just migrate their issues</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info("Migrating project: XYZ . . . "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "Redmine project id is 13 for old Bugzilla project id 91");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createProjectArtifacts(destCon, srcCon, 91, 13);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> projectMap.put(91, 13);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createProject(Connection destCon, Connection srcCon, String insertSql, int bugzillaProjId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet rs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate(insertSql, new String[] { "id" });</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs = insertStmt.getGeneratedKeys();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs.next();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int redmineProjId = rs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // put in map for later retrieval</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> projectMap.put(bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Redmine project id is " + redmineProjId + " for old Bugzilla project id "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + bugzillaProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createProjectArtifacts(destCon, srcCon, bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(rs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createProjectArtifacts(Connection destCon, Connection srcCon, int bugzillaProjId,</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int redmineProjId) throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int[] trackers = { 1, 2, 3 };</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // enable issue tracker module</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate("INSERT INTO enabled_modules (project_id, name) VALUES ("</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + redmineProjId + ", 'issue_tracking')");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Enabled tracker module");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // create trackers</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Creating trackers");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> for (int tracker : trackers)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate("INSERT INTO projects_trackers (project_id, tracker_id) VALUES ("</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + redmineProjId + ", " + tracker + ")");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Thread.sleep(1000);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Creating versions");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createVersions(destCon, srcCon, bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Thread.sleep(1000);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Assigining project members");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> assignProjectMembers(destCon, srcCon, bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Thread.sleep(1000);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Creating issue categories");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createCategories(destCon, srcCon, bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Thread.sleep(1000);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Migrating issues");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createIssues(destCon, srcCon, bugzillaProjId, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createVersions(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT value FROM versions WHERE product_id = " + bugzillaProjId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO versions (project_id, name, created_on, updated_on) VALUES (?, ?, now(), now())";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet keysRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String name = selectRs.getString(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Creating version: " + name);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(2, name);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs = insertStmt.getGeneratedKeys();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs.next();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int newId = keysRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> versionMap.put(bugzillaProjId + name, newId); // store for later retrieval</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void assignProjectMembers(Connection destCon, Connection srcCon, int bugzillaProjId,</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int redmineProjId) throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT DISTINCT p.userid, pd.id, p.login_name "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "FROM group_control_map gcm, user_group_map ugm, profiles p, products pd "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "WHERE gcm.group_id = ugm.group_id AND ugm.user_id = p.userid AND gcm.product_id = pd.id "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "AND pd.id = " + bugzillaProjId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO members (user_id, project_id, role_id, created_on) VALUES (?, ?, ?, now())";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int member = -1;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> member = selectRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String login_name = selectRs.getString(3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> .info(" Assigning member: " + member + "(" + userMap.get(member) + "), "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + login_name);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, userMap.get(member));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(2, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (login_name.endsWith("@domain.com"))</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(3, 4); // mark everyone as developer</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(3, 6); // client</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createCategories(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT id, product_id, name, initialowner FROM components WHERE product_id = "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + bugzillaProjId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO issue_categories (project_id, name, assigned_to_id) VALUES (?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet keysRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String name = selectRs.getString(3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> name = name.length() <= 30 ? name : name.substring(0, 30);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Creating category: " + name);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(2, name);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(3, userMap.get(selectRs.getInt(4)));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeUpdate();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs = insertStmt.getGeneratedKeys();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs.next();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int newCatId = keysRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(keysRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> categoryMap.put(selectRs.getInt(1), newCatId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(keysRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createIssues(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT bugs.bug_id, bugs.short_desc, longdescs.thetext, bugs.bug_file_loc AS url, bugs.assigned_to, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "bugs.reporter, bugs.creation_ts, longdescs.bug_when, bugs.priority, bugs.version, bugs.component_id, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "bugs.bug_status, bugs.resolution, bugs.bug_severity, longdescs.comment_id, longdescs.who, lastdiffed, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "bugs.cf_phases1, bugs.cf_work "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "FROM bugs, longdescs WHERE bugs.bug_id = longdescs.bug_id AND bugs.product_id = "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + bugzillaProjId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT_ISSUE = "INSERT INTO issues (project_id, subject, description, assigned_to_id, author_id, created_on, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "updated_on, start_date, priority_id, fixed_version_id, category_id, tracker_id, status_id) "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT_JOURNAL = "INSERT INTO journals (journalized_id, journalized_type, user_id, notes, created_on) "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + " VALUES (?, ?, ?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT_CUSTOM = "INSERT INTO custom_values (customized_type, customized_id, custom_field_id, value) "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "VALUES ('Issue', ?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertIssueStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertJournalStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertCustomStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet keysRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt = destCon.prepareStatement(INSERT_ISSUE, new String[] { "id" });</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt = destCon.prepareStatement(INSERT_JOURNAL);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt = destCon.prepareStatement(INSERT_CUSTOM);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int bugId = -1;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int redmineBugId = -1;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int currentBugId = selectRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // first record for the bug</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (currentBugId != bugId) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> bugId = currentBugId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" " + bugId + " >>>");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(1, redmineProjId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setString(2, selectRs.getString(2)); // subject</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // description along with URL</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setString(3, selectRs.getString(3) + "\n\n" + selectRs.getString(4));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(4, userMap.get(selectRs.getInt(5))); // assigned_to</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(5, userMap.get(selectRs.getInt(6)));// reporter</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setTimestamp(6, selectRs.getTimestamp(7)); // created_on</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setTimestamp(7, selectRs.getTimestamp(17)); // updated_on</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setDate(8, selectRs.getDate(7)); // start_date</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(9, priority.get(selectRs.getString(9)));// priority</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // fixed_version_id - do post processing to set 0 values to null/empty</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(10, versionMap.get(bugzillaProjId + selectRs.getString(10)));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(11, categoryMap.get(selectRs.getInt(11))); // component/category</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(12, tracker.get(selectRs.getString(14))); // tracker</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.setInt(13, status.get(selectRs.getString(12))); // status</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // (resolution)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertIssueStmt.executeUpdate();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs = insertIssueStmt.getGeneratedKeys();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keysRs.next();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> redmineBugId = keysRs.getInt(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(keysRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> issueMap.put(bugId, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Custom values Severity(5), Defect Injection(3) and Defect Cause(4)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(2, 5); // severity</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setString(3, selectRs.getString(14));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(2, 3); // injection</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setString(3, selectRs.getString(18));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setInt(2, 4); // cause</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.setString(3, selectRs.getString(19));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Map Bugzilla CC to Redmine Watchers</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> createWatchers(destCon, srcCon, bugId, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // log.info(" Watchers (CC list) migrated");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // process attachments (11931 has attachments)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> handleAttachments(destCon, srcCon, bugId, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // log.info(" Attachments migrated");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Create journal entries (comments on issues)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.setString(2, "Issue");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.setInt(3, userMap.get(selectRs.getInt(16)));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.setString(4, selectRs.getString(3));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.setTimestamp(5, selectRs.getTimestamp(8));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertJournalStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Created Journal entries");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertCustomStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Created severity, cuase and injection");</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Map Bugzilla Dependencies to Redmine Relations and Duplicates</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> for (Integer issue : issueMap.keySet()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> updateIssueRelations(destCon, srcCon, issue);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // log.info(" Updated related and duplicate issues");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertIssueStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertJournalStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertCustomStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void createWatchers(Connection destCon, Connection srcCon, int bugzillaBugId, int redmineBugId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO watchers (watchable_id, user_id, watchable_type) VALUES (?, ?, 'Issue')";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT bug_id, who FROM cc where bug_id = " + bugzillaBugId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String cc = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int count = 0;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int who = selectRs.getInt(2);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (count == 0)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> cc = " Adding as watcher: " + who + "(" + userMap.get(who) + ")";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> cc += ", " + who + "(" + userMap.get(who) + ")";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(2, userMap.get(who));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> count++;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (cc != null && cc.length() > 0)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(cc);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void handleAttachments(Connection destCon, Connection srcCon, int bugzillaBugId, int redmineBugId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT = "SELECT a.filename, a.creation_ts, ad.thedata, a.attach_id, a.bug_id, a.mimetype, a.submitter_id, a.description "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "FROM attachments a, attach_data ad WHERE a.attach_id = ad.id and a.bug_id = "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + bugzillaBugId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO attachments (container_id, container_type, filename, disk_filename, filesize, "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + "content_type, digest, downloads, author_id, created_on, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet selectRs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectRs = selectStmt.executeQuery(SELECT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (selectRs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String fileName = selectRs.getString(1);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Timestamp fileTs = selectRs.getTimestamp(2);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // disk file name in redmine is yymmddhhmmss_filename.ext</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String diskFileName = sdf.format(fileTs) + "_" + fileName;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.info(" Saving attachment: " + fileName + "(" + diskFileName + ") for issue "</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> + bugzillaBugId + "(" + redmineBugId + ")");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Blob blob = selectRs.getBlob(3);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> byte[] allBytesInBlob = blob.getBytes(1, (int) blob.length());</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> File file = new File(filePath, diskFileName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> file.createNewFile();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> FileOutputStream fos = new FileOutputStream(file);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> fos.write(allBytesInBlob);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> fos.flush();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> fos.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, redmineBugId);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(2, "Issue");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(3, fileName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(4, diskFileName);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(5, allBytesInBlob.length);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(6, selectRs.getString(6)); // mime type</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(7, ""); // digest- leave it blank</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(8, 0); // downloads</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(9, userMap.get(selectRs.getInt(7))); // author</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setTimestamp(10, fileTs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(11, selectRs.getString(8));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectRs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void updateIssueRelations(Connection destCon, Connection srcCon, int bugzillaBugId)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> throws Exception {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT_DUP = "SELECT dupe_of, dupe FROM duplicates WHERE dupe_of = " + bugzillaBugId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String SELECT_DEP = "SELECT blocked, dependson FROM dependencies WHERE blocked = " + bugzillaBugId;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> String INSERT = "INSERT INTO issue_relations (issue_from_id, issue_to_id, relation_type) VALUES (?, ?, ?)";</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Statement selectStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> PreparedStatement insertStmt = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ResultSet rs = null;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt = destCon.prepareStatement(INSERT);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> selectStmt = srcCon.createStatement();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs = selectStmt.executeQuery(SELECT_DUP);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (rs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, issueMap.get(rs.getInt(1)));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(2, issueMap.get(rs.getInt(2)));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(3, "duplicates");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(rs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs = selectStmt.executeQuery(SELECT_DEP);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> while (rs.next()) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(1, rs.getInt(1));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setInt(2, rs.getInt(2));</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.setString(3, "blocks");</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.addBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> insertStmt.executeBatch();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(rs);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(selectStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> close(insertStmt);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void close(ResultSet rs) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (rs == null)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> return;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> rs.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } catch (Exception e) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.error("Failed to close ResultSet", e);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> private void close(Statement stmt) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (stmt == null)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> return;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> try {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> stmt.close();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> } catch (Exception e) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> log.error("Failed to close PreparedStatement", e);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">}</span><i> </i>Anonymousnoreply@blogger.com6tag:blogger.com,1999:blog-12168038.post-42020428827759702372009-09-22T12:26:00.000+05:302009-09-22T12:26:59.953+05:30403 Forbidden with wgetWe have few shell scripts, which access some HTTP URLs using <font face='Courier New'>wget</font> as part of some task. <font face='Courier New'>wget</font> failed to access some of the URLs with error message like below-<br />
<blockquote><font face='Courier New'>Resolving repo1.maven.org... 38.97.124.18<br />
Connecting to repo1.maven.org|38.97.124.18|:80... connected.<br />
HTTP request sent, awaiting response... 403 Forbidden<br />
2009-09-22 11:38:53 ERROR 403: Forbidden.</font><br />
</blockquote>Though all failed URLs were accessible using browsers from same machine / user account. Reason of this failure is that <font face='Courier New'>wget</font> does not send any information about itself (agent information) i.e. name and version of the browser etc. and some servers do not entertain requests without agent information. A simple workaround for this problem is to add a dummy agent information as shown below-<br />
<blockquote><font face='Courier New'>wget <b><font color='#330099'>-U MyBrowser/1.0</font></b> URL_TO_DOWNLOAD</font><br />
</blockquote>Now everything will work as expected.Anonymousnoreply@blogger.com4tag:blogger.com,1999:blog-12168038.post-35868243910246381712009-09-06T14:05:00.000+05:302009-09-06T14:05:44.272+05:30Few more notes about Redmine installationThis is in continuation to earlier post <a href='/2009/08/getting-started-with-redmine.html' target='_blank'>Getting started with Redmine</a> where I jotted down my experience about installation of Redmine and other required packages. When I attempted to install Redmine in another machine with slightly different configuration and in different manner, I stumbled upon few more facts.<br />
<ul><li><b>zlib-devel</b> package should be installed and that too before installing ruby. Though ruby, rubygem and rake will be installed without any problem but installation of rails (<font face='Courier New'>gem install rails</font>) will fail due to lack of zlib-devel.</li>
<li>If <b>PostgreSQL</b> is installed from a RPM package, say <font face='Courier New'>yum install postgresql-server</font>. Then PostgreSQL installation will not have <font face='Courier New'>pg_config</font> file and installation of PostgreSQL adaptor for ruby (<font face='Courier New'>gem install pg</font>) will fail as installer script uses <font face='Courier New'>pg_config</font> during installation. In absence of this file PostgreSQL adaptor will fail with an error like below-<br />
<br />
<font face='Courier New'> [vinod@localhost opt]$ gem install pg<br />
Building native extensions. This could take a while...<br />
ERROR: Error installing pg:<br />
ERROR: Failed to build gem native extension.<br />
<br />
/opt/ruby-1.8.7/bin/ruby extconf.rb<br />
extconf.rb:1: command not found: pg_config --version<br />
ERROR: can't find pg_config.<br />
HINT: Make sure pg_config is in your PATH<br />
*** extconf.rb failed ***<br />
Could not create Makefile due to some reason, probably lack of<br />
necessary libraries and/or headers. Check the mkmf.log file for more<br />
details. You may need configuration options.<br />
<br />
Provided configuration options:<br />
--with-opt-dir<br />
--without-opt-dir<br />
--with-opt-include<br />
--without-opt-include=${opt-dir}/include<br />
--with-opt-lib<br />
--without-opt-lib=${opt-dir}/lib<br />
--with-make-prog<br />
--without-make-prog<br />
--srcdir=.<br />
--curdir<br />
--ruby=/opt/apps/ruby-1.8.7/bin/ruby<br />
<br />
Gem files will remain installed in /opt/ruby-1.8.7/lib/ruby/gems/1.8/gems/pg-0.8.0 for inspection.<br />
Results logged to /opt/ruby-1.8.7/lib/ruby/gems/1.8/gems/pg-0.8.0/ext/gem_make.out</font></li>
<li><b>OpenSSL</b> develipment headers are required by Phusion Passenger. Without this Passenger installation will fail with an error like-<br />
<font face='Courier New'> * OpenSSL support for Ruby... not found<br />
<br />
* To install OpenSSL support for Ruby:<br />
Please (re)install Ruby with OpenSSL support by downloading it from http://www.ruby-lang.org/.</font><br />
<br />
To overcome this first install openssl-devel package<br />
<font face='Courier New'> yum install openssl-devel</font><br />
<br />
Then execute following script from Ruby installation-<br />
<font face='Courier New'> /opt/ruby-1.8.7-p174/ext/openssl/ruby extconf.rb --with-openssl-dir=/usrl/local/ssl</font><br />
<br />
Now passenger installation will succeed.</li>
</ul>I think there were few more things I stumbled upon, but lost their track during all this exercise.Anonymousnoreply@blogger.com0tag:blogger.com,1999:blog-12168038.post-24354162996976023452009-08-24T16:29:00.001+05:302009-08-24T16:34:26.795+05:30Redmine with Subversion 1.6Recently I updated SVN client to 1.6.x from 1.5.x on the machine where Redmine was installed. After this change Redmine stopped fetching further revisions from SVN repositories for existing projects and for new projects it was showing following error message on 'Repository' tab-<br />
<blockquote><font face='Courier New'>The entry or revision was not found in the repository.</font></blockquote>In Redmine log file I could not find any worthwhile information. There was just one liner-<br />
<blockquote><font face='Courier New'>Completed in 0.34931 (2 reqs/sec) | Rendering: 0.01537 (4%) | DB: 0.01871 (5%) | 500 Internal Server Error [http://myhost/redmine/repositories/show/myproject]</font></blockquote>Then I looked inside Apache (Redmine was fronted by Apache + Passenger) error log file, it had error messages like given below-<br />
<br />
<font face='Courier New'> -----------------------------------------------------------------------<br />
ATTENTION! Your password for authentication realm:<br />
<br />
<http://serverIP:80=""> My SVN<br />
<br />
can only be stored to disk unencrypted! You are advised to configure<br />
your system so that Subversion can store passwords encrypted, if<br />
possible. See the documentation for details.<br />
<br />
You can avoid future appearances of this warning by setting the value<br />
of the 'store-plaintext-passwords' option to either 'yes' or 'no' in<br />
'/home/appsupport/.subversion/servers'.<br />
-----------------------------------------------------------------------<br />
Store password unencrypted (yes/no)? svn: Can't read stdin: End of file found</font><br />
<br />
So svn client was asking whether to save the password or not and there was no one to answer that question, leading to abortion of the operation. Uncommenting following two lines in <font face='Courier New'>~/.subversion/config</font> file-<br />
<blockquote><font face='Courier New'>store-passwords = no<br />
store-auth-creds = no</font></blockquote>resolved this issue.Anonymousnoreply@blogger.com2tag:blogger.com,1999:blog-12168038.post-65614237089674058622009-08-15T00:15:00.000+05:302009-08-15T00:15:42.338+05:30Hudson: svn authentication cancelledThe Hudson was building all projects without any issues since it was installed. All of a sudden yesterday my inbox was filled with failure reports, Hudson was not able to build any of the projects. Logs were having stacktrace like mentioned below-<br />
<br />
<font face='Courier New'>ERROR: svn: authentication cancelled<br />
org.tmatesoft.svn.core.SVNCancelException: svn: authentication cancelled<br />
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.cancel(SVNErrorManager.java:37)<br />
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.cancel(SVNErrorManager.java:32)<br />
at org.tmatesoft.svn.core.internal.wc.DefaultSVNAuthenticationManager.getNextAuthentication(DefaultSVNAuthenticationManager.java:200)<br />
at hudson.scm.FilterSVNAuthenticationManager.getNextAuthentication(FilterSVNAuthenticationManager.java:42)<br />
at org.tmatesoft.svn.core.internal.io.dav.http.HTTPConnection.request(HTTPConnection.java:537)<br />
at org.tmatesoft.svn.core.internal.io.dav.http.HTTPConnection.request(HTTPConnection.java:273)<br />
at org.tmatesoft.svn.core.internal.io.dav.http.HTTPConnection.request(HTTPConnection.java:261)<br />
at org.tmatesoft.svn.core.internal.io.dav.DAVConnection.exchangeCapabilities(DAVConnection.java:516)<br />
at org.tmatesoft.svn.core.internal.io.dav.DAVConnection.open(DAVConnection.java:98)<br />
at org.tmatesoft.svn.core.internal.io.dav.DAVRepository.openConnection(DAVRepository.java:1001)<br />
at org.tmatesoft.svn.core.internal.io.dav.DAVRepository.testConnection(DAVRepository.java:97)<br />
at hudson.scm.SubversionSCM$DescriptorImpl.checkRepositoryPath(SubversionSCM.java:1686)<br />
at hudson.scm.SubversionSCM.repositoryLocationsExist(SubversionSCM.java:1826)<br />
at hudson.scm.SubversionSCM.checkout(SubversionSCM.java:506)<br />
at hudson.scm.SubversionSCM.checkout(SubversionSCM.java:469)<br />
at hudson.model.AbstractProject.checkout(AbstractProject.java:898)<br />
at hudson.model.AbstractBuild$AbstractRunner.checkout(AbstractBuild.java:391)<br />
at hudson.model.AbstractBuild$AbstractRunner.run(AbstractBuild.java:340)<br />
at hudson.model.Run.run(Run.java:1090)<br />
at hudson.maven.MavenModuleSetBuild.run(MavenModuleSetBuild.java:301)<br />
at hudson.model.ResourceController.execute(ResourceController.java:93)<br />
at hudson.model.Executor.run(Executor.java:122)<br />
Skipping sonar analysis due to bad build status FAILURE<br />
Finished: FAILURE</font><br />
<br />
The stack trace talks about authentication cancellation not failure, which is quite strange. I still thought that it might be an authentication failure and tried to authenticate against SVN server using same credentials as used by Hudson. Authentication was successful using various methods (browser, command line client, TortoiseSVN). Google search did not provided any relevant information and I was running out of clues :-(<br />
<br />
After putting lots of pondering I remembered that day before I did a manual (from console) project checkout on the same machine and during checkout svn client (1.6.4) asked to save the password and I said 'no'. Will that make any difference? I think it should not.<br />
<br />
Just for the sake of trial I deleted the '<font face='Courier New'>~/.subversion</font>' directory and did a Hudson build after that. Now Hudson was able to do SVN checkout/update and build project. After several trials, I concluded that if password is not saved during manual checkout, Hudson will fail with above-mentioned error. Though it will wok fine if password was saved. Another observation was that Hudson SVN client (SVNKit 1.3.0) does not create '<font face='Courier New'>~/.subversion/auth</font>', while '<font face='Courier New'>svn</font>' command does, irrespective of one chooses to save the password or not.<br />
<br />
Though I do not know why Hudson (SVNKit) cancels the SVN authentication but removing the '<font face='Courier New'>~/.subversion</font>' directory solves the issue.Anonymousnoreply@blogger.com15tag:blogger.com,1999:blog-12168038.post-64360243925519325192009-08-07T11:36:00.000+05:302009-08-07T11:36:35.645+05:30Yum hangs on Fedora 11 behind proxyYesterday I installed Fedora 11 on a new machine from a live CD. As usual I tried to update the system just after installation. This machine was behind a proxy server and http_proxy variable was configured to let yum get through proxy server. Yum hanged just after emitting two line on the console-<br />
<br />
<font face="Courier New"> [root@localhost ~]# yum update<br />
Loading "refresh-packagekit" plugin<br />
Setting up Package Sacks</font><br />
<br />
Even after 1 hour it did not came out and I can't kill it using <font face="Courier New">ctrl+c</font>, killing the process from another terminal was the only option. Running <font face="Courier New">yum</font> in verbose mode also does not print any useful information-<br />
<br />
<font face="Courier New"> [root@localhost ~]# yum -v update<br />
Not loading "blacklist" plugin, as it is disabled<br />
Loading "refresh-packagekit" plugin<br />
Not loading "whiteout" plugin, as it is disabled<br />
Config time: 0.082<br />
Yum Version: 3.2.22<br />
Setting up Package Sacks</font><br />
<br />
The <font face="Courier New">wget http://www.yahoo.com/</font> was successfully downloading the web page that means internet connectivity and proxy was not an issue. Then I looked inside <font face="Courier New">/etc/yum.repos.d/fedora.repo</font>, to get some clues. All URLs mentioned in that file were using https, so I tried to do <font face="Courier New">wget</font> over https-<br />
<font face="Courier New"><br />
[root@localhost ~]# wget https://www.yahoo.com/<br />
--2009-08-07 16:35:11-- https://www.yahoo.com/<br />
Resolving www.yahoo.com... 69.147.76.15<br />
Connecting to www.yahoo.com|69.147.76.15|:443...</font><br />
<br />
and that too hanged after emitting above text. Now I see why <font face="Courier New">yum</font> is not able to update. Changing URLs from https to http in <font face="Courier New">/etc/yum.repos.d/fedora.repo</font> took yum slightly ahead from where it was hanging earlier-<br />
<br />
<font face="Courier New"> [root@localhost ~]# yum -v update<br />
</font><font face="Courier New"> Not loading "blacklist" plugin, as it is disabled<br />
Loading "refresh-packagekit" plugin<br />
Not loading "whiteout" plugin, as it is disabled<br />
Config time: 0.082<br />
Yum Version: 3.2.22<br />
Setting up Package Sacks</font><br />
<font face="Courier New"> fedora/metalink | 10 kB 00:00<br />
fedora | 3.8 kB 00:00<br />
fedora/primary_db | 8.4 MB 00:09</font><br />
<br />
Now <font face="Courier New">yum</font> hanged after emitting above-mentioned text. Changing URLS from https to http in <font face="Courier New">/etc/yum.repos.d/fedora-updates.repo</font> made update successful.<br />
<br />
This https issue seems to be specific to Fedora 11, as I never faced any difficulties on updating previous Fedora releases (6, 7, 8 and 9).Anonymousnoreply@blogger.com7tag:blogger.com,1999:blog-12168038.post-91399854227574911392009-08-04T11:34:00.000+05:302009-08-04T11:34:22.098+05:30Updating a Linux box behind proxy serverRecently I had to update a Linux (Fedora 9 to be precise) box, which was behind a proxy server. To get through the proxy server I configured relevant environment variables as described <a href="http://blog.vinodsingh.com/2008/09/proxy-setting-on-linux.html">here</a>. After executing <font face="Courier New">'sudo yum update'</font> command system seems to be hanged after emitting following line on the console-<br />
<br />
<font face="Courier New">Loaded plugins: refresh-packagekit</font><br />
<br />
After a long wait yum failed with following error message on console-<br />
<br />
<font face="Courier New">[vinod@localhost ~]# sudo yum update<br />
Loaded plugins: refresh-packagekit<br />
Could not retrieve mirrorlist http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=i386 error was<br />
[Errno 4] IOError: <urlopen error (110, 'Connection timed out')><br />
Error: Cannot retrieve repository metadata (repomd.xml) for repository: fedora. Please verify its path and try again</font><br />
<br />
OK, so <font face="Courier New">yum</font> was not able to get through proxy, but when I tried to access the same URL using <font face="Courier New">wget</font>-<br />
<br />
<font face="Courier New">[vinod@localhost ~]# wget http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-9&arch=i386</font><br />
<br />
it was able to access it. Then what could be wrong :-/<br />
<br />
After pondering for sometime, I realized that proxy configuration was done using a non-root user while <font face="Courier New">yum</font> was running as <font face="Courier New">root</font> (due to <b><font face="Courier New">sudo</font></b>). Environment variables configured for a user are not carried forwarded (to another user) by Linux when user is changed using <font face="Courier New">su</font> or <font face="Courier New">sudo</font> command. Configuring the proxy as root solved the problem and yum successfully updated system.Anonymousnoreply@blogger.com0tag:blogger.com,1999:blog-12168038.post-32592357146484805812009-08-03T12:10:00.002+05:302009-08-03T12:49:19.086+05:30Getting started with RedmineFor sometime I was looking for a project management software, which can provide an integrated view of most of the project related artifacts/activities. <a target="_blank" rel="nofollow" href="http://www.redmine.org/">Redmine</a> was the most impressive tool, I came across. It has issue tracking, wiki, forum, documents, integration with third party tools (source repositories, LDAP etc.), plugin to integrate with Hudson and many more features. It has plugin based architecture, so possibilities are almost endless.<br />
<br />
Redmine is written in Ruby (Ruby on Rail) and I know nothing about that other than spelling :-) I had to install several packages to get started with it. Redmine stores its data in a database and when it comes to open source DB, PostgreSQL is always my first choice. As a practice I prefer to serve all web applications through Apache and here I found an Apache module called Passenger, which can serve Ruby applications.<br />
<br />
Here are my notes, which I took during installing Redmine and supporting applications.<br />
<ol><li>Install PostgreSQL for Linux<br />
<font face="Courier New"> $ ./postgresql-x.x.x-x.bin --mode text</font><br />
<br />
Follow the installer instructions to complete the installation. Make sure that PostgreSQL accepts connections from other than 'localhost' else you can't connect to DB from any other machine. This can be done by editing '<font face="Courier New">pg_hba</font>' configuration file.<br />
</li><br />
<li>Install Ruby, Redmine needs ruby 1.8.7-<br />
<font face="Courier New"> $ tar zxvf ruby-1.8.7-p174.zip<br />
$ cd ruby-1.8.7-p174<br />
$ ./configure --prefix=/opt/redmine/ruby-1.8.7<br />
$ make<br />
$ make install</font><br />
<br />
Add ruby to your classpath before executing further installation steps.</li><br />
<li>Install ruby-gems, say rubygems-1.3.4<font face="Courier New"><br />
$ unzip rubygems-1.3.4.zip<br />
$ cd rubygems-1.3.4<br />
$ ruby setup.rb</font></li><br />
<li>Install rake<font face="Courier New"><br />
$ unzip rake-0.8.7.zip<br />
$ cd rake-0.8.7<br />
$ ruby install.rb</font></li><br />
<li>Install rails (Ruby On Rails) version 2.1.2 (required by Redmine)<br />
<font face="Courier New"> $ gem install rails -v 2.1.2</font><br />
<br />
The GEM needs internet connectivity to download and install the packages. Though there are ways to install packages locally, after downloading them manually. As I am new to Ruby so preferred the easiest installation method.</li><br />
<li>Install PostgreSQL adapter for Ruby<br />
Add PostgreSQL's 'bin' directory in PATH before executing following command-<br />
<font face="Courier New"> $ gem install pg</font></li><br />
<li>Install redmine<br />
<br />
<ul type="disc"><li>Unzip the downloaded archive<br />
<font face="Courier New">$ tar zxvf redmine-0.8.4.tar.gz</font></li><br />
<li>Create 'redmine' database user<br />
<font face="Courier New">CREATE ROLE redmine LOGIN PASSWORD 'redmine' NOSUPERUSER NOINHERIT CREATEDB NOCREATEROLE;</font></li><br />
<li>Create 'redmine' database<br />
<font face="Courier New">CREATE DATABASE redmine WITH ENCODING='UTF8' OWNER=redmine;</font></li><br />
<li>Define PosgtreSQL DB in '<font face="Courier New">/opt/redmine/redmine-0.8.4/config/database.yml</font>'<br />
<font face="Courier New"> production:<br />
adapter: postgresql<br />
database: redmine<br />
host: localhost<br />
port: 5432<br />
username: redmine<br />
password: "redmine"</font></li><br />
<li>Create the database structure, by running the following command under the redmine installation directory. It will create tables and an administrator account.<br />
<font face="Courier New"> $ rake db:migrate RAILS_ENV="production"</font></li><br />
<li>Insert default configuration data in database, by running the following command:<br />
<font face="Courier New"> $ rake redmine:load_default_data RAILS_ENV="production"</font></li>
</ul></li><br />
<li>Install Passenger module to enable Apache to serve ruby applications<br />
<font face="Courier New"> $ tar zxvf passenger-2.2.4.tar.gz<br />
$ cd passenger-2.2.4/bin<br />
$ export APXS2=/opt/redmine/apache-2.2.11/bin/apxs<br />
$ ./passenger-install-apache2-module</font></li><br />
<li>Configure Apache to server redmine or any other ruby application using passenger<br />
To publish a rubby application under a sub-uri, create a symbolic link to application's public directory under web root directory. Thats the way passenger works, pointing directly to application's public directory will not work here.<br />
<br />
<font face="Courier New"> $ ln -s /opt/redmine/redmine-0.8.4/public redmine</font><br />
<br />
Here is a sample configuration text from Apache's <font face="Courier New">httpd-vhosts.conf</font> file to serve Redmine-<br />
<br />
<font face="Courier New"> NameVirtualHost *:80<br />
<br />
LoadModule passenger_module /opt/redmine/passenger-2.2.4/ext/apache2/mod_passenger.so<br />
PassengerRoot /opt/redmine/passenger-2.2.4<br />
PassengerRuby /opt/redmine/ruby-1.8.7/bin/ruby<br />
<br />
<VirtualHost *:80><br />
ServerAdmin webmaster@mydomain.com<br />
ServerName redmine<br />
<br />
DocumentRoot "/opt/apache-2.2.11/htdocs"<br />
RailsEnv production<br />
RailsBaseURI /redmine<br />
<br />
ErrorLog "logs/error_log"<br />
CustomLog "logs/access_log" common<br />
<br />
<Directory "/opt/redmine/redmine-0.8.4/public"><br />
Options FollowSymLinks<br />
AllowOverride None<br />
Order deny,allow<br />
Allow from all<br />
</Directory><br />
</VirtualHost></font></li>
</ol>Now Redmine is available for action. Next steps could be configuring to integrate with LDAP and few more things or installing some plugins to enhance the functionality. Next I am looking for ways to migrate issue tracker from Bugzilla to Redmine, to have everything under one roof.Anonymousnoreply@blogger.com2tag:blogger.com,1999:blog-12168038.post-13022994243511469582009-07-17T13:16:00.001+05:302009-07-17T13:46:50.100+05:30Managing Maven repository with ArtifactoryIt is close to one year, since we started using Maven as build tool for new projects. Overall experience with Maven has been very good. We are managing a simple disk based directory structure, which is served by Apache as local Maven repository. All artifacts in the local repository are populated manually by copying from a local machine. As more and more projects started using Maven, managing repository became a headache.<br />
<br />
Need of a repository manager was felt to minimize the overhead of manually managing local repository. This <a href='http://docs.codehaus.org/display/MAVENUSER/Maven+Repository+Manager+Feature+Matrix' target='_blank' rel='nofollow'>wiki article</a> has a good comparison of Archiva, <a href='http://artifactory.jfrog.org/' target='_blank' rel='nofollow'>Artifactory</a> and Nexus. After a glance at comparison the Artifactory and Nexus seems to have more less similar feature set. The out of the box LDAP integration in Artifactory (Nexus has this feature in paid version) is a big differentiator, this feature was the main reason of going with Artifactory instead of Nexus.<br />
<br />
Artifactory keeps its data in a database using <a rel='nofollow' href='http://jackrabbit.apache.org/' target='_blank'>Jackrabbit</a>, a Java Content Repository (JSR 170) implementation and Jackrabbit supports almost all popular databases see the list <a href='http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/fs/db/' target='_blank' rel='nofollow'>here</a> (these are the DDL scripts for all supported databases and executing when Artifactory is started first time). By default Artifactory uses Derby and other than that they have documented just about using MySQL. Personally I always prefer PostgreSQL over MySQL, so decided to go with PostgreSQL along with WAR based deployment in Tomcat.<br />
<br />
Artifactory works out of the box just by dropping the war file in Tomcat and on first run it creates a Derby database in <font face='Courier New'>${user.home}/.artifactory/data</font> directory. To change the database to PostgreSQL or something else stop the Tomcat and follow the steps given below-<br />
<br />
1. Delete <font face='Courier New'>${user.home}/.artifactory/data</font> directory.<br />
<br />
2. Uncomment and change following line in <font face='Courier New'>${user.home}/.artifactory/etc/artifactory.system.properties</font> file-<br />
<font face='Courier New'>artifactory.jcr.configPath=repo/postgresql</font><br />
<br />
3. Create <font face='Courier New'>${user.home}/.artifactory/etc/repo/postgresql/repo.xml</font> with following contents (this is based on MySQL configuration file available in Artifactory code base)-<br />
<pre class="xml" name="code"><?xml version="1.0" encoding="ISO-8859-1"?>
<!--<!DOCTYPE Repository SYSTEM "config.dtd">-->
<Repository>
<!--
virtual file system where the repository stores global state
(e.g. registered namespaces, custom node types, etc.)
-->
<!-- PostgreSQL Filesystem -->
<FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
<param name="driver" value="org.postgresql.Driver"/>
<param name="url" value="jdbc:postgresql://localhost:5432/artifactory"/>
<param name="user" value="artifactory_user"/>
<param name="password" value="password"/>
<param name="schema" value="postgresql"/>
</FileSystem>
<!-- http://wiki.apache.org/jackrabbit/DataStore -->
<!-- PostgreSQL Datastore -->
<DataStore class="org.artifactory.jcr.jackrabbit.ArtifactoryDbDataStoreImpl">
<param name="url" value="jdbc:postgresql://localhost:5432/artifactory"/>
<param name="tablePrefix" value=""/>
<param name="user" value="artifactory_user"/>
<param name="password" value="password"/>
<param name="databaseType" value="postgresql"/>
<param name="driver" value="org.postgresql.Driver"/>
<param name="minRecordLength" value="512"/>
<param name="maxConnections" value="15"/>
<param name="copyWhenReading" value="true"/>
</DataStore>
<!--
security configuration
-->
<Security appName="Jackrabbit">
<!--
access manager:
class: FQN of class implementing the AccessManager interface
-->
<AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager">
<!-- <param name="config" value="${rep.home}/access.xml"/> -->
</AccessManager>
<LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule">
<!-- anonymous user name ('anonymous' is the default value) -->
<param name="anonymousId" value="anonymous"/>
<!--
default user name to be used instead of the anonymous user
when no login credentials are provided (unset by default)
-->
<param name="defaultUserId" value="superuser"/>
</LoginModule>
</Security>
<!--
location of workspaces root directory and name of default workspace
-->
<Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
<!--
workspace configuration template:
used to create the initial workspace if there's no workspace yet
-->
<Workspace name="${wsp.name}">
<!--
virtual file system of the workspace:
class: FQN of class implementing the FileSystem interface
-->
<FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
<param name="path" value="${wsp.home}"/>
</FileSystem>
<!--
persistence manager of the workspace:
class: FQN of class implementing the PersistenceManager interface
-->
<!-- PostgreSQL Persistance Manager -->
<PersistenceManager class="org.apache.jackrabbit.core.persistence.bundle.PostgreSQLPersistenceManager">
<param name="url" value="jdbc:postgresql://localhost:5432/artifactory"/>
<param name="user" value="artifactory_user"/>
<param name="password" value="password"/>
<param name="schemaObjectPrefix" value="${wsp.name}_"/>
</PersistenceManager>
<!-- http://issues.apache.org/jira/browse/JCR-314 -->
<ISMLocking class="org.apache.jackrabbit.core.state.FineGrainedISMLocking"/>
<!--
Search index and the file system it uses.
class: FQN of class implementing the QueryHandler interface
If required by the QueryHandler implementation, one may configure
a FileSystem that the handler may use.
Supported parameters for lucene search index:
- path: location of the index. This parameter is mandatory!
- useCompoundFile: advises lucene to use compound files for the index files
- minMergeDocs: minimum number of nodes in an index until segments are merged
- volatileIdleTime: idle time in seconds until the volatile index is
moved to persistent index even though minMergeDocs is not reached.
- maxMergeDocs: maximum number of nodes in segments that will be merged
- mergeFactor: determines how often segment indices are merged
- maxFieldLength: the number of words that are fulltext indexed at most per property.
- bufferSize: maximum number of documents that are held in a pending
queue until added to the index
- cacheSize: size of the document number cache. This cache maps
uuids to lucene document numbers
- forceConsistencyCheck: runs a consistency check on every startup. If
false, a consistency check is only performed when the search index
detects a prior forced shutdown. This parameter only has an effect
if 'enableConsistencyCheck' is set to 'true'.
- enableConsistencyCheck: if set to 'true' a consistency check is
performed depending on the parameter 'forceConsistencyCheck'. If
set to 'false' no consistency check is performed on startup, even
if a redo log had been applied.
- autoRepair: errors detected by a consistency check are automatically
repaired. If false, errors are only written to the log.
- analyzer: class name of a lucene analyzer to use for fulltext indexing of text.
- queryClass: class name that implements the javax.jcr.query.Query interface.
this class must extend the class: org.apache.jackrabbit.core.query.AbstractQueryImpl
- respectDocumentOrder: If true and the query does not contain an 'order by' clause,
result nodes will be in document order. For better performance when queries return
a lot of nodes set to 'false'.
- resultFetchSize: The number of results the query handler should
initially fetch when a query is executed.
Default value: Integer.MAX_VALUE (-> all)
- extractorPoolSize: defines the maximum number of background threads that are
used to extract text from binary properties. If set to zero (default) no
background threads are allocated and text extractors run in the current thread.
- extractorTimeout: a text extractor is executed using a background thread if it
doesn't finish within this timeout defined in milliseconds. This parameter has
no effect if extractorPoolSize is zero.
- extractorBackLogSize: the size of the extractor pool back log. If all threads in
the pool are busy, incomming work is put into a wait queue. If the wait queue
reaches the back log size incomming extractor work will not be queued anymore
but will be executed with the current thread.
- synonymProviderClass: the name of a class that implements
org.apache.jackrabbit.core.query.lucene.SynonymProvider. The
default value is null (-> not set).
Note: all parameters (except path) in this SearchIndex config are default
values and can be omitted.
-->
<SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
<param name="path" value="${rep.home}/index"/>
<param name="useCompoundFile" value="true"/>
<!-- Default is 100 -->
<param name="minMergeDocs" value="500"/>
<param name="maxMergeDocs" value="10000"/>
<param name="volatileIdleTime" value="3"/>
<!-- Default is 10: more segments quicker the indexing but slower the searching -->
<param name="mergeFactor" value="10"/>
<param name="maxFieldLength" value="10000"/>
<!-- Default is 10 -->
<param name="bufferSize" value="100"/>
<param name="cacheSize" value="1000"/>
<param name="forceConsistencyCheck" value="false"/>
<param name="enableConsistencyCheck" value="true"/>
<param name="autoRepair" value="true"/>
<param name="analyzer" value="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
<param name="queryClass" value="org.apache.jackrabbit.core.query.QueryImpl"/>
<param name="respectDocumentOrder" value="false"/>
<param name="resultFetchSize" value="700"/>
<param name="supportHighlighting" value="true"/>
<!--
Use 5 background threads for text extraction that takes more than 100 milliseconds
-->
<param name="extractorPoolSize" value="5"/>
<param name="extractorTimeout" value="100"/>
<!-- Default is 100 -->
<param name="extractorBackLogSize" value="500"/>
<!-- Indexing configuration -->
<!--<param name="indexingConfiguration" value="${rep.home}/index/index_config.xml"/>-->
</SearchIndex>
</Workspace>
<!--
Configures the versioning
-->
<Versioning rootPath="${rep.home}/version">
<!--
Configures the filesystem to use for versioning for the respective
persistence manager
-->
<FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
<param name="path" value="${rep.home}/version"/>
</FileSystem>
<!--
Configures the perisistence manager to be used for persisting version state.
Please note that the current versioning implementation is based on
a 'normal' persistence manager, but this could change in future
implementations.
-->
<!--We do not use versionning-->
<PersistenceManager class="org.apache.jackrabbit.core.persistence.mem.InMemPersistenceManager">
<param name="persistent" value="false"/>
</PersistenceManager>
</Versioning>
<!-- Clustering configuration -->
<!--
<Cluster id="node1">
<Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
<param name="revision" value="${rep.home}/revision.log"/>
<param name="driver" value="org.postgresql.Driver"/>
<param name="url"
value="jdbc:postgresql://localhost:5432/artifactory"/>
<param name="user" value="artifactory_user"/>
<param name="password" value="password"/>
</Journal>
</Cluster>
-->
</Repository></pre>4. Create Artifactory database using following script-<br />
<font face='Courier New'> CREATE ROLE artifactory_user LOGIN PASSWORD 'password' NOINHERIT VALID UNTIL 'infinity';<br />
<br />
CREATE DATABASE artifactory WITH ENCODING='UTF8' OWNER=artifactory_user;</font><br />
<br />
5. Copy PostgreSQL JDBC driver to <font face='Courier New'>$TOMCAT_HOME/lib</font> directory.<br />
<br />
6. Restart Tomcat and we are good to go.<br />
<br />
Following two steps are optional.<br />
<br />
7. Do the LDAP (Active Directory in our case) integration using "Admin > Security > LDAP Settings" screen.<br />
<br />
8. Using "Admin > Security > Groups" screen create a group say 'developers' and check the option to automatically join all new users to this group. Now whenever someone tries to login, Artifactory will delegate the task of authentication to LDAP and on successful authentication it will create an account (if not already exist) and include that user in this group.<br />
<br />
Now all installation and configuration steps are complete, I proceeded for a project build using Artifactory repository URL <font face='Courier New'>http://<server>:<port>/artifactory/repo/</font>. Oops! Maven was not able to get any artifacts :-(, when looked into Artifactory logs, there was a following error message-<br />
<br />
<font face='Courier New'>2009-07-17 17:17:56,932 [WARN ] (o.a.r.RemoteRepoBase:199) - repo1: Error in getting information for 'org/apache/maven/plugins/maven-compiler-plugin/2.0.2/maven-compiler-plugin-2.0.2.pom' (Failed retrieving resource from http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/2.0.2/maven-compiler-plugin-2.0.2.pom: repo1.maven.org).</font><br />
<br />
What could be the reason??? Error message does not give much information. Well server was behind a proxy and Artifactory needs to be told to use the proxy server for internet access. After configuring a proxy, one need to edit the configuration of all remote repositories individually to use the proxy. IMO it would have been better to have an option during proxy configuration to declare that as global proxy to be used by all repositories.<br />
<br />
Now artifactory is working fine and taken some workload off from my busy schedule, all thanks to the great work being done by JFrog guys.Anonymousnoreply@blogger.com3tag:blogger.com,1999:blog-12168038.post-6589973332372998522009-06-12T11:12:00.000+05:302009-06-12T11:12:15.804+05:30Copy, move and delete files using Java<u><b>Delete</b></u><br />
Deleting files in Java is fairly simple, just one method call-<br />
<br />
<font face='Courier New'> new File("file path").delete();</font><br />
<br />
Only caveat here is that 'non-empty' directories can not be deleted. Of course you will need to have write permission (required for all write operations on file) on that directory as well.<br />
<br />
<u><b>Move</b></u><br />
Moving files is also as simple as deleting, just one method call-<br />
<br />
<font face='Courier New'> new File("source file path").renameTo(new File("destination file path"));</font><br />
<br />
<u><b>Copy</b></u><br />
<br />
Copying files in Java needs some heavy lifting as there is no API to do this task. Here is an example to copy files from one directory to another-<br />
<pre class="java" name="code">public void copyFiles(String source, String destination) throws IOException {
File srcDir = new File(source);
File[] files = srcDir.listFiles();
FileChannel in = null;
FileChannel out = null;
for (File file : files) {
try {
in = new FileInputStream(file).getChannel();
File outFile = new File(destination, file.getName());
out = new FileOutputStream(outFile).getChannel();
in.transferTo(0, in.size(), out);
} finally {
if (in != null)
in.close();
if (out != null)
out.close();
}
}
}</pre>The above example uses NIO API from Java 5, which finishes task pretty fast.Anonymousnoreply@blogger.com6tag:blogger.com,1999:blog-12168038.post-39009996789320863572009-06-06T13:01:00.002+05:302009-06-06T13:07:25.265+05:30Spell checking with FCKeditor<a rel="nofollow" href="http://www.fckeditor.net/" target="_blank">FCKeditor</a> is an excellent tool for providing rich text editing experience on web. Best part is that it works almost out of the box, there is very little to configure. Spelling checking is an exception here. Though spell checking also works out of the box, but with Internet Explorer only, which is not the only browser out there. Recently I used FCKeditor in a Java web application and had to integrate the spell check functionality as well. The spellar pages + <a rel="nofollow" href="http://aspell.net/" target="_blank">aspell</a> + PHP combination works pretty well for this task. FCKeditor comes with perl, PHP and Cold Fusion scripts for spell checking, I used PHP. In this post I will try to outline the steps required to make spell check working in FCKeditor.<br /><br /><b>1. Install PHP</b><br />PHP installation (I did it on Fedora) is pretty simple, just download the source and execute following command sequence-<br /><blockquote><span style="font-family:Courier New;">tar –xvzf php-5.2.9.tar.gz<br />cd php-5.2.9<br />./configure --prefix=/path_to_install \<br /> --with-apxs2=/apache_install_dir/bin/apxs \<br /> --disable-cli \<br /> --disable-cgi<br />make<br />make install<br /></span></blockquote>For more details about PHP installation see <a rel="nofollow" href="http://www.php.net/install" target="_blank">PHP documentation</a>.<br /><br /><b>2. Integrate PHP with Apache</b><br />Apache needs to be instructed to invoke PHP interpreter for all requests for PHP files ('<span style="font-family:Courier New;">.php</span>' extension). First we need to include PHP module by adding following line in <span style="font-family:Courier New;">/apache_install_dir/conf/httpd.conf</span>-<br /><blockquote><span style="font-family:Courier New;">LoadModule php5_module modules/libphp5.so</span></blockquote>In same configuration file add PHP handler-<br /><blockquote><span style="font-family:Courier New;"><Directory "/apache_install_dir/htdocs"><br /> . . . .<br /> . . . .<br /> <FilesMatch \.php$ ><br /> SetHandler application/x-httpd-php<br /> </FilesMatch><br /></Directory></span></blockquote><b>3. Install aspell and dictionaries</b><br />Aspell provides dictionaries for all major world languages. Installation of aspell and its dictionaries is fairly straightforward. After downloading aspell and dictionaries execute following command sequence for each of the downloaded archive-<br /><blockquote><span style="font-family:Courier New;">tar –xvzf archive_name.tar.gz<br />cd archive_name<br />./configure<br />make<br />make install</span></blockquote><b>4. FCKeditor Configuration</b><br />By default FCKeditor is configured to use 'ieSpell', change it to use 'SpellarPages' instead. Also make sure it uses PHP and does spell checking in FireFox. Following properties needs to be changed in 'fckconfig.js'-<br /><blockquote><span style="font-family:Courier New;">FCKConfig.SpellChecker = 'SpellerPages' ;<br />FCKConfig.SpellerPagesServerScript = 'server-scripts/spellchecker.php' ;<br />FCKConfig.FirefoxSpellChecker = true ;</span></blockquote>Now configure the language and 'aspell' path in 'spellchecker.php'-<br /><blockquote><span style="font-family:Courier New;">$aspell_prog = 'aspell';<br />$lang = 'en_US';</span></blockquote><br />Now we are ready to test the application and see spell check in action. All software versions being used for this post are outlined below-<br /><ul><li>Apache 2.2.11</li><li>PHP 5.2.9</li><li>FCKeditor 2.6.4 (with bundled Spellar Pages)</li><li>Aspell 0.60.6</li><li>Tomcat 6.0.18</li><li>Java 6 update 13</li><li>Fedora 9</li></ul>To know more about using Apache to serve Java application see <a href="/2009/01/apache-in-front-of-tomcat-and-jboss.html">Apache in from of Tomcat and JBoss</a>.Anonymousnoreply@blogger.com2tag:blogger.com,1999:blog-12168038.post-17434303955492197922009-05-17T09:04:00.005+05:302009-05-17T09:36:17.695+05:30Install Linux (Ubuntu) from USB driveRecently one of our old laptop was crashed, so I had to re-install the operating system. We lost the Windows XP CD came with it, so installed XP from another CD. New installation was not ready to accept the keys mentioned on the sticker at the bottom of the laptop. So I decided to install Ubuntu 9.04 using a USB drive. <a rel='nofollow' href='https://help.ubuntu.com/community/Installation/FromUSBStick' target='_blank'>This document</a> has detailed information about USB installation. As no Ubuntu installation was available at that time, so creating a boot USB drive from downloaded ISO file was the only option and <a rel='nofollow' href='http://unetbootin.sourceforge.net/' target='_blank'>UNetbootin</a> did the task nicely on Dell Latitude D630. But IBM ThinkPad R51 refused to boot from the USB disk (Kingston DataTraveler 4GB), anyway I was prepared for such failures and tried with another USB drive (Sandisk Cruzer micro 2GB), which worked well. On first boot system recognized my wireless network and connected instantly along with nice sound blowing from speakers.<br/><br/>My experience with USB installation was pretty nice and it took just 15 minutes for the whole exercise. Ubuntu installation was much easier then Windows XP.Anonymousnoreply@blogger.com1tag:blogger.com,1999:blog-12168038.post-3129026020953825292009-04-22T03:44:00.001+05:302009-05-17T09:38:27.960+05:30Encoding warning with Maven 2.1.0Recently I updated Maven to newest release (2.1.0) for a new project. Upgrade from 2.0.9 was smooth and all existing build scripts were working fine. While glancing at the logs emitted by Maven found this warning message-<br /><br /><span style="color: #ff6666; font-family: Courier New;">[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!</span><br /><br />This warning message was not emitted by Maven 2.0.9. Reason of this warning message is that maven (plugins) use platform default encoding and it notifies the users about the build being platform dependent. This article (<a rel='nofollow' href="http://docs.codehaus.org/display/MAVENUSER/POM+Element+for+Source+File+Encoding">http://docs.codehaus.org/display/MAVENUSER/POM+Element+for+Source+File+Encoding</a>) has detailed information about this change. Instead of using platform default encoding, Maven can be instructed to use any encoding by adding following property in the pom.xml<br /><br /><span style="color: #3333ff; font-family: Courier New;"> <properties><br /> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><br /> </properties></span><br /><br />and it no longer emits the warning message.Anonymousnoreply@blogger.com12tag:blogger.com,1999:blog-12168038.post-91471861680751184812009-04-16T11:37:00.002+05:302009-12-01T14:23:33.419+05:30Contact Me application with Google AppsBlogosphere is buzzing with Google App Engine for Java (GAEJ), so thought to give it a try. Google has put decent documentation to start with. Any Java web developer can start straight away with it. Only learning curve is to know the limitations of GAEJ. I would not talk about the features and limitations of it as there is lot of information is made available by Google and the people who dissected it before me :-)<br />
<br />
I wanted to have a small application having contact form, which I wanted to put on my blog instead of displaying the email address. Here is the html / jsp page having contact form, gathering name, email, subject and the message-<br />
<pre class="html" name="code"><%@ taglib prefix='c' uri='http://java.sun.com/jstl/core'%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Contact Me</title>
</head>
<body>
<h1>Contact Me!</h1>
<!-- Show error message, if any. -->
<font color='red'><c:out value="${requestScope.errorMessage}" escapeXml="false" /></font>
<form action="contactme" method="post">
<table>
<tr>
<td><label for="name">Name : </label></td>
<td><input type="text" size="40" maxlength="40" title="Name" id="name" name="name" value="<c:out value='${param.name}' />" /></td>
</tr>
<tr>
<td><label for="email">Email : </label></td>
<td><input type="text" size="40" maxlength="60" title="Email" id="email" name="email" value="<c:out value='${param.email}' />" /></td>
</tr>
<tr>
<td><label for="subject">Subject : </label></td>
<td><input type="text" size="40" maxlength="80" title="Subject" id="subject" name="subject" value="<c:out value='${param.subject}' />" /></td>
</tr>
<tr>
<td><label for="message" style="vertical-align: text-top">Message : </label></td>
<td><textarea rows="10" cols="60" id="message" name="message" ><c:out value='${param.message}' /></textarea></td>
</tr>
<tr>
<td colspan="2"><input type="submit" title="Submit" value="Submit">
<br /> All fields are required.</td>
</tr>
</table>
</form>
</body>
</html></pre><br />
This is the same html / jsp, which we are using for several years. Okay, now here is the servlet backing this form-<br />
<pre class="java" name="code">package com.vinodsingh.gae.contact;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.repackaged.org.apache.commons.logging.Log;
import com.google.appengine.repackaged.org.apache.commons.logging.LogFactory;
public class ContactMeServlet extends HttpServlet {
private Log log = LogFactory.getLog(getClass());
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
if (validate(req, resp)) {
sendMail(req, resp);
req.setAttribute("name", req.getParameter("name"));
req.getRequestDispatcher("/thanks.jsp").forward(req, resp);
} else {
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
} catch (Exception e) {
log.error("Error while processing contact request", e);
}
}
// do very basic validation
@SuppressWarnings("unchecked")
private boolean validate(HttpServletRequest req, HttpServletResponse resp) {
StringBuilder errorMessage = new StringBuilder();
Map<String, String[]> params = req.getParameterMap();
for (Entry<String, String[]> param : params.entrySet()) {
String key = param.getKey();
String value = param.getValue()[0];
if ("name".equals(key))
if (value.length() < 5)
errorMessage.append("Please enter a valid name.\n");
if ("email".equals(key)) {
if (value.length() < 12)
errorMessage.append("Please enter a valid email address.<br />");
else if (value.indexOf('@') < 1)
errorMessage.append("Please enter a valid email address.<br />");
}
if ("subject".equals(key))
if (value.length() < 15)
errorMessage.append("Please write a subject of at least 15 characters.<br />");
if ("message".equals(key))
if (value.length() < 50)
errorMessage.append("Please write a message of at least 50 characters.<br />");
}
if (errorMessage.length() > 0) {
req.setAttribute("errorMessage", errorMessage.toString());
return false;
}
return true;
}
// Send email
private void sendMail(HttpServletRequest req, HttpServletResponse resp) {
Session session = Session.getDefaultInstance(new Properties(), null);
try {
log.info("Got a contact request from: " + req.getParameter("email") + ", " + req.getParameter("name"));
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("me@mydomain.com", req.getParameter("name")));
msg.setReplyTo(new Address[] { new InternetAddress(req.getParameter("email"), req.getParameter("name")) });
// send email to both myself and the sender
msg.addRecipient(Message.RecipientType.TO, new InternetAddress(req.getParameter("email"), req
.getParameter("name")));
msg.addRecipient(Message.RecipientType.CC, new InternetAddress("me@mydomain.com", "Vinod Singh"));
msg.setSubject(req.getParameter("subject"));
msg.setText(req.getParameter("message"));
Transport.send(msg);
} catch (Exception e) {
log.error("Error while sending emai", e);
}
}
}</pre>Here only unusual thing is the mail sending part, where we do not need to have the SMTP thing to send the email, GAE will use application owner's email instead. All is well till now.<br />
<br />
So it is the time to deploy the application. BOOM, deploy is failed miserably with this error-<br />
<br />
<font face='Courier New'>Unable to upload:<br />
java.lang.IllegalStateException: cannot find javac executable based on java.home, tried "C:\Program Files\Java\jre6\bin\javac.exe" and "C:\Program Files\Java\bin\javac.exe"<br />
at com.google.appengine.tools.admin.AppAdminFactory$ApplicationProcessingOptions.getJavaCompiler(AppAdminFactory.java:325)<br />
at com.google.appengine.tools.admin.Application.compileJavaFiles(Application.java:340)<br />
at com.google.appengine.tools.admin.Application.compileJsps(Application.java:326)<br />
at com.google.appengine.tools.admin.Application.createStagingDirectory(Application.java:235)<br />
at com.google.appengine.tools.admin.AppAdminImpl.update(AppAdminImpl.java:39)<br />
at com.google.appengine.tools.admin.AppCfg$UpdateAction.execute(AppCfg.java:469)<br />
at com.google.appengine.tools.admin.AppCfg.<init>(AppCfg.java:114)<br />
at com.google.appengine.tools.admin.AppCfg.main(AppCfg.java:59)</font><br />
<br />
I have Java Development Kit 1.5.x and 1.6.x along with one more JRE on my machine (Vista), still GAE SDK can't find the <font face='Courier New'>'javac'</font> :-( This issue is logged <a href='http://code.google.com/p/googleappengine/issues/detail?id=1226' target='_blank'>here</a>. Removing all JREs and keeping just the JDK makes it work. Okay finally my application is up and running and ready to receive the contact requests. I tried it myself and it worked as expected. As I was adding the contact person's email address in 'Reply-to' field, so I was expecting to GMail to fill that in 'TO' address on hitting the 'Reply' button. Here I came to know that GMail discards 'Reply-to' field altogether. Anyway this GMail issue has nothing to do with GAE.<br />
<br />
GAE for Java looks a good step in adopting the Java for hobby. Before GAE if I wanted to create an application in java just for hobby, there was no hosting solution available at reasonable prices. While there are numerous options for PHP. I believe this will have a good impact on Java but if some of the restrictions removed as they do not conform to WORA concept of Java.Anonymousnoreply@blogger.com0